@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
@@ -0,0 +1,163 @@
1
+ import { configurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
2
+ import {
3
+ allCustomFieldsFragment,
4
+ booleanCustomFieldFragment,
5
+ customFieldConfigFragment,
6
+ dateTimeCustomFieldFragment,
7
+ floatCustomFieldFragment,
8
+ intCustomFieldFragment,
9
+ localeStringCustomFieldFragment,
10
+ localeTextCustomFieldFragment,
11
+ relationCustomFieldFragment,
12
+ stringCustomFieldFragment,
13
+ structCustomFieldFragment,
14
+ textCustomFieldFragment,
15
+ } from '@/vdb/providers/server-config.js';
16
+ import { ResultOf } from 'gql.tada';
17
+ import React from 'react';
18
+ import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
19
+
20
+ // Base custom field config
21
+ export type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
22
+
23
+ // Individual custom field type configurations
24
+ export type StringCustomFieldConfig = ResultOf<typeof stringCustomFieldFragment>;
25
+ export type LocaleStringCustomFieldConfig = ResultOf<typeof localeStringCustomFieldFragment>;
26
+ export type TextCustomFieldConfig = ResultOf<typeof textCustomFieldFragment>;
27
+ export type LocaleTextCustomFieldConfig = ResultOf<typeof localeTextCustomFieldFragment>;
28
+ export type BooleanCustomFieldConfig = ResultOf<typeof booleanCustomFieldFragment>;
29
+ export type IntCustomFieldConfig = ResultOf<typeof intCustomFieldFragment>;
30
+ export type FloatCustomFieldConfig = ResultOf<typeof floatCustomFieldFragment>;
31
+ export type DateTimeCustomFieldConfig = ResultOf<typeof dateTimeCustomFieldFragment>;
32
+ export type RelationCustomFieldConfig = ResultOf<typeof relationCustomFieldFragment>;
33
+ export type StructCustomFieldConfig = ResultOf<typeof structCustomFieldFragment>;
34
+
35
+ // Union type of all custom field configs
36
+ export type AllCustomFieldConfigs = ResultOf<typeof allCustomFieldsFragment>;
37
+
38
+ // Configurable operation argument definition
39
+ export type ConfigurableArgDef = ResultOf<typeof configurableOperationDefFragment>['args'][number];
40
+
41
+ // Union type for all field definitions
42
+ export type ConfigurableFieldDef = AllCustomFieldConfigs | ConfigurableArgDef;
43
+
44
+ // Struct field types (used within struct custom fields)
45
+ export type StructField = StructCustomFieldConfig['fields'][number];
46
+
47
+ // Individual struct field type configurations (for type guards)
48
+ export type StringStructField = Extract<StructField, { type: 'string' }>;
49
+ export type IntStructField = Extract<StructField, { type: 'int' }>;
50
+ export type FloatStructField = Extract<StructField, { type: 'float' }>;
51
+ export type BooleanStructField = Extract<StructField, { type: 'boolean' }>;
52
+ export type DateTimeStructField = Extract<StructField, { type: 'datetime' }>;
53
+
54
+ /**
55
+ * @description
56
+ * Props that get passed to all form input components. They are based on the
57
+ * controller props used by the underlying `react-hook-form`, i.e.:
58
+ *
59
+ * ```ts
60
+ * export type ControllerRenderProps = {
61
+ * onChange: (event: any) => void;
62
+ * onBlur: () => void;
63
+ * value: any;
64
+ * disabled?: boolean;
65
+ * name: string;
66
+ * ref: RefCallBack;
67
+ * };
68
+ * ```
69
+ *
70
+ * in addition, they can optionally be passed a `fieldDef` prop if the
71
+ * component is used in the context of a custom field or configurable operation arg.
72
+ *
73
+ * The `fieldDef` arg, when present, has the following shape:
74
+ *
75
+ * ```ts
76
+ * export type ConfigurableArgDef = {
77
+ * defaultValue: any
78
+ * description: string | null
79
+ * label: string | null
80
+ * list: boolean
81
+ * name: string
82
+ * required: boolean
83
+ * type: string
84
+ * ui: any
85
+ * }
86
+ * ```
87
+ *
88
+ * @docsCategory forms
89
+ * @docsPage DashboardFormComponent
90
+ */
91
+ export type DashboardFormComponentProps<
92
+ TFieldValues extends FieldValues = FieldValues,
93
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
94
+ > = ControllerRenderProps<TFieldValues, TName> & {
95
+ fieldDef?: ConfigurableFieldDef;
96
+ };
97
+
98
+ /**
99
+ * @description
100
+ * Metadata which can be defined on a {@link DashboardFormComponent} which
101
+ * provides additional information about how the dashboard should render the
102
+ * component.
103
+ *
104
+ * The metadata is defined by adding the static property on the component:
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * export const MyCustomInput: DashboardFormComponent = props => {
109
+ * // implementation omitted
110
+ * }
111
+ *
112
+ * // highlight-start
113
+ * MyCustomInput.metadata = {
114
+ * isListInput: true
115
+ * }
116
+ * // highlight-end
117
+ * ```
118
+ *
119
+ * @docsCategory forms
120
+ * @docsPage DashboardFormComponent
121
+ */
122
+ export type DashboardFormComponentMetadata = {
123
+ /**
124
+ * @description
125
+ * Defines whether this form component is designed to handle list inputs.
126
+ * If set to `'dynamic'`, it means the component has internal logic that can
127
+ * handle both lists and single values.
128
+ */
129
+ isListInput?: boolean | 'dynamic';
130
+ isFullWidth?: boolean;
131
+ };
132
+
133
+ /**
134
+ * @description
135
+ * This is the common type for all custom form components registered for:
136
+ *
137
+ * - custom fields
138
+ * - configurable operation args
139
+ * - detail page fields
140
+ *
141
+ * Here's a simple example:
142
+ *
143
+ * ```ts
144
+ * import { DashboardFormComponent, Input } from '\@vendure/dashboard';
145
+ *
146
+ * const MyComponent: DashboardFormComponent = (props) => {
147
+ * return <Input value={props.value}
148
+ * onChange={props.onChange}
149
+ * onBlur={props.onBlur}
150
+ * name={props.name}
151
+ * disabled={props.disabled}
152
+ * ref={props.ref}
153
+ * />;
154
+ * };
155
+ * ```
156
+ *
157
+ * @docsCategory forms
158
+ * @docsPage DashboardFormComponent
159
+ * @docsWeight 0
160
+ */
161
+ export type DashboardFormComponent = React.ComponentType<DashboardFormComponentProps> & {
162
+ metadata?: DashboardFormComponentMetadata;
163
+ };
@@ -3,33 +3,24 @@ import {
3
3
  isEnumType,
4
4
  isScalarType,
5
5
  } from '@/vdb/framework/document-introspection/get-document-structure.js';
6
- import { StructCustomFieldConfig } from '@vendure/common/lib/generated-types';
7
- import { ResultOf } from 'gql.tada';
6
+ import {
7
+ CustomFieldConfig,
8
+ DateTimeCustomFieldConfig,
9
+ FloatCustomFieldConfig,
10
+ IntCustomFieldConfig,
11
+ StringCustomFieldConfig,
12
+ StructCustomFieldConfig,
13
+ StructField,
14
+ } from '@/vdb/framework/form-engine/form-engine-types.js';
8
15
  import { z, ZodRawShape, ZodType, ZodTypeAny } from 'zod';
9
16
 
10
- import { structCustomFieldFragment } from '../../providers/server-config.js';
11
-
12
- type CustomFieldConfig = {
13
- name: string;
14
- type: string;
15
- pattern?: string;
16
- intMin?: number;
17
- intMax?: number;
18
- floatMin?: number;
19
- floatMax?: number;
20
- datetimeMin?: string;
21
- datetimeMax?: string;
22
- list?: boolean;
23
- nullable?: boolean;
24
- };
25
-
26
- type StructFieldConfig = ResultOf<typeof structCustomFieldFragment>['fields'][number];
27
-
28
- function mapGraphQLCustomFieldToConfig(field: StructFieldConfig): CustomFieldConfig {
29
- const baseConfig = {
30
- name: field.name,
31
- type: field.type,
17
+ function mapGraphQLCustomFieldToConfig(field: StructField) {
18
+ const { __typename, ...rest } = field;
19
+ const baseConfig: CustomFieldConfig = {
20
+ ...rest,
32
21
  list: field.list ?? false,
22
+ readonly: false,
23
+ requiresPermission: [],
33
24
  nullable: true, // Default to true since GraphQL fields are nullable by default
34
25
  };
35
26
 
@@ -37,26 +28,34 @@ function mapGraphQLCustomFieldToConfig(field: StructFieldConfig): CustomFieldCon
37
28
  case 'StringStructFieldConfig':
38
29
  return {
39
30
  ...baseConfig,
40
- pattern: field.pattern ?? undefined,
41
- };
31
+ __typename: 'StringCustomFieldConfig',
32
+ pattern: field.pattern ?? null,
33
+ options: [],
34
+ } satisfies StringCustomFieldConfig;
42
35
  case 'IntStructFieldConfig':
43
36
  return {
44
37
  ...baseConfig,
45
- intMin: field.intMin ?? undefined,
46
- intMax: field.intMax ?? undefined,
47
- };
38
+ __typename: 'IntCustomFieldConfig',
39
+ intMin: field.intMin ?? null,
40
+ intMax: field.intMax ?? null,
41
+ intStep: field.intStep ?? null,
42
+ } satisfies IntCustomFieldConfig;
48
43
  case 'FloatStructFieldConfig':
49
44
  return {
50
45
  ...baseConfig,
51
- floatMin: field.floatMin ?? undefined,
52
- floatMax: field.floatMax ?? undefined,
53
- };
46
+ __typename: 'FloatCustomFieldConfig',
47
+ floatMin: field.floatMin ?? null,
48
+ floatMax: field.floatMax ?? null,
49
+ floatStep: field.floatStep ?? null,
50
+ } satisfies FloatCustomFieldConfig;
54
51
  case 'DateTimeStructFieldConfig':
55
52
  return {
56
53
  ...baseConfig,
57
- datetimeMin: field.datetimeMin ?? undefined,
58
- datetimeMax: field.datetimeMax ?? undefined,
59
- };
54
+ __typename: 'DateTimeCustomFieldConfig',
55
+ datetimeMin: field.datetimeMin ?? null,
56
+ datetimeMax: field.datetimeMax ?? null,
57
+ datetimeStep: field.datetimeStep ?? null,
58
+ } satisfies DateTimeCustomFieldConfig;
60
59
  default:
61
60
  return baseConfig;
62
61
  }
@@ -116,7 +115,7 @@ function createDateValidationSchema(minDate: Date | undefined, maxDate: Date | u
116
115
  * @param pattern - Optional regex pattern string for validation
117
116
  * @returns Zod string schema with optional pattern validation
118
117
  */
119
- function createStringValidationSchema(pattern?: string): ZodType {
118
+ function createStringValidationSchema(pattern?: string | null): ZodType {
120
119
  let schema = z.string();
121
120
  if (pattern) {
122
121
  schema = schema.regex(new RegExp(pattern), {
@@ -134,30 +133,7 @@ function createStringValidationSchema(pattern?: string): ZodType {
134
133
  * @param max - Optional maximum value constraint
135
134
  * @returns Zod number schema with optional range validation
136
135
  */
137
- function createIntValidationSchema(min?: number, max?: number): ZodType {
138
- let schema = z.number();
139
- if (min != null) {
140
- schema = schema.min(min, {
141
- message: `Value must be at least ${min}`,
142
- });
143
- }
144
- if (max != null) {
145
- schema = schema.max(max, {
146
- message: `Value must be at most ${max}`,
147
- });
148
- }
149
- return schema;
150
- }
151
-
152
- /**
153
- * Creates a Zod validation schema for float fields with optional min/max constraints.
154
- * Used for float-type custom fields that may have numeric range limits.
155
- *
156
- * @param min - Optional minimum value constraint
157
- * @param max - Optional maximum value constraint
158
- * @returns Zod number schema with optional range validation
159
- */
160
- function createFloatValidationSchema(min?: number, max?: number): ZodType {
136
+ function createNumberValidationSchema(min?: number | null, max?: number | null): ZodType {
161
137
  let schema = z.number();
162
138
  if (min != null) {
163
139
  schema = schema.min(min, {
@@ -187,17 +163,23 @@ function createCustomFieldValidationSchema(customField: CustomFieldConfig): ZodT
187
163
  case 'localeString':
188
164
  case 'localeText':
189
165
  case 'string':
190
- zodType = createStringValidationSchema(customField.pattern);
166
+ zodType = createStringValidationSchema((customField as StringCustomFieldConfig).pattern);
191
167
  break;
192
168
  case 'int':
193
- zodType = createIntValidationSchema(customField.intMin, customField.intMax);
169
+ zodType = createNumberValidationSchema(
170
+ (customField as IntCustomFieldConfig).intMin,
171
+ (customField as IntCustomFieldConfig).intMax,
172
+ );
194
173
  break;
195
174
  case 'float':
196
- zodType = createFloatValidationSchema(customField.floatMin, customField.floatMax);
175
+ zodType = createNumberValidationSchema(
176
+ (customField as FloatCustomFieldConfig).floatMin,
177
+ (customField as FloatCustomFieldConfig).floatMax,
178
+ );
197
179
  break;
198
180
  case 'datetime': {
199
- const minDate = parseDate(customField.datetimeMin);
200
- const maxDate = parseDate(customField.datetimeMax);
181
+ const minDate = parseDate((customField as DateTimeCustomFieldConfig).datetimeMin);
182
+ const maxDate = parseDate((customField as DateTimeCustomFieldConfig).datetimeMax);
201
183
  zodType = createDateValidationSchema(minDate, maxDate);
202
184
  break;
203
185
  }
@@ -227,7 +209,7 @@ function createStructFieldSchema(structFieldConfig: StructCustomFieldConfig): Zo
227
209
 
228
210
  const nestedSchema: ZodRawShape = {};
229
211
  for (const structSubField of structFieldConfig.fields) {
230
- const config = mapGraphQLCustomFieldToConfig(structSubField as StructFieldConfig);
212
+ const config = mapGraphQLCustomFieldToConfig(structSubField);
231
213
  let subFieldType = createCustomFieldValidationSchema(config);
232
214
 
233
215
  // Handle list and nullable for struct sub-fields
@@ -253,7 +235,7 @@ function createStructFieldSchema(structFieldConfig: StructCustomFieldConfig): Zo
253
235
  * @param customField - Custom field config containing list/nullable flags
254
236
  * @returns Modified Zod schema with list/nullable modifiers applied
255
237
  */
256
- function applyListAndNullableModifiers(zodType: ZodType, customField: CustomFieldConfig): ZodType {
238
+ function applyCustomFieldModifiers(zodType: ZodType, customField: CustomFieldConfig): ZodType {
257
239
  let modifiedType = zodType;
258
240
 
259
241
  if (customField.list) {
@@ -262,7 +244,9 @@ function applyListAndNullableModifiers(zodType: ZodType, customField: CustomFiel
262
244
  if (customField.nullable !== false) {
263
245
  modifiedType = modifiedType.optional().nullable();
264
246
  }
265
-
247
+ if (customField.readonly) {
248
+ modifiedType = modifiedType.readonly();
249
+ }
266
250
  return modifiedType;
267
251
  }
268
252
 
@@ -299,7 +283,7 @@ function processCustomFieldsSchema(
299
283
  zodType = createCustomFieldValidationSchema(customField);
300
284
  }
301
285
 
302
- zodType = applyListAndNullableModifiers(zodType, customField);
286
+ zodType = applyCustomFieldModifiers(zodType, customField);
303
287
  const schemaPropertyName = getGraphQlInputName(customField);
304
288
  customFieldsSchema[schemaPropertyName] = zodType;
305
289
  }
@@ -319,7 +303,7 @@ export function createFormSchemaFromFields(
319
303
  const isEnum = isEnumType(field.type);
320
304
 
321
305
  if ((isScalar || isEnum) && field.name !== 'customFields') {
322
- schemaConfig[field.name] = getZodTypeFromField(field, customFieldConfigs);
306
+ schemaConfig[field.name] = getZodTypeFromField(field);
323
307
  } else if (field.name === 'customFields') {
324
308
  const customFieldsSchema =
325
309
  customFieldConfigs && customFieldConfigs.length > 0
@@ -391,7 +375,7 @@ export function getDefaultValueFromField(field: FieldInfo, defaultLanguageCode?:
391
375
  }
392
376
  }
393
377
 
394
- export function getZodTypeFromField(field: FieldInfo, customFieldConfigs?: CustomFieldConfig[]): ZodTypeAny {
378
+ export function getZodTypeFromField(field: FieldInfo): ZodTypeAny {
395
379
  let zodType: ZodType;
396
380
 
397
381
  // This function is only used for non-custom fields, so we don't need custom field logic here
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  DataDisplayComponent,
3
- DataInputComponent,
4
3
  useComponentRegistry,
5
4
  } from '@/vdb/framework/component-registry/component-registry.js';
6
5
  import { generateInputComponentKey } from '@/vdb/framework/extension-api/input-component-extensions.js';
6
+ import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
7
7
  import { usePageBlock } from '@/vdb/hooks/use-page-block.js';
8
8
  import { usePage } from '@/vdb/hooks/use-page.js';
9
9
  import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
@@ -34,7 +34,7 @@ export function OverriddenFormComponent({ fieldName, field, children }: Readonly
34
34
  const pageBlock = usePageBlock({ optional: true });
35
35
  const componentRegistry = useComponentRegistry();
36
36
  let DisplayComponent: DataDisplayComponent | undefined;
37
- let InputComponent: DataInputComponent | undefined;
37
+ let InputComponent: DashboardFormComponent | undefined;
38
38
  if (page.pageId && pageBlock?.blockId) {
39
39
  const customInputComponentKey = generateInputComponentKey(page.pageId, pageBlock.blockId, fieldName);
40
40
  DisplayComponent = componentRegistry.getDisplayComponent(customInputComponentKey);
@@ -1,3 +1,25 @@
1
+ import {
2
+ AllCustomFieldConfigs,
3
+ BooleanCustomFieldConfig,
4
+ BooleanStructField,
5
+ ConfigurableArgDef,
6
+ ConfigurableFieldDef,
7
+ DateTimeCustomFieldConfig,
8
+ DateTimeStructField,
9
+ FloatCustomFieldConfig,
10
+ FloatStructField,
11
+ IntCustomFieldConfig,
12
+ IntStructField,
13
+ LocaleStringCustomFieldConfig,
14
+ LocaleTextCustomFieldConfig,
15
+ RelationCustomFieldConfig,
16
+ StringCustomFieldConfig,
17
+ StringStructField,
18
+ StructCustomFieldConfig,
19
+ StructField,
20
+ TextCustomFieldConfig,
21
+ } from '@/vdb/framework/form-engine/form-engine-types.js';
22
+
1
23
  import { FieldInfo } from '../document-introspection/get-document-structure.js';
2
24
 
3
25
  /**
@@ -97,3 +119,204 @@ export function removeEmptyIdFields<T extends Record<string, any>>(values: T, fi
97
119
  recursiveRemove(result, fields);
98
120
  return result;
99
121
  }
122
+
123
+ // =============================================================================
124
+ // TYPE GUARDS FOR CONFIGURABLE FIELD DEFINITIONS
125
+ // =============================================================================
126
+
127
+ /**
128
+ * Determines if a field definition is a custom field config (vs configurable operation arg)
129
+ */
130
+ export function isCustomFieldConfig(input: ConfigurableFieldDef): input is AllCustomFieldConfigs {
131
+ return input.hasOwnProperty('readonly');
132
+ }
133
+
134
+ /**
135
+ * Determines if a field definition is a configurable operation argument
136
+ */
137
+ export function isConfigurableArgDef(input: ConfigurableFieldDef): input is ConfigurableArgDef {
138
+ return !input.hasOwnProperty('readonly');
139
+ }
140
+
141
+ // =============================================================================
142
+ // TYPE GUARDS FOR SPECIFIC CUSTOM FIELD TYPES
143
+ // =============================================================================
144
+
145
+ /**
146
+ * String custom field with optional pattern and options
147
+ */
148
+ export function isStringCustomFieldConfig(input: ConfigurableFieldDef): input is StringCustomFieldConfig {
149
+ return input.type === 'string' && isCustomFieldConfig(input);
150
+ }
151
+
152
+ /**
153
+ * String custom field that has options (select dropdown)
154
+ */
155
+ export function isStringFieldWithOptions(input: ConfigurableFieldDef): input is StringCustomFieldConfig {
156
+ const isCustomFieldWithOptions =
157
+ input.type === 'string' &&
158
+ isCustomFieldConfig(input) &&
159
+ input.hasOwnProperty('options') &&
160
+ Array.isArray((input as any).options);
161
+ if (isCustomFieldWithOptions) {
162
+ return true;
163
+ }
164
+ const isConfigArgWithOptions =
165
+ input.type === 'string' && isConfigurableArgDef(input) && Array.isArray(input.ui?.options);
166
+ if (isConfigArgWithOptions) {
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+
172
+ /**
173
+ * Locale string custom field
174
+ */
175
+ export function isLocaleStringCustomFieldConfig(
176
+ input: ConfigurableFieldDef,
177
+ ): input is LocaleStringCustomFieldConfig {
178
+ return input.type === 'localeString' && isCustomFieldConfig(input);
179
+ }
180
+
181
+ /**
182
+ * Text custom field (textarea)
183
+ */
184
+ export function isTextCustomFieldConfig(input: ConfigurableFieldDef): input is TextCustomFieldConfig {
185
+ return input.type === 'text' && isCustomFieldConfig(input);
186
+ }
187
+
188
+ /**
189
+ * Locale text custom field (localized textarea)
190
+ */
191
+ export function isLocaleTextCustomFieldConfig(
192
+ input: ConfigurableFieldDef,
193
+ ): input is LocaleTextCustomFieldConfig {
194
+ return input.type === 'localeText' && isCustomFieldConfig(input);
195
+ }
196
+
197
+ /**
198
+ * Boolean custom field
199
+ */
200
+ export function isBooleanCustomFieldConfig(input: ConfigurableFieldDef): input is BooleanCustomFieldConfig {
201
+ return input.type === 'boolean' && isCustomFieldConfig(input);
202
+ }
203
+
204
+ /**
205
+ * Integer custom field with optional min/max/step
206
+ */
207
+ export function isIntCustomFieldConfig(input: ConfigurableFieldDef): input is IntCustomFieldConfig {
208
+ return input.type === 'int' && isCustomFieldConfig(input);
209
+ }
210
+
211
+ /**
212
+ * Float custom field with optional min/max/step
213
+ */
214
+ export function isFloatCustomFieldConfig(input: ConfigurableFieldDef): input is FloatCustomFieldConfig {
215
+ return input.type === 'float' && isCustomFieldConfig(input);
216
+ }
217
+
218
+ /**
219
+ * DateTime custom field with optional min/max/step
220
+ */
221
+ export function isDateTimeCustomFieldConfig(input: ConfigurableFieldDef): input is DateTimeCustomFieldConfig {
222
+ return input.type === 'datetime' && isCustomFieldConfig(input);
223
+ }
224
+
225
+ /**
226
+ * Relation custom field (references another entity)
227
+ */
228
+ export function isRelationCustomFieldConfig(input: ConfigurableFieldDef): input is RelationCustomFieldConfig {
229
+ return input.type === 'relation' && isCustomFieldConfig(input);
230
+ }
231
+
232
+ /**
233
+ * Struct custom field (nested object with sub-fields)
234
+ */
235
+ export function isStructCustomFieldConfig(input: ConfigurableFieldDef): input is StructCustomFieldConfig {
236
+ return input.type === 'struct' && isCustomFieldConfig(input);
237
+ }
238
+
239
+ // Legacy alias for backward compatibility
240
+ export const isStructFieldConfig = isStructCustomFieldConfig;
241
+
242
+ // =============================================================================
243
+ // TYPE GUARDS FOR STRUCT FIELD TYPES (fields within struct custom fields)
244
+ // =============================================================================
245
+
246
+ /**
247
+ * String field within a struct custom field
248
+ */
249
+ export function isStringStructField(input: StructField): input is StringStructField {
250
+ return input.type === 'string';
251
+ }
252
+
253
+ /**
254
+ * String struct field that has options (select dropdown)
255
+ */
256
+ export function isStringStructFieldWithOptions(
257
+ input: StructField,
258
+ ): input is StringStructField & { options: any[] } {
259
+ return (
260
+ input.type === 'string' && input.hasOwnProperty('options') && Array.isArray((input as any).options)
261
+ );
262
+ }
263
+
264
+ /**
265
+ * Integer field within a struct custom field
266
+ */
267
+ export function isIntStructField(input: StructField): input is IntStructField {
268
+ return input.type === 'int';
269
+ }
270
+
271
+ /**
272
+ * Float field within a struct custom field
273
+ */
274
+ export function isFloatStructField(input: StructField): input is FloatStructField {
275
+ return input.type === 'float';
276
+ }
277
+
278
+ /**
279
+ * Boolean field within a struct custom field
280
+ */
281
+ export function isBooleanStructField(input: StructField): input is BooleanStructField {
282
+ return input.type === 'boolean';
283
+ }
284
+
285
+ /**
286
+ * DateTime field within a struct custom field
287
+ */
288
+ export function isDateTimeStructField(input: StructField): input is DateTimeStructField {
289
+ return input.type === 'datetime';
290
+ }
291
+
292
+ // =============================================================================
293
+ // UTILITY TYPE GUARDS
294
+ // =============================================================================
295
+
296
+ /**
297
+ * Determines if a field is a list/array field
298
+ */
299
+ export function isListField(input?: ConfigurableFieldDef): boolean {
300
+ return input && isCustomFieldConfig(input) ? Boolean(input.list) : false;
301
+ }
302
+
303
+ /**
304
+ * Determines if a field is readonly
305
+ */
306
+ export function isReadonlyField(input?: ConfigurableFieldDef): boolean {
307
+ return input && isCustomFieldConfig(input) ? Boolean(input.readonly) : false;
308
+ }
309
+
310
+ /**
311
+ * Determines if a field requires special permissions
312
+ */
313
+ export function hasPermissionRequirement(input: ConfigurableFieldDef): boolean {
314
+ return isCustomFieldConfig(input) && Boolean(input.requiresPermission);
315
+ }
316
+
317
+ /**
318
+ * Determines if a field is nullable
319
+ */
320
+ export function isNullableField(input: ConfigurableFieldDef): boolean {
321
+ return isCustomFieldConfig(input) && Boolean(input.nullable);
322
+ }