@vendure/dashboard 3.3.5-master-202506231200 → 3.3.5-master-202506241318

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.5-master-202506231200",
4
+ "version": "3.3.5-master-202506241318",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "^3.3.5-master-202506231200",
90
- "@vendure/core": "^3.3.5-master-202506231200",
89
+ "@vendure/common": "^3.3.5-master-202506241318",
90
+ "@vendure/core": "^3.3.5-master-202506241318",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "6f49cf141df0d5c95a0a93d908acd95b0979f278"
133
+ "gitHead": "6420b216556b8173cc4b581d1304cb6d11e04c60"
134
134
  }
@@ -1,7 +1,4 @@
1
- import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
2
- import { Control, ControllerRenderProps } from 'react-hook-form';
3
1
  import {
4
- Form,
5
2
  FormControl,
6
3
  FormDescription,
7
4
  FormField,
@@ -10,12 +7,15 @@ import {
10
7
  FormMessage,
11
8
  } from '@/components/ui/form.js';
12
9
  import { Input } from '@/components/ui/input.js';
10
+ import { CustomFormComponent } from '@/framework/form-engine/custom-form-component.js';
11
+ import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
13
12
  import { useUserSettings } from '@/hooks/use-user-settings.js';
14
- import { Switch } from '../ui/switch.js';
15
- import { CustomFieldType } from '@vendure/common/lib/shared-types';
16
- import { TranslatableFormField } from './translatable-form-field.js';
17
13
  import { customFieldConfigFragment } from '@/providers/server-config.js';
14
+ import { CustomFieldType } from '@vendure/common/lib/shared-types';
18
15
  import { ResultOf } from 'gql.tada';
16
+ import { Control, ControllerRenderProps } from 'react-hook-form';
17
+ import { Switch } from '../ui/switch.js';
18
+ import { TranslatableFormField } from './translatable-form-field.js';
19
19
 
20
20
  type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
21
21
 
@@ -29,59 +29,171 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
29
29
  const {
30
30
  settings: { displayLanguage },
31
31
  } = useUserSettings();
32
- function getTranslation(input: Array<{ languageCode: string; value: string }> | null | undefined) {
32
+
33
+ const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
33
34
  return input?.find(t => t.languageCode === displayLanguage)?.value;
34
- }
35
+ };
36
+
35
37
  const customFields = useCustomFieldConfig(entityType);
38
+
39
+ const getFieldName = (fieldDefName: string) => {
40
+ return formPathPrefix
41
+ ? `${formPathPrefix}.customFields.${fieldDefName}`
42
+ : `customFields.${fieldDefName}`;
43
+ };
44
+
36
45
  return (
37
46
  <div className="grid grid-cols-2 gap-4">
38
47
  {customFields?.map(fieldDef => (
39
- <div key={fieldDef.name}>
40
- {fieldDef.type === 'localeString' || fieldDef.type === 'localeText' ? (
41
- <TranslatableFormField
42
- control={control}
43
- name={formPathPrefix ? `${formPathPrefix}.customFields.${fieldDef.name}` : `customFields.${fieldDef.name}`}
44
- render={({ field }) => (
45
- <FormItem>
46
- <FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
47
- <FormControl>
48
- {fieldDef.readonly ? field.value : <FormInputForType fieldDef={fieldDef} field={field} />}
49
- </FormControl>
50
- </FormItem>
51
- )}
52
- />
53
- ) : (
54
- <FormField
55
- control={control}
56
- name={formPathPrefix ? `${formPathPrefix}.customFields.${fieldDef.name}` : `customFields.${fieldDef.name}`}
57
- render={({ field }) => (
58
- <FormItem>
59
- <FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
60
- <FormControl>
61
- {fieldDef.readonly ? field.value : <FormInputForType fieldDef={fieldDef} field={field} />}
62
- </FormControl>
63
- <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
64
- <FormMessage />
65
- </FormItem>
66
- )}
67
- />
68
- )}
69
- </div>
48
+ <CustomFieldItem
49
+ key={fieldDef.name}
50
+ fieldDef={fieldDef}
51
+ control={control}
52
+ fieldName={getFieldName(fieldDef.name)}
53
+ getTranslation={getTranslation}
54
+ />
70
55
  ))}
71
56
  </div>
72
57
  );
73
58
  }
74
59
 
75
- function FormInputForType({ fieldDef, field }: { fieldDef: CustomFieldConfig, field: ControllerRenderProps<any, any> }) {
76
- switch (fieldDef.type as CustomFieldType) {
60
+ interface CustomFieldItemProps {
61
+ fieldDef: CustomFieldConfig;
62
+ control: Control<any, any>;
63
+ fieldName: string;
64
+ getTranslation: (
65
+ input: Array<{ languageCode: string; value: string }> | null | undefined,
66
+ ) => string | undefined;
67
+ }
68
+
69
+ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: CustomFieldItemProps) {
70
+ const hasCustomFormComponent = fieldDef.ui && fieldDef.ui.component;
71
+ const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
72
+
73
+ // For locale fields, always use TranslatableFormField regardless of custom components
74
+ if (isLocaleField) {
75
+ return (
76
+ <TranslatableFormField
77
+ control={control}
78
+ name={fieldName}
79
+ render={({ field, ...props }) => (
80
+ <FormItem>
81
+ <FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
82
+ <FormControl>
83
+ {hasCustomFormComponent ? (
84
+ <CustomFormComponent
85
+ fieldDef={fieldDef}
86
+ fieldProps={{
87
+ ...props,
88
+ field: {
89
+ ...field,
90
+ disabled: fieldDef.readonly ?? false,
91
+ },
92
+ }}
93
+ />
94
+ ) : (
95
+ <FormInputForType fieldDef={fieldDef} field={field} />
96
+ )}
97
+ </FormControl>
98
+ <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
99
+ <FormMessage />
100
+ </FormItem>
101
+ )}
102
+ />
103
+ );
104
+ }
105
+
106
+ // For non-locale fields with custom components
107
+ if (hasCustomFormComponent) {
108
+ return (
109
+ <FormField
110
+ control={control}
111
+ name={fieldName}
112
+ render={fieldProps => (
113
+ <CustomFieldFormItem
114
+ fieldDef={fieldDef}
115
+ getTranslation={getTranslation}
116
+ fieldName={fieldProps.field.name}
117
+ >
118
+ <CustomFormComponent
119
+ fieldDef={fieldDef}
120
+ fieldProps={{
121
+ ...fieldProps,
122
+ field: {
123
+ ...fieldProps.field,
124
+ disabled: fieldDef.readonly ?? false,
125
+ },
126
+ }}
127
+ />
128
+ </CustomFieldFormItem>
129
+ )}
130
+ />
131
+ );
132
+ }
133
+
134
+ // For regular fields without custom components
135
+ return (
136
+ <FormField
137
+ control={control}
138
+ name={fieldName}
139
+ render={({ field }) => (
140
+ <CustomFieldFormItem
141
+ fieldDef={fieldDef}
142
+ getTranslation={getTranslation}
143
+ fieldName={field.name}
144
+ >
145
+ <FormInputForType fieldDef={fieldDef} field={field} />
146
+ </CustomFieldFormItem>
147
+ )}
148
+ />
149
+ );
150
+ }
151
+
152
+ interface CustomFieldFormItemProps {
153
+ fieldDef: CustomFieldConfig;
154
+ getTranslation: (
155
+ input: Array<{ languageCode: string; value: string }> | null | undefined,
156
+ ) => string | undefined;
157
+ fieldName: string;
158
+ children: React.ReactNode;
159
+ }
160
+
161
+ function CustomFieldFormItem({ fieldDef, getTranslation, fieldName, children }: CustomFieldFormItemProps) {
162
+ return (
163
+ <FormItem>
164
+ <FormLabel>{getTranslation(fieldDef.label) ?? fieldName}</FormLabel>
165
+ <FormControl>{children}</FormControl>
166
+ <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
167
+ <FormMessage />
168
+ </FormItem>
169
+ );
170
+ }
171
+
172
+ function FormInputForType({
173
+ fieldDef,
174
+ field,
175
+ }: {
176
+ fieldDef: CustomFieldConfig;
177
+ field: ControllerRenderProps<any, any>;
178
+ }) {
179
+ const isReadonly = fieldDef.readonly ?? false;
180
+
181
+ switch (fieldDef.type as CustomFieldType) {
77
182
  case 'string':
78
- return <Input {...field} />;
183
+ return <Input {...field} disabled={isReadonly} />;
79
184
  case 'float':
80
185
  case 'int':
81
- return <Input type="number" {...field} onChange={(e) => field.onChange(e.target.valueAsNumber)} />;
186
+ return (
187
+ <Input
188
+ type="number"
189
+ {...field}
190
+ disabled={isReadonly}
191
+ onChange={e => field.onChange(e.target.valueAsNumber)}
192
+ />
193
+ );
82
194
  case 'boolean':
83
- return <Switch checked={field.value} onCheckedChange={field.onChange} />;
195
+ return <Switch checked={field.value} onCheckedChange={field.onChange} disabled={isReadonly} />;
84
196
  default:
85
- return <Input {...field} />
197
+ return <Input {...field} disabled={isReadonly} />;
86
198
  }
87
199
  }
@@ -1,5 +1,12 @@
1
- import { Button } from '@vendure/dashboard';
2
- import { Checkbox } from '@vendure/dashboard';
1
+ import { api } from '@/graphql/api.js';
2
+ import { graphql } from '@/graphql/graphql.js';
3
+ import { Trans, useLingui } from '@/lib/trans.js';
4
+ import { zodResolver } from '@hookform/resolvers/zod';
5
+ import { useQuery } from '@tanstack/react-query';
6
+ import { useForm } from 'react-hook-form';
7
+ import { z } from 'zod';
8
+ import { Button } from '../ui/button.js';
9
+ import { Checkbox } from '../ui/checkbox.js';
3
10
  import {
4
11
  Form,
5
12
  FormControl,
@@ -8,16 +15,9 @@ import {
8
15
  FormItem,
9
16
  FormLabel,
10
17
  FormMessage,
11
- } from '@vendure/dashboard';
12
- import { Input } from '@vendure/dashboard';
13
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@vendure/dashboard';
14
- import { api } from '@vendure/dashboard';
15
- import { graphql } from '@vendure/dashboard';
16
- import { zodResolver } from '@hookform/resolvers/zod';
17
- import { Trans, useLingui } from '@/lib/trans.js';
18
- import { useQuery } from '@tanstack/react-query';
19
- import { useForm } from 'react-hook-form';
20
- import { z } from 'zod';
18
+ } from '../ui/form.js';
19
+ import { Input } from '../ui/input.js';
20
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select.js';
21
21
 
22
22
  // Query document to fetch available countries
23
23
  const getAvailableCountriesDocument = graphql(`
@@ -58,7 +58,12 @@ interface CustomerAddressFormProps<T = any> {
58
58
  onCancel?: () => void;
59
59
  }
60
60
 
61
- export function CustomerAddressForm<T>({ address, setValuesForUpdate, onSubmit, onCancel }: CustomerAddressFormProps<T>) {
61
+ export function CustomerAddressForm<T>({
62
+ address,
63
+ setValuesForUpdate,
64
+ onSubmit,
65
+ onCancel,
66
+ }: CustomerAddressFormProps<T>) {
62
67
  const { i18n } = useLingui();
63
68
 
64
69
  // Fetch available countries
@@ -6,8 +6,8 @@ export function LogoMark(props: React.ComponentProps<'svg'>) {
6
6
  fill="currentColor"
7
7
  />
8
8
  <path
9
- fill-rule="evenodd"
10
- clip-rule="evenodd"
9
+ fillRule="evenodd"
10
+ clipRule="evenodd"
11
11
  d="M174.388 4.798a2.148 2.148 0 0 0-2.136 2.157c0 1.191.957 2.158 2.136 2.158a2.149 2.149 0 0 0 2.137-2.158c0-1.19-.958-2.157-2.137-2.157Zm-2.611 2.157c0-1.456 1.169-2.637 2.611-2.637 1.443 0 2.612 1.181 2.612 2.637 0 1.457-1.169 2.638-2.612 2.638-1.442 0-2.611-1.181-2.611-2.638Z"
12
12
  fill="currentColor"
13
13
  />
@@ -1,13 +1,7 @@
1
- import { Controller } from 'react-hook-form';
2
- import { FieldPath } from 'react-hook-form';
3
1
  import { useUserSettings } from '@/hooks/use-user-settings.js';
4
- import { ControllerProps } from 'react-hook-form';
5
- import { FieldValues } from 'react-hook-form';
2
+ import { Controller, ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
3
+ import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '../ui/form.js';
6
4
  import { FormFieldWrapper } from './form-field-wrapper.js';
7
- import { FormMessage } from '../ui/form.js';
8
- import { FormControl } from '../ui/form.js';
9
- import { FormItem } from '../ui/form.js';
10
- import { FormDescription, FormField, FormLabel } from '../ui/form.js';
11
5
 
12
6
  export type TranslatableEntity = FieldValues & {
13
7
  translations?: Array<{ languageCode: string }> | null;
@@ -199,6 +199,23 @@ function unwrapVariableDefinitionType(type: TypeNode): NamedTypeNode {
199
199
  return type;
200
200
  }
201
201
 
202
+ /**
203
+ * @description
204
+ * Helper function to get the first field selection from a query operation definition.
205
+ */
206
+ function getFirstQueryField(documentNode: DocumentNode): FieldNode {
207
+ const operationDefinition = documentNode.definitions.find(
208
+ (def): def is OperationDefinitionNode =>
209
+ def.kind === 'OperationDefinition' && def.operation === 'query',
210
+ );
211
+ const firstSelection = operationDefinition?.selectionSet.selections[0];
212
+ if (firstSelection?.kind === 'Field') {
213
+ return firstSelection;
214
+ } else {
215
+ throw new Error('Could not determine query field');
216
+ }
217
+ }
218
+
202
219
  /**
203
220
  * @description
204
221
  * This function is used to get the name of the query from a DocumentNode.
@@ -216,16 +233,32 @@ function unwrapVariableDefinitionType(type: TypeNode): NamedTypeNode {
216
233
  * The query name is `product`.
217
234
  */
218
235
  export function getQueryName(documentNode: DocumentNode): string {
219
- const operationDefinition = documentNode.definitions.find(
220
- (def): def is OperationDefinitionNode =>
221
- def.kind === 'OperationDefinition' && def.operation === 'query',
222
- );
223
- const firstSelection = operationDefinition?.selectionSet.selections[0];
224
- if (firstSelection?.kind === 'Field') {
225
- return firstSelection.name.value;
226
- } else {
227
- throw new Error('Could not determine query name');
228
- }
236
+ const firstField = getFirstQueryField(documentNode);
237
+ return firstField.name.value;
238
+ }
239
+
240
+ /**
241
+ * @description
242
+ * This function is used to get the entity name from a DocumentNode.
243
+ *
244
+ * For example, in the following query:
245
+ *
246
+ * ```graphql
247
+ * query ProductDetail($id: ID!) {
248
+ * product(id: $id) {
249
+ * ...ProductDetail
250
+ * }
251
+ * }
252
+ * ```
253
+ *
254
+ * The entity name is `Product`.
255
+ */
256
+ export function getEntityName(documentNode: DocumentNode): string {
257
+ const firstField = getFirstQueryField(documentNode);
258
+ // Get the return type from the field definition
259
+ const fieldName = firstField.name.value;
260
+ const queryInfo = getQueryInfo(fieldName);
261
+ return queryInfo.type;
229
262
  }
230
263
 
231
264
  /**
@@ -1,4 +1,5 @@
1
1
  import { registerDashboardWidget } from '../dashboard-widget/widget-extensions.js';
2
+ import { addCustomFormComponent } from '../form-engine/custom-form-component-extensions.js';
2
3
  import {
3
4
  registerDashboardActionBarItem,
4
5
  registerDashboardPageBlock,
@@ -76,6 +77,11 @@ export function defineDashboardExtension(extension: DashboardExtension) {
76
77
  registerDashboardWidget(widget);
77
78
  }
78
79
  }
80
+ if (extension.customFormComponents) {
81
+ for (const component of extension.customFormComponents) {
82
+ addCustomFormComponent(component);
83
+ }
84
+ }
79
85
  const callbacks = globalRegistry.get('extensionSourceChangeCallbacks');
80
86
  if (callbacks.size) {
81
87
  for (const callback of callbacks) {
@@ -5,8 +5,21 @@ import type React from 'react';
5
5
 
6
6
  import { DashboardAlertDefinition } from '../alert/types.js';
7
7
  import { DashboardWidgetDefinition } from '../dashboard-widget/types.js';
8
+ import { CustomFormComponentInputProps } from '../form-engine/custom-form-component.js';
8
9
  import { NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
9
10
 
11
+ /**
12
+ * @description
13
+ * Allows you to define custom form components for custom fields in the dashboard.
14
+ *
15
+ * @docsCategory extensions
16
+ * @since 3.4.0
17
+ */
18
+ export interface DashboardCustomFormComponent {
19
+ id: string;
20
+ component: React.FunctionComponent<CustomFormComponentInputProps>;
21
+ }
22
+
10
23
  export interface DashboardRouteDefinition {
11
24
  component: (route: AnyRoute) => React.ReactNode;
12
25
  path: string;
@@ -137,4 +150,10 @@ export interface DashboardExtension {
137
150
  * given components and optionally also add a nav menu item.
138
151
  */
139
152
  widgets?: DashboardWidgetDefinition[];
153
+
154
+ /**
155
+ * @description
156
+ * Allows you to define custom form components for custom fields in the dashboard.
157
+ */
158
+ customFormComponents?: DashboardCustomFormComponent[];
140
159
  }
@@ -0,0 +1,28 @@
1
+ import { DashboardCustomFormComponent } from '../extension-api/extension-api-types.js';
2
+ import { globalRegistry } from '../registry/global-registry.js';
3
+
4
+ import { CustomFormComponentInputProps } from './custom-form-component.js';
5
+
6
+ globalRegistry.register(
7
+ 'customFormComponents',
8
+ new Map<string, React.FunctionComponent<CustomFormComponentInputProps>>(),
9
+ );
10
+
11
+ export function getCustomFormComponents() {
12
+ return globalRegistry.get('customFormComponents');
13
+ }
14
+
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
+ }
@@ -0,0 +1,33 @@
1
+ import { CustomFieldConfig } from '@vendure/common/lib/generated-types';
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';
10
+
11
+ export interface CustomFormComponentProps {
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
+ }
24
+
25
+ export function CustomFormComponent({ fieldDef, fieldProps }: CustomFormComponentProps) {
26
+ const Component = getCustomFormComponent(fieldDef.ui?.component);
27
+
28
+ if (!Component) {
29
+ return null;
30
+ }
31
+
32
+ return <Component {...fieldProps} />;
33
+ }
@@ -13,14 +13,18 @@ import { useForm } from 'react-hook-form';
13
13
 
14
14
  export interface GeneratedFormOptions<
15
15
  T extends TypedDocumentNode<any, any>,
16
- VarName extends (keyof VariablesOf<T>) | undefined = 'input',
16
+ VarName extends keyof VariablesOf<T> | undefined = 'input',
17
17
  E extends Record<string, any> = Record<string, any>,
18
18
  > {
19
19
  document?: T;
20
20
  varName?: VarName;
21
21
  entity: E | null | undefined;
22
- setValues: (entity: NonNullable<E>) => VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>;
23
- onSubmit?: (values: VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>) => void;
22
+ setValues: (
23
+ entity: NonNullable<E>,
24
+ ) => VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>;
25
+ onSubmit?: (
26
+ values: VarName extends keyof VariablesOf<T> ? VariablesOf<T>[VarName] : VariablesOf<T>,
27
+ ) => void;
24
28
  }
25
29
 
26
30
  /**
@@ -41,7 +45,7 @@ export function useGeneratedForm<
41
45
  const updateFields = document ? getOperationVariablesFields(document, varName) : [];
42
46
  const schema = createFormSchemaFromFields(updateFields);
43
47
  const defaultValues = getDefaultValuesFromFields(updateFields, activeChannel?.defaultLanguageCode);
44
- const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages);
48
+ const processedEntity = ensureTranslationsForAllLanguages(entity, availableLanguages, defaultValues);
45
49
 
46
50
  const form = useForm({
47
51
  resolver: async (values, context, options) => {
@@ -53,7 +57,7 @@ export function useGeneratedForm<
53
57
  },
54
58
  mode: 'onChange',
55
59
  defaultValues,
56
- values: processedEntity ? setValues(processedEntity) : defaultValues,
60
+ values: processedEntity ? processedEntity : defaultValues,
57
61
  });
58
62
  let submitHandler = (event: FormEvent) => {
59
63
  event.preventDefault();
@@ -69,11 +73,13 @@ export function useGeneratedForm<
69
73
 
70
74
  /**
71
75
  * Ensures that an entity with translations has entries for all available languages.
72
- * If a language is missing, it creates an empty translation based on the structure of existing translations.
76
+ * If a language is missing, it creates an empty translation based on the structure of existing translations
77
+ * and the expected form structure from defaultValues.
73
78
  */
74
79
  function ensureTranslationsForAllLanguages<E extends Record<string, any>>(
75
80
  entity: E | null | undefined,
76
81
  availableLanguages: string[] = [],
82
+ expectedStructure?: Record<string, any>,
77
83
  ): E | null | undefined {
78
84
  if (
79
85
  !entity ||
@@ -91,23 +97,56 @@ function ensureTranslationsForAllLanguages<E extends Record<string, any>>(
91
97
  // Get existing language codes
92
98
  const existingLanguageCodes = new Set(translations.map((t: any) => t.languageCode));
93
99
 
100
+ // Get the expected translation structure from defaultValues or existing translations
101
+ const existingTemplate = translations[0] || {};
102
+ const expectedTranslationStructure = expectedStructure?.translations?.[0] || {};
103
+
104
+ // Merge the structures to ensure we have all expected fields
105
+ const templateStructure = {
106
+ ...expectedTranslationStructure,
107
+ ...existingTemplate,
108
+ };
109
+
94
110
  // Add missing language translations
95
111
  for (const langCode of availableLanguages) {
96
112
  if (!existingLanguageCodes.has(langCode)) {
97
- // Find a translation to use as template for field structure
98
- const template = translations[0] || {};
99
113
  const emptyTranslation: Record<string, any> = {
100
114
  languageCode: langCode,
101
115
  };
102
116
 
103
- // Add empty fields based on template (excluding languageCode)
104
- Object.keys(template).forEach(key => {
117
+ // Add empty fields based on merged template structure (excluding languageCode)
118
+ Object.keys(templateStructure).forEach(key => {
105
119
  if (key !== 'languageCode') {
106
- emptyTranslation[key] = '';
120
+ if (typeof templateStructure[key] === 'object' && templateStructure[key] !== null) {
121
+ // For nested objects like customFields, create an empty object
122
+ emptyTranslation[key] = Array.isArray(templateStructure[key]) ? [] : {};
123
+ } else {
124
+ // For primitive values, use empty string as default
125
+ emptyTranslation[key] = '';
126
+ }
107
127
  }
108
128
  });
109
129
 
110
130
  translations.push(emptyTranslation);
131
+ } else {
132
+ // For existing translations, ensure they have all expected fields
133
+ const existingTranslation = translations.find((t: any) => t.languageCode === langCode);
134
+ if (existingTranslation) {
135
+ Object.keys(expectedTranslationStructure).forEach(key => {
136
+ if (key !== 'languageCode' && !(key in existingTranslation)) {
137
+ if (
138
+ typeof expectedTranslationStructure[key] === 'object' &&
139
+ expectedTranslationStructure[key] !== null
140
+ ) {
141
+ existingTranslation[key] = Array.isArray(expectedTranslationStructure[key])
142
+ ? []
143
+ : {};
144
+ } else {
145
+ existingTranslation[key] = '';
146
+ }
147
+ }
148
+ });
149
+ }
111
150
  }
112
151
  }
113
152
 
@@ -10,8 +10,12 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
10
10
  import { AnyRoute, useNavigate } from '@tanstack/react-router';
11
11
  import { ResultOf, VariablesOf } from 'gql.tada';
12
12
  import { toast } from 'sonner';
13
- import { getOperationVariablesFields } from '../document-introspection/get-document-structure.js';
13
+ import {
14
+ getEntityName,
15
+ getOperationVariablesFields,
16
+ } from '../document-introspection/get-document-structure.js';
14
17
 
18
+ import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
15
19
  import {
16
20
  CustomFieldsPageBlock,
17
21
  DetailFormGrid,
@@ -41,6 +45,7 @@ export interface DetailPageProps<
41
45
  /**
42
46
  * @description
43
47
  * The name of the entity.
48
+ * If not provided, it will be inferred from the query document.
44
49
  */
45
50
  entityName?: string;
46
51
  /**
@@ -80,6 +85,29 @@ export interface DetailPageProps<
80
85
  setValuesForUpdate: (entity: ResultOf<T>[EntityField]) => VariablesOf<U>['input'];
81
86
  }
82
87
 
88
+ /**
89
+ * Renders form input components based on field type
90
+ */
91
+ function renderFieldInput(fieldInfo: { type: string }, field: any) {
92
+ switch (fieldInfo.type) {
93
+ case 'Int':
94
+ case 'Float':
95
+ return (
96
+ <Input
97
+ type="number"
98
+ value={field.value}
99
+ onChange={e => field.onChange(e.target.valueAsNumber)}
100
+ />
101
+ );
102
+ case 'DateTime':
103
+ return <DateTimeInput {...field} />;
104
+ case 'Boolean':
105
+ return <Checkbox value={field.value} onCheckedChange={field.onChange} />;
106
+ default:
107
+ return <Input {...field} />;
108
+ }
109
+ }
110
+
83
111
  /**
84
112
  * @description
85
113
  * **Status: Developer Preview**
@@ -100,7 +128,7 @@ export function DetailPage<
100
128
  >({
101
129
  pageId,
102
130
  route,
103
- entityName,
131
+ entityName: passedEntityName,
104
132
  queryDocument,
105
133
  createDocument,
106
134
  updateDocument,
@@ -110,11 +138,15 @@ export function DetailPage<
110
138
  const params = route.useParams();
111
139
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
112
140
  const navigate = useNavigate();
141
+ const inferredEntityName = getEntityName(queryDocument);
142
+
143
+ const entityName = passedEntityName ?? inferredEntityName;
113
144
 
114
145
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage<any, any, any>({
115
146
  queryDocument,
116
147
  updateDocument,
117
148
  createDocument,
149
+ entityName,
118
150
  params: { id: params.id },
119
151
  setValuesForUpdate,
120
152
  onSuccess: async data => {
@@ -133,6 +165,7 @@ export function DetailPage<
133
165
  });
134
166
 
135
167
  const updateFields = getOperationVariablesFields(updateDocument, 'input');
168
+ const translations = updateFields.find(fieldInfo => fieldInfo.name === 'translations');
136
169
 
137
170
  return (
138
171
  <Page pageId={pageId} form={form} submitHandler={submitHandler}>
@@ -152,6 +185,7 @@ export function DetailPage<
152
185
  <DetailFormGrid>
153
186
  {updateFields
154
187
  .filter(fieldInfo => fieldInfo.name !== 'customFields')
188
+ .filter(fieldInfo => fieldInfo.name !== 'translations')
155
189
  .map(fieldInfo => {
156
190
  if (fieldInfo.name === 'id' && fieldInfo.type === 'ID') {
157
191
  return null;
@@ -162,33 +196,22 @@ export function DetailPage<
162
196
  control={form.control}
163
197
  name={fieldInfo.name as never}
164
198
  label={fieldInfo.name}
165
- render={({ field }) => {
166
- switch (fieldInfo.type) {
167
- case 'Int':
168
- case 'Float':
169
- return (
170
- <Input
171
- type="number"
172
- value={field.value}
173
- onChange={e =>
174
- field.onChange(e.target.valueAsNumber)
175
- }
176
- />
177
- );
178
- case 'DateTime':
179
- return <DateTimeInput {...field} />;
180
- case 'Boolean':
181
- return (
182
- <Checkbox
183
- value={field.value}
184
- onCheckedChange={field.onChange}
185
- />
186
- );
187
- case 'String':
188
- default:
189
- return <Input {...field} />;
190
- }
191
- }}
199
+ render={({ field }) => renderFieldInput(fieldInfo, field)}
200
+ />
201
+ );
202
+ })}
203
+ {translations?.typeInfo
204
+ ?.filter(
205
+ fieldInfo => !['customFields', 'id', 'languageCode'].includes(fieldInfo.name),
206
+ )
207
+ .map(fieldInfo => {
208
+ return (
209
+ <TranslatableFormFieldWrapper
210
+ key={fieldInfo.name}
211
+ control={form.control}
212
+ name={fieldInfo.name as never}
213
+ label={fieldInfo.name}
214
+ render={({ field }) => renderFieldInput(fieldInfo, field)}
192
215
  />
193
216
  );
194
217
  })}
@@ -1,5 +1,7 @@
1
1
  import { NEW_ENTITY_PATH } from '@/constants.js';
2
2
  import { api, Variables } from '@/graphql/api.js';
3
+ import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
4
+ import { removeReadonlyCustomFields } from '@/lib/utils.js';
3
5
  import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
4
6
  import {
5
7
  DefinedInitialDataOptions,
@@ -59,6 +61,12 @@ export interface DetailPageOptions<
59
61
  params: {
60
62
  id: string;
61
63
  };
64
+ /**
65
+ * @description
66
+ * The entity type name for custom field configuration lookup.
67
+ * Required to filter out readonly custom fields before mutations.
68
+ */
69
+ entityName: string;
62
70
  /**
63
71
  * @description
64
72
  * The document to create the entity.
@@ -232,16 +240,19 @@ export function useDetailPage<
232
240
  transformUpdateInput,
233
241
  params,
234
242
  entityField,
243
+ entityName,
235
244
  onSuccess,
236
245
  onError,
237
246
  } = options;
238
247
  const isNew = params.id === NEW_ENTITY_PATH;
239
248
  const queryClient = useQueryClient();
249
+ const customFieldConfig = useCustomFieldConfig(entityName);
240
250
  const detailQueryOptions = getDetailQueryOptions(addCustomFields(queryDocument), {
241
251
  id: isNew ? '__NEW__' : params.id,
242
252
  });
243
253
  const detailQuery = useSuspenseQuery(detailQueryOptions);
244
254
  const entityQueryField = entityField ?? getQueryName(queryDocument);
255
+
245
256
  const entity = (detailQuery?.data as any)[entityQueryField] as
246
257
  | DetailPageEntity<T, EntityField>
247
258
  | undefined;
@@ -280,10 +291,15 @@ export function useDetailPage<
280
291
  entity,
281
292
  setValues: setValuesForUpdate,
282
293
  onSubmit(values: any) {
294
+ // Filter out readonly custom fields before submitting
295
+ const filteredValues = removeReadonlyCustomFields(values, customFieldConfig || []);
296
+
283
297
  if (isNew) {
284
- createMutation.mutate({ input: transformCreateInput?.(values) ?? values });
298
+ const finalInput = transformCreateInput?.(filteredValues) ?? filteredValues;
299
+ createMutation.mutate({ input: finalInput });
285
300
  } else {
286
- updateMutation.mutate({ input: transformUpdateInput?.(values) ?? values });
301
+ const finalInput = transformUpdateInput?.(filteredValues) ?? filteredValues;
302
+ updateMutation.mutate({ input: finalInput });
287
303
  }
288
304
  },
289
305
  });
@@ -4,6 +4,7 @@ import {
4
4
  DashboardActionBarItem,
5
5
  DashboardPageBlockDefinition,
6
6
  } from '../extension-api/extension-api-types.js';
7
+ import { CustomFormComponentInputProps } from '../form-engine/custom-form-component.js';
7
8
  import { NavMenuConfig } from '../nav-menu/nav-menu-extensions.js';
8
9
 
9
10
  export interface GlobalRegistryContents {
@@ -14,6 +15,7 @@ export interface GlobalRegistryContents {
14
15
  dashboardPageBlockRegistry: Map<string, DashboardPageBlockDefinition[]>;
15
16
  dashboardWidgetRegistry: Map<string, DashboardWidgetDefinition>;
16
17
  dashboardAlertRegistry: Map<string, DashboardAlertDefinition>;
18
+ customFormComponents: Map<string, React.FunctionComponent<CustomFormComponentInputProps>>;
17
19
  }
18
20
 
19
21
  export type GlobalRegistryKey = keyof GlobalRegistryContents;
@@ -291,13 +291,15 @@ export type introspection_types = {
291
291
  'ProductOptionInUseError': { kind: 'OBJECT'; name: 'ProductOptionInUseError'; fields: { 'errorCode': { name: 'errorCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ErrorCode'; ofType: null; }; } }; 'message': { name: 'message'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'optionGroupCode': { name: 'optionGroupCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'productVariantCount': { name: 'productVariantCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
292
292
  'ProductOptionTranslation': { kind: 'OBJECT'; name: 'ProductOptionTranslation'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; }; };
293
293
  'ProductOptionTranslationInput': { kind: 'INPUT_OBJECT'; name: 'ProductOptionTranslationInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
294
- 'ProductReview': { kind: 'OBJECT'; name: 'ProductReview'; fields: { 'author': { name: 'author'; type: { kind: 'OBJECT'; name: 'Customer'; ofType: null; } }; 'authorLocation': { name: 'authorLocation'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'authorName': { name: 'authorName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'body': { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; ofType: null; } }; 'downvotes': { name: 'downvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'product': { name: 'product'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Product'; ofType: null; }; } }; 'productVariant': { name: 'productVariant'; type: { kind: 'OBJECT'; name: 'ProductVariant'; ofType: null; } }; 'rating': { name: 'rating'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'response': { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'responseCreatedAt': { name: 'responseCreatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'state': { name: 'state'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'summary': { name: 'summary'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'upvotes': { name: 'upvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
295
- 'ProductReviewCustomFields': { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
296
- 'ProductReviewFilterParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }]; };
294
+ 'ProductReview': { kind: 'OBJECT'; name: 'ProductReview'; fields: { 'author': { name: 'author'; type: { kind: 'OBJECT'; name: 'Customer'; ofType: null; } }; 'authorLocation': { name: 'authorLocation'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'authorName': { name: 'authorName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'body': { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; ofType: null; } }; 'downvotes': { name: 'downvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'product': { name: 'product'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Product'; ofType: null; }; } }; 'productVariant': { name: 'productVariant'; type: { kind: 'OBJECT'; name: 'ProductVariant'; ofType: null; } }; 'rating': { name: 'rating'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'response': { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'responseCreatedAt': { name: 'responseCreatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'state': { name: 'state'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'summary': { name: 'summary'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'translations': { name: 'translations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProductReviewTranslation'; ofType: null; }; }; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'upvotes': { name: 'upvotes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
295
+ 'ProductReviewCustomFields': { kind: 'OBJECT'; name: 'ProductReviewCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'verifiedReviewerName': { name: 'verifiedReviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
296
+ 'ProductReviewFilterParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'verifiedReviewerName'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }]; };
297
297
  'ProductReviewHistogramItem': { kind: 'OBJECT'; name: 'ProductReviewHistogramItem'; fields: { 'bin': { name: 'bin'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'frequency': { name: 'frequency'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
298
298
  'ProductReviewList': { kind: 'OBJECT'; name: 'ProductReviewList'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProductReview'; ofType: null; }; }; }; } }; 'totalItems': { name: 'totalItems'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
299
299
  'ProductReviewListOptions': { kind: 'INPUT_OBJECT'; name: 'ProductReviewListOptions'; isOneOf: false; inputFields: [{ name: 'skip'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'take'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'sort'; type: { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; ofType: null; }; defaultValue: null }, { name: 'filter'; type: { kind: 'INPUT_OBJECT'; name: 'ProductReviewFilterParameter'; ofType: null; }; defaultValue: null }, { name: 'filterOperator'; type: { kind: 'ENUM'; name: 'LogicalOperator'; ofType: null; }; defaultValue: null }]; };
300
- 'ProductReviewSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
300
+ 'ProductReviewSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductReviewSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'summary'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'rating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'authorLocation'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'upvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downvotes'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'state'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'responseCreatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'verifiedReviewerName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
301
+ 'ProductReviewTranslation': { kind: 'OBJECT'; name: 'ProductReviewTranslation'; fields: { 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductReviewTranslationCustomFields'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'text': { name: 'text'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; };
302
+ 'ProductReviewTranslationCustomFields': { kind: 'OBJECT'; name: 'ProductReviewTranslationCustomFields'; fields: { 'reviewerName': { name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
301
303
  'ProductSortParameter': { kind: 'INPUT_OBJECT'; name: 'ProductSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'slug'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'description'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'infoUrl'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'downloadable'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'shortName'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'lastUpdated'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewRating'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'reviewCount'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'featuredReview'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
302
304
  'ProductTranslation': { kind: 'OBJECT'; name: 'ProductTranslation'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'OBJECT'; name: 'ProductTranslationCustomFields'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'languageCode': { name: 'languageCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageCode'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'slug': { name: 'slug'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; }; };
303
305
  'ProductTranslationCustomFields': { kind: 'OBJECT'; name: 'ProductTranslationCustomFields'; fields: { 'shortName': { name: 'shortName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
@@ -465,8 +467,7 @@ export type introspection_types = {
465
467
  'UpdateProductInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'featuredAssetId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'assetIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'facetValueIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'INPUT_OBJECT'; name: 'UpdateProductCustomFieldsInput'; ofType: null; }; defaultValue: null }]; };
466
468
  'UpdateProductOptionGroupInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductOptionGroupInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'code'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductOptionGroupTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
467
469
  'UpdateProductOptionInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductOptionInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'code'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductOptionGroupTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
468
- 'UpdateProductReviewCustomFieldsInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewCustomFieldsInput'; isOneOf: false; inputFields: [{ name: 'reviewerName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; };
469
- 'UpdateProductReviewInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'summary'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewCustomFieldsInput'; ofType: null; }; defaultValue: null }]; };
470
+ 'UpdateProductReviewInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductReviewInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'summary'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'body'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'response'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
470
471
  'UpdateProductVariantInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ProductVariantTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'facetValueIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'optionIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'sku'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'taxCategoryId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'price'; type: { kind: 'SCALAR'; name: 'Money'; ofType: null; }; defaultValue: null }, { name: 'prices'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantPriceInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'featuredAssetId'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'assetIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; }; defaultValue: null }, { name: 'stockOnHand'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'stockLevels'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'StockLevelInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'outOfStockThreshold'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'useGlobalOutOfStockThreshold'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'trackInventory'; type: { kind: 'ENUM'; name: 'GlobalFlag'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
471
472
  'UpdateProductVariantPriceInput': { kind: 'INPUT_OBJECT'; name: 'UpdateProductVariantPriceInput'; isOneOf: false; inputFields: [{ name: 'currencyCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'CurrencyCode'; ofType: null; }; }; defaultValue: null }, { name: 'price'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Money'; ofType: null; }; }; defaultValue: null }, { name: 'delete'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
472
473
  'UpdatePromotionInput': { kind: 'INPUT_OBJECT'; name: 'UpdatePromotionInput'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'startsAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'endsAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; defaultValue: null }, { name: 'couponCode'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'perCustomerUsageLimit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'usageLimit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'conditions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConfigurableOperationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'actions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ConfigurableOperationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'translations'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'PromotionTranslationInput'; ofType: null; }; }; }; defaultValue: null }, { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; defaultValue: null }]; };
package/src/lib/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- // Generated on: 2025-03-28T08:23:55.325Z
3
2
 
4
3
  export * from './components/data-display/boolean.js';
5
4
  export * from './components/data-display/date-time.js';
5
+ export * from './components/data-display/json.js';
6
6
  export * from './components/data-display/money.js';
7
7
  export * from './components/data-input/affixed-input.js';
8
8
  export * from './components/data-input/customer-group-input.js';
@@ -10,12 +10,22 @@ export * from './components/data-input/datetime-input.js';
10
10
  export * from './components/data-input/facet-value-input.js';
11
11
  export * from './components/data-input/money-input.js';
12
12
  export * from './components/data-input/richt-text-input.js';
13
+ export * from './components/data-table/add-filter-menu.js';
13
14
  export * from './components/data-table/data-table-column-header.js';
14
15
  export * from './components/data-table/data-table-faceted-filter.js';
16
+ export * from './components/data-table/data-table-filter-badge.js';
15
17
  export * from './components/data-table/data-table-filter-dialog.js';
16
18
  export * from './components/data-table/data-table-pagination.js';
19
+ export * from './components/data-table/data-table-types.js';
17
20
  export * from './components/data-table/data-table-view-options.js';
18
21
  export * from './components/data-table/data-table.js';
22
+ export * from './components/data-table/filters/data-table-boolean-filter.js';
23
+ export * from './components/data-table/filters/data-table-datetime-filter.js';
24
+ export * from './components/data-table/filters/data-table-id-filter.js';
25
+ export * from './components/data-table/filters/data-table-number-filter.js';
26
+ export * from './components/data-table/filters/data-table-string-filter.js';
27
+ export * from './components/data-table/human-readable-operator.js';
28
+ export * from './components/data-table/refresh-button.js';
19
29
  export * from './components/layout/app-layout.js';
20
30
  export * from './components/layout/app-sidebar.js';
21
31
  export * from './components/layout/channel-switcher.js';
@@ -25,13 +35,17 @@ export * from './components/layout/language-dialog.js';
25
35
  export * from './components/layout/nav-main.js';
26
36
  export * from './components/layout/nav-projects.js';
27
37
  export * from './components/layout/nav-user.js';
38
+ export * from './components/layout/prerelease-popup.js';
28
39
  export * from './components/login/login-form.js';
29
40
  export * from './components/shared/alerts.js';
30
41
  export * from './components/shared/animated-number.js';
42
+ export * from './components/shared/asset/asset-focal-point-editor.js';
31
43
  export * from './components/shared/asset/asset-gallery.js';
32
44
  export * from './components/shared/asset/asset-picker-dialog.js';
33
45
  export * from './components/shared/asset/asset-preview-dialog.js';
46
+ export * from './components/shared/asset/asset-preview-selector.js';
34
47
  export * from './components/shared/asset/asset-preview.js';
48
+ export * from './components/shared/asset/asset-properties.js';
35
49
  export * from './components/shared/asset/focal-point-control.js';
36
50
  export * from './components/shared/assigned-facet-values.js';
37
51
  export * from './components/shared/channel-code-label.js';
@@ -43,6 +57,7 @@ export * from './components/shared/copyable-text.js';
43
57
  export * from './components/shared/country-selector.js';
44
58
  export * from './components/shared/currency-selector.js';
45
59
  export * from './components/shared/custom-fields-form.js';
60
+ export * from './components/shared/customer-address-form.js';
46
61
  export * from './components/shared/customer-group-chip.js';
47
62
  export * from './components/shared/customer-group-selector.js';
48
63
  export * from './components/shared/customer-selector.js';
@@ -61,8 +76,11 @@ export * from './components/shared/icon-mark.js';
61
76
  export * from './components/shared/language-selector.js';
62
77
  export * from './components/shared/logo-mark.js';
63
78
  export * from './components/shared/multi-select.js';
79
+ export * from './components/shared/navigation-confirmation.js';
80
+ export * from './components/shared/option-value-input.js';
64
81
  export * from './components/shared/paginated-list-data-table.js';
65
82
  export * from './components/shared/permission-guard.js';
83
+ export * from './components/shared/product-variant-selector.js';
66
84
  export * from './components/shared/rich-text-editor.js';
67
85
  export * from './components/shared/role-code-label.js';
68
86
  export * from './components/shared/role-selector.js';
@@ -103,6 +121,10 @@ export * from './components/ui/table.js';
103
121
  export * from './components/ui/tabs.js';
104
122
  export * from './components/ui/textarea.js';
105
123
  export * from './components/ui/tooltip.js';
124
+ export * from './framework/alert/alert-extensions.js';
125
+ export * from './framework/alert/alert-item.js';
126
+ export * from './framework/alert/alerts-indicator.js';
127
+ export * from './framework/alert/types.js';
106
128
  export * from './framework/component-registry/component-registry.js';
107
129
  export * from './framework/component-registry/dynamic-component.js';
108
130
  export * from './framework/dashboard-widget/base-widget.js';
@@ -122,11 +144,14 @@ export * from './framework/document-introspection/hooks.js';
122
144
  export * from './framework/extension-api/define-dashboard-extension.js';
123
145
  export * from './framework/extension-api/extension-api-types.js';
124
146
  export * from './framework/extension-api/use-dashboard-extensions.js';
147
+ export * from './framework/form-engine/custom-form-component-extensions.js';
148
+ export * from './framework/form-engine/custom-form-component.js';
125
149
  export * from './framework/form-engine/form-schema-tools.js';
126
150
  export * from './framework/form-engine/use-generated-form.js';
127
151
  export * from './framework/layout-engine/layout-extensions.js';
128
152
  export * from './framework/layout-engine/location-wrapper.js';
129
153
  export * from './framework/layout-engine/page-layout.js';
154
+ export * from './framework/layout-engine/page-provider.js';
130
155
  export * from './framework/nav-menu/nav-menu-extensions.js';
131
156
  export * from './framework/page/detail-page-route-loader.js';
132
157
  export * from './framework/page/detail-page.js';
@@ -135,6 +160,8 @@ export * from './framework/page/page-api.js';
135
160
  export * from './framework/page/page-types.js';
136
161
  export * from './framework/page/use-detail-page.js';
137
162
  export * from './framework/page/use-extended-router.js';
163
+ export * from './framework/registry/global-registry.js';
164
+ export * from './framework/registry/registry-types.js';
138
165
  export * from './hooks/use-auth.js';
139
166
  export * from './hooks/use-channel.js';
140
167
  export * from './hooks/use-custom-field-config.js';
@@ -146,4 +173,5 @@ export * from './hooks/use-permissions.js';
146
173
  export * from './hooks/use-server-config.js';
147
174
  export * from './hooks/use-theme.js';
148
175
  export * from './hooks/use-user-settings.js';
176
+ export * from './lib/trans.js';
149
177
  export * from './lib/utils.js';
@@ -58,3 +58,52 @@ export function normalizeString(input: string, spaceReplacer = ' '): string {
58
58
  .replace(/\s+/g, spaceReplacer)
59
59
  .replace(multipleSequentialReplacerRegex, spaceReplacer);
60
60
  }
61
+
62
+ /**
63
+ * Removes any readonly custom fields from form values before submission.
64
+ * This prevents errors when submitting readonly custom field values to mutations.
65
+ *
66
+ * @param values - The form values that may contain custom fields
67
+ * @param customFieldConfigs - Array of custom field configurations for the entity
68
+ * @returns The values with readonly custom fields removed
69
+ */
70
+ export function removeReadonlyCustomFields<T extends Record<string, any>>(
71
+ values: T,
72
+ customFieldConfigs: Array<{ name: string; readonly?: boolean | null }> = [],
73
+ ): T {
74
+ if (!values || !customFieldConfigs?.length) {
75
+ return values;
76
+ }
77
+
78
+ // Create a deep copy to avoid mutating the original values
79
+ const result = structuredClone(values);
80
+
81
+ // Get readonly field names
82
+ const readonlyFieldNames = customFieldConfigs
83
+ .filter(config => config.readonly === true)
84
+ .map(config => config.name);
85
+
86
+ if (readonlyFieldNames.length === 0) {
87
+ return result;
88
+ }
89
+
90
+ // Remove readonly fields from main customFields
91
+ if (result.customFields && typeof result.customFields === 'object') {
92
+ for (const fieldName of readonlyFieldNames) {
93
+ delete result.customFields[fieldName];
94
+ }
95
+ }
96
+
97
+ // Remove readonly fields from translations customFields
98
+ if (Array.isArray(result.translations)) {
99
+ for (const translation of result.translations) {
100
+ if (translation?.customFields && typeof translation.customFields === 'object') {
101
+ for (const fieldName of readonlyFieldNames) {
102
+ delete translation.customFields[fieldName];
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ return result;
109
+ }