@elementor/editor-variables 3.33.0-99 → 3.35.0-325

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 (60) hide show
  1. package/dist/index.d.mts +17 -4
  2. package/dist/index.d.ts +17 -4
  3. package/dist/index.js +1907 -810
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1854 -748
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +16 -14
  8. package/src/api.ts +24 -0
  9. package/src/batch-operations.ts +86 -0
  10. package/src/components/fields/color-field.tsx +1 -0
  11. package/src/components/fields/font-field.tsx +2 -1
  12. package/src/components/fields/label-field.tsx +42 -6
  13. package/src/components/ui/deleted-variable-alert.tsx +14 -10
  14. package/src/components/ui/{no-variables.tsx → empty-state.tsx} +8 -13
  15. package/src/components/ui/menu-item-content.tsx +14 -11
  16. package/src/components/ui/mismatch-variable-alert.tsx +5 -9
  17. package/src/components/ui/missing-variable-alert.tsx +8 -9
  18. package/src/components/ui/no-search-results.tsx +1 -2
  19. package/src/components/ui/tags/assigned-tag.tsx +6 -3
  20. package/src/components/ui/tags/warning-variable-tag.tsx +44 -0
  21. package/src/components/ui/variable/deleted-variable.tsx +13 -6
  22. package/src/components/ui/variable/mismatch-variable.tsx +11 -4
  23. package/src/components/ui/variable/missing-variable.tsx +2 -2
  24. package/src/components/variable-creation.tsx +13 -4
  25. package/src/components/variable-edit.tsx +12 -12
  26. package/src/components/variable-restore.tsx +3 -2
  27. package/src/components/variables-manager/hooks/use-auto-edit.ts +21 -0
  28. package/src/components/variables-manager/hooks/use-error-navigation.ts +49 -0
  29. package/src/components/variables-manager/hooks/use-variables-manager-state.ts +89 -0
  30. package/src/components/variables-manager/variable-editable-cell.tsx +131 -67
  31. package/src/components/variables-manager/variables-manager-create-menu.tsx +118 -0
  32. package/src/components/variables-manager/variables-manager-panel.tsx +290 -59
  33. package/src/components/variables-manager/variables-manager-table.tsx +137 -15
  34. package/src/components/variables-selection.tsx +61 -15
  35. package/src/controls/variable-control.tsx +1 -1
  36. package/src/hooks/use-prop-variables.ts +28 -9
  37. package/src/hooks/use-variable-bound-prop.ts +42 -0
  38. package/src/index.ts +1 -0
  39. package/src/init.ts +9 -6
  40. package/src/mcp/create-variable-tool.ts +70 -0
  41. package/src/mcp/delete-variable-tool.ts +50 -0
  42. package/src/mcp/index.ts +17 -0
  43. package/src/mcp/list-variables-tool.ts +58 -0
  44. package/src/mcp/update-variable-tool.ts +81 -0
  45. package/src/mcp/variables-resource.ts +28 -0
  46. package/src/register-variable-types.tsx +4 -0
  47. package/src/service.ts +60 -1
  48. package/src/storage.ts +8 -0
  49. package/src/types.ts +1 -0
  50. package/src/utils/filter-by-search.ts +5 -0
  51. package/src/utils/tracking.ts +37 -22
  52. package/src/utils/unlink-variable.ts +1 -1
  53. package/src/utils/validations.ts +72 -3
  54. package/src/variables-registry/create-variable-type-registry.ts +20 -8
  55. package/src/variables-registry/variable-type-registry.ts +2 -1
  56. package/src/components/ui/tags/deleted-tag.tsx +0 -37
  57. package/src/components/ui/tags/mismatch-tag.tsx +0 -37
  58. package/src/components/ui/tags/missing-tag.tsx +0 -25
  59. /package/src/components/variables-manager/{variable-edit-menu.tsx → ui/variable-edit-menu.tsx} +0 -0
  60. /package/src/components/variables-manager/{variable-table-cell.tsx → ui/variable-table-cell.tsx} +0 -0
@@ -0,0 +1,17 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+
3
+ import { initCreateVariableTool } from './create-variable-tool';
4
+ import { initDeleteVariableTool } from './delete-variable-tool';
5
+ import { initListVariablesTool } from './list-variables-tool';
6
+ import { initUpdateVariableTool } from './update-variable-tool';
7
+ import { initVariablesResource } from './variables-resource';
8
+
9
+ export function initMcp() {
10
+ const { setMCPDescription } = getMCPByDomain( 'variables' );
11
+ setMCPDescription( `Elementor Editor Variables MCP` );
12
+ initListVariablesTool();
13
+ initCreateVariableTool();
14
+ initUpdateVariableTool();
15
+ initDeleteVariableTool();
16
+ initVariablesResource();
17
+ }
@@ -0,0 +1,58 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+ import { z } from '@elementor/schema';
3
+
4
+ import { service } from '../service';
5
+ import { type TVariable } from '../storage';
6
+
7
+ const VariableSchema = {
8
+ type: z.string().describe( 'The type of the variable.' ),
9
+ label: z.string().describe( 'The label of the variable, displayed to the user' ),
10
+ value: z.string().describe( 'The value of the variable.' ),
11
+ id: z
12
+ .string()
13
+ .describe(
14
+ 'The unique identifier of the variable. Used for internal reference, not to be exposed to end users'
15
+ ),
16
+ };
17
+ const VariableListSchema = {
18
+ variables: z.array( z.object( VariableSchema ) ).describe( 'List of variables' ),
19
+ };
20
+
21
+ export const initListVariablesTool = () => {
22
+ getMCPByDomain( 'variables' ).addTool( {
23
+ name: 'list-global-variables',
24
+ description: `List editor global variables
25
+
26
+ ## When to use this tool:
27
+ - When a user requests to see all available global variables in the Elementor editor.
28
+ - When you need to be exact on a variable label, to avoid any mistakes.
29
+ - When you want to see the most up-to-date list of global variables.
30
+ - Before using any other variables related tools that makes changes, such as deletion, creation, or updates. This ensures you have the latest information and there is no naming collision or mismatching.
31
+
32
+ ## Example tool response (JSON format):
33
+ \`\`\`json
34
+ { variables: [
35
+ { type: 'global-color-variable', label: 'Cool', value: 'rgb(1,2,3)', id: 'some-unique-id' },
36
+ { type: 'global-font-variable', label: 'Headline', value: 'serif', id: 'some-other-unique-id' },
37
+ ] }
38
+ \`\`\`
39
+
40
+ Once you get the response, please display the variables in a user-friendly way, unless explicitly requested otherwise.
41
+ Unless explicitly requested otherwise, response in HTML Format, prefer to use tables or unordered lists.
42
+
43
+ Note: **The label is most improtant to be seen as-is without any changes.**
44
+
45
+ <important>
46
+ **Do not omit the label**. This is important for the user to identify the variable.
47
+ **Do not change the label**, it must be displayed exactly as it is, in it's original characters as received from this tool.
48
+ </important>
49
+ `,
50
+ outputSchema: VariableListSchema,
51
+ handler: async () => {
52
+ const variables = service.variables() as Record< string, TVariable >;
53
+ return {
54
+ variables: Object.entries( variables ).map( ( [ id, varData ] ) => ( { id, ...varData } ) ),
55
+ };
56
+ },
57
+ } );
58
+ };
@@ -0,0 +1,81 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+ import { z } from '@elementor/schema';
3
+
4
+ import { service } from '../service';
5
+
6
+ export const initUpdateVariableTool = () => {
7
+ getMCPByDomain( 'variables' ).addTool( {
8
+ schema: {
9
+ id: z.string().describe( 'The unique identifier of the variable to be updated or renamed.' ),
10
+ label: z
11
+ .string()
12
+ .describe(
13
+ 'The label of the variable to be stored after the change. If the user only wishes to update the value, this must be strictly equal to the current label.'
14
+ ),
15
+ value: z
16
+ .string()
17
+ .describe(
18
+ "The new value for the variable. For color variables, this should be a valid CSS color (e.g., 'rgb(255,0,0)', '#ff0000', 'red'). For font variables, this should be a valid font family (e.g., 'Arial', 'serif'). If the user wishes to rename only, make sure you provide the existing value."
19
+ ),
20
+ },
21
+ outputSchema: {
22
+ status: z.enum( [ 'ok', 'error' ] ).describe( 'The status of the operation' ),
23
+ message: z
24
+ .string()
25
+ .optional()
26
+ .describe( 'Optional message providing additional information about the operation' ),
27
+ },
28
+ name: 'update-global-variable',
29
+ description: `Update an existing global variable
30
+
31
+ ## When to use this tool:
32
+ - When a user requests to update an existing global variable in the Elementor editor.
33
+ - When you need to modify the value of an existing variable.
34
+ - When you want to rename an existing variable (change its label).
35
+ - When you want to both rename and modify the value of an existing variable.
36
+
37
+ ## Prerequisites:
38
+ - Ensure you have the most up-to-date list of existing global variables to avoid label duplication. You can use the "list-global-variables" tool to fetch the current variables.
39
+ - Make sure when updating a variable, the new label is unique and not already in use by another variable.
40
+ - Make sure you understand whether you are updating a value, renaming, or both.
41
+ - Reference the variable by the "id" property, given from the "list-global-variables" tool.
42
+ - If the user wishes to rename, make sure you have the existing value.
43
+ - If the user wishes to update the value, make sure you have to **correct label**.
44
+ - You must have the unique identifier, the current label, the current value, and the new value or label or both, before using this tool.
45
+
46
+ ## Required parameters:
47
+ - id: The unique identifier of the variable to be updated or renamed.
48
+ - label: The label of the variable to be stored after the change. If the user only wishes to update the value, this must be strictly equal to the current label.
49
+ - value: The new value for the variable. For color variables, this should be a valid CSS color (e.g., 'rgb(255,0,0)', '#ff0000', 'red'). For font variables, this should be a valid font family (e.g., 'Arial', 'serif'). If the user wishes to rename only, make sure you provide the existing value.
50
+
51
+ ## Example tool call (JSON format):
52
+ \`\`\`json
53
+ { "id": "some-unique-id", "label": "Cool", "value": "rgb(0,140,250)" }
54
+ \`\`\`
55
+
56
+ ## Example responses (JSON format):
57
+ Successful update:
58
+ \`\`\`json
59
+ { "status": "ok" }
60
+ \`\`\`
61
+
62
+ Failed update, which must be displayed to the end user. If the error message is not plain, attempt to find the most useful part of the message and display it.
63
+ \`\`\`json
64
+ { "status": "error", "message": "Label 'Cool' is already in use by another variable." }
65
+ \`\`\`
66
+ `,
67
+ handler: async ( params ) => {
68
+ const { id, label, value } = params;
69
+ try {
70
+ await service.update( id, { label, value } );
71
+ return { status: 'ok' };
72
+ } catch ( error ) {
73
+ const message: string = ( error as Error ).message || 'Unknown server error';
74
+ return {
75
+ status: 'error',
76
+ message: `There was an error creating the variable: ${ message }`,
77
+ };
78
+ }
79
+ },
80
+ } );
81
+ };
@@ -0,0 +1,28 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+
3
+ export const GLOBAL_VARIABLES_URI = 'elementor://variables';
4
+
5
+ export const initVariablesResource = () => {
6
+ const { mcpServer } = getMCPByDomain( 'variables' );
7
+
8
+ mcpServer.resource(
9
+ 'global-variables',
10
+ GLOBAL_VARIABLES_URI,
11
+ {
12
+ description:
13
+ 'Global variables list. Variables are being used in this way: If it is directly in the schema, you need to put the ID which is the key inside the object.',
14
+ },
15
+ async () => {
16
+ return {
17
+ contents: [ { uri: GLOBAL_VARIABLES_URI, text: localStorage[ 'elementor-global-variables' ] } ],
18
+ };
19
+ }
20
+ );
21
+
22
+ window.addEventListener( 'variables:updated', () => {
23
+ mcpServer.server.sendResourceUpdated( {
24
+ uri: GLOBAL_VARIABLES_URI,
25
+ contents: [ { uri: GLOBAL_VARIABLES_URI, text: localStorage[ 'elementor-global-variables' ] } ],
26
+ } );
27
+ } );
28
+ };
@@ -11,19 +11,23 @@ import { registerVariableType } from './variables-registry/variable-type-registr
11
11
 
12
12
  export function registerVariableTypes() {
13
13
  registerVariableType( {
14
+ key: colorVariablePropTypeUtil.key,
14
15
  valueField: ColorField,
15
16
  icon: BrushIcon,
16
17
  propTypeUtil: colorVariablePropTypeUtil,
17
18
  fallbackPropTypeUtil: colorPropTypeUtil,
18
19
  variableType: 'color',
19
20
  startIcon: ( { value } ) => <ColorIndicator size="inherit" component="span" value={ value } />,
21
+ defaultValue: '#ffffff',
20
22
  } );
21
23
 
22
24
  registerVariableType( {
25
+ key: fontVariablePropTypeUtil.key,
23
26
  valueField: FontField,
24
27
  icon: TextIcon,
25
28
  propTypeUtil: fontVariablePropTypeUtil,
26
29
  fallbackPropTypeUtil: stringPropTypeUtil,
27
30
  variableType: 'font',
31
+ defaultValue: 'Roboto',
28
32
  } );
29
33
  }
package/src/service.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { __ } from '@wordpress/i18n';
2
2
 
3
3
  import { apiClient } from './api';
4
+ import { buildOperationsArray, type OperationResult } from './batch-operations';
4
5
  import { OP_RW, Storage, type TVariablesList } from './storage';
5
6
  import { styleVariablesRepository } from './style-variables-repository';
6
7
  import { type Variable } from './types';
@@ -12,8 +13,12 @@ export const service = {
12
13
  return storage.load();
13
14
  },
14
15
 
16
+ getWatermark: (): number => {
17
+ return storage.state.watermark;
18
+ },
19
+
15
20
  init: () => {
16
- service.load();
21
+ return service.load();
17
22
  },
18
23
 
19
24
  load: () => {
@@ -168,6 +173,60 @@ export const service = {
168
173
  };
169
174
  } );
170
175
  },
176
+
177
+ batchSave: ( originalVariables: TVariablesList, currentVariables: TVariablesList ) => {
178
+ const operations = buildOperationsArray( originalVariables, currentVariables );
179
+ const batchPayload = { operations, watermark: storage.state.watermark };
180
+
181
+ if ( operations.length === 0 ) {
182
+ return Promise.resolve( {
183
+ success: true,
184
+ watermark: storage.state.watermark,
185
+ operations: 0,
186
+ } );
187
+ }
188
+
189
+ return apiClient
190
+ .batch( batchPayload )
191
+ .then( ( response ) => {
192
+ const { success, data: payload } = response.data;
193
+
194
+ if ( ! success ) {
195
+ throw new Error( 'Unexpected response from server' );
196
+ }
197
+
198
+ return payload;
199
+ } )
200
+ .then( ( data ) => {
201
+ const { results, watermark } = data;
202
+
203
+ handleWatermark( OP_RW, watermark );
204
+
205
+ if ( results ) {
206
+ results.forEach( ( result: OperationResult ) => {
207
+ if ( result.variable ) {
208
+ const { id: variableId, ...variableData } = result.variable;
209
+
210
+ if ( result.type === 'create' ) {
211
+ storage.add( variableId, variableData );
212
+ } else {
213
+ storage.update( variableId, variableData );
214
+ }
215
+
216
+ styleVariablesRepository.update( {
217
+ [ variableId ]: variableData,
218
+ } );
219
+ }
220
+ } );
221
+ }
222
+
223
+ return {
224
+ success: true,
225
+ watermark,
226
+ operations: operations.length,
227
+ };
228
+ } );
229
+ },
171
230
  };
172
231
 
173
232
  const handleWatermark = ( operation: string, newWatermark: number ) => {
package/src/storage.ts CHANGED
@@ -2,6 +2,7 @@ export type TVariable = {
2
2
  type: string;
3
3
  label: string;
4
4
  value: string;
5
+ order?: number;
5
6
  deleted?: boolean;
6
7
  deleted_at?: string;
7
8
  };
@@ -20,6 +21,10 @@ export class Storage {
20
21
  variables: TVariablesList;
21
22
  };
22
23
 
24
+ notifyChange() {
25
+ window.dispatchEvent( new Event( 'variables:updated' ) );
26
+ }
27
+
23
28
  constructor() {
24
29
  this.state = {
25
30
  watermark: -1,
@@ -43,18 +48,21 @@ export class Storage {
43
48
 
44
49
  localStorage.setItem( STORAGE_WATERMARK_KEY, this.state.watermark.toString() );
45
50
  localStorage.setItem( STORAGE_KEY, JSON.stringify( this.state.variables ) );
51
+ this.notifyChange();
46
52
  }
47
53
 
48
54
  add( id: string, variable: TVariable ) {
49
55
  this.load();
50
56
  this.state.variables[ id ] = variable;
51
57
  localStorage.setItem( STORAGE_KEY, JSON.stringify( this.state.variables ) );
58
+ this.notifyChange();
52
59
  }
53
60
 
54
61
  update( id: string, variable: TVariable ) {
55
62
  this.load();
56
63
  this.state.variables[ id ] = variable;
57
64
  localStorage.setItem( STORAGE_KEY, JSON.stringify( this.state.variables ) );
65
+ this.notifyChange();
58
66
  }
59
67
 
60
68
  watermark( watermark: number ) {
package/src/types.ts CHANGED
@@ -22,4 +22,5 @@ export type NormalizedVariable = {
22
22
  key: string;
23
23
  label: string;
24
24
  value: string;
25
+ order?: number;
25
26
  };
@@ -0,0 +1,5 @@
1
+ export function filterBySearch< T extends { label: string } >( variables: T[], searchValue: string ): T[] {
2
+ const lowerSearchValue = searchValue.toLowerCase();
3
+
4
+ return variables.filter( ( variable ) => variable.label.toLowerCase().includes( lowerSearchValue ) );
5
+ }
@@ -1,3 +1,5 @@
1
+ import { getMixpanel } from '@elementor/mixpanel';
2
+
1
3
  type VariableEventData = {
2
4
  varType: string;
3
5
  controlPath: string;
@@ -5,35 +7,48 @@ type VariableEventData = {
5
7
  };
6
8
 
7
9
  export const trackVariableEvent = ( { varType, controlPath, action }: VariableEventData ) => {
8
- const extendedWindow = window as unknown as Window & {
9
- elementorCommon?: {
10
- eventsManager?: {
11
- dispatchEvent: ( name: string, data: Record< string, string > ) => void;
12
- config?: {
13
- locations: Record< string, string >;
14
- secondaryLocations: Record< string, string >;
15
- names: {
16
- variables?: Record< string, string >;
17
- };
18
- triggers: Record< string, string >;
19
- elements?: Record< string, string >;
20
- };
21
- };
22
- };
23
- };
24
-
25
- const config = extendedWindow?.elementorCommon?.eventsManager?.config;
10
+ const { dispatchEvent, config } = getMixpanel();
26
11
  if ( ! config?.names?.variables?.[ action ] ) {
27
12
  return;
28
13
  }
29
14
 
30
15
  const name = config.names.variables[ action ];
31
- extendedWindow.elementorCommon?.eventsManager?.dispatchEvent( name, {
32
- location: config.locations.variables,
33
- secondaryLocation: config.secondaryLocations.variablesPopover,
34
- trigger: config.triggers.click,
16
+ dispatchEvent?.( name, {
17
+ location: config?.locations?.variables || '',
18
+ secondaryLocation: config?.secondaryLocations?.variablesPopover || '',
19
+ trigger: config?.triggers?.click || '',
35
20
  var_type: varType,
36
21
  control_path: controlPath,
37
22
  action_type: name,
38
23
  } );
39
24
  };
25
+
26
+ type VariablesManagerEventData = {
27
+ action: 'openManager' | 'add' | 'saveChanges' | 'delete';
28
+ varType?: string;
29
+ controlPath?: string;
30
+ };
31
+
32
+ export const trackVariablesManagerEvent = ( { action, varType, controlPath }: VariablesManagerEventData ) => {
33
+ const { dispatchEvent, config } = getMixpanel();
34
+ if ( ! config?.names?.variables?.[ action ] ) {
35
+ return;
36
+ }
37
+
38
+ const name = config.names.variables[ action ];
39
+ const eventData: Record< string, string > = {
40
+ location: config?.locations?.variablesManager || '',
41
+ trigger: config?.triggers?.click || '',
42
+ action_type: name,
43
+ };
44
+
45
+ if ( varType ) {
46
+ eventData.var_type = varType;
47
+ }
48
+
49
+ if ( controlPath ) {
50
+ eventData.style_control_path = controlPath;
51
+ }
52
+
53
+ dispatchEvent?.( name, eventData );
54
+ };
@@ -7,7 +7,7 @@ export function transformValueBeforeUnlink( variable: Variable, propTypeKey: str
7
7
  const { valueTransformer } = getVariableType( propTypeKey );
8
8
 
9
9
  if ( valueTransformer ) {
10
- return valueTransformer( variable.value );
10
+ return valueTransformer( variable );
11
11
  }
12
12
 
13
13
  return variable.value;
@@ -1,4 +1,8 @@
1
- import { __ } from '@wordpress/i18n';
1
+ import type * as React from 'react';
2
+ import { AlertTriangleFilledIcon, InfoCircleFilledIcon } from '@elementor/icons';
3
+ import { __, sprintf } from '@wordpress/i18n';
4
+
5
+ import { type TVariable, type TVariablesList } from '../storage';
2
6
 
3
7
  export const ERROR_MESSAGES = {
4
8
  MISSING_VARIABLE_NAME: __( 'Give your variable a name.', 'elementor' ),
@@ -8,21 +12,49 @@ export const ERROR_MESSAGES = {
8
12
  VARIABLE_LABEL_MAX_LENGTH: __( 'Keep names up to 50 characters.', 'elementor' ),
9
13
  DUPLICATED_LABEL: __( 'This variable name already exists. Please choose a unique name.', 'elementor' ),
10
14
  UNEXPECTED_ERROR: __( 'There was a glitch. Try saving your variable again.', 'elementor' ),
15
+ BATCH: {
16
+ DUPLICATED_LABELS: ( count: number, name: string ) =>
17
+ // eslint-disable-next-line @wordpress/i18n-translator-comments
18
+ sprintf( __( 'We found %1$d duplicated %2$s.', 'elementor' ), count, name ),
19
+ UNEXPECTED_ERROR: __( 'There was a glitch.', 'elementor' ),
20
+ DUPLICATED_LABEL_ACTION: __( 'Take me there', 'elementor' ),
21
+ DUPLICATED_LABEL_ACTION_MESSAGE: __( 'Please rename the variables.', 'elementor' ),
22
+ UNEXPECTED_ERROR_ACTION_MESSAGE: __( 'Try saving your variables again.', 'elementor' ),
23
+ },
11
24
  } as const;
12
25
 
13
26
  export const VARIABLE_LABEL_MAX_LENGTH = 50;
14
27
 
15
- type ErrorResponse = {
28
+ type BatchErrorData = {
29
+ [ id: string ]: {
30
+ status?: number;
31
+ message?: string;
32
+ };
33
+ };
34
+ export type ErrorResponse = {
16
35
  response?: {
17
36
  data?: {
18
37
  code?: string;
38
+ data?: BatchErrorData;
19
39
  };
20
40
  };
21
41
  };
22
42
 
43
+ export type ErrorAction = {
44
+ label?: string;
45
+ message?: string;
46
+ callback?: () => void;
47
+ data?: {
48
+ duplicatedIds?: string[];
49
+ };
50
+ };
51
+
23
52
  export type MappedError = {
24
53
  field: string;
25
54
  message: string;
55
+ action?: ErrorAction;
56
+ severity?: 'error' | 'secondary';
57
+ IconComponent?: React.ElementType;
26
58
  };
27
59
 
28
60
  export const mapServerError = ( error: ErrorResponse ): MappedError | undefined => {
@@ -33,10 +65,43 @@ export const mapServerError = ( error: ErrorResponse ): MappedError | undefined
33
65
  };
34
66
  }
35
67
 
68
+ if ( error?.response?.data?.code === 'batch_duplicated_label' ) {
69
+ const errorData = error?.response?.data?.data ?? {};
70
+ const count = Object.keys( errorData ).length;
71
+ const name = count === 1 ? 'name' : 'names';
72
+ const duplicatedIds = Object.keys( errorData );
73
+
74
+ return {
75
+ field: 'label',
76
+ message: ERROR_MESSAGES.BATCH.DUPLICATED_LABELS( count, name ),
77
+ severity: 'error',
78
+ IconComponent: AlertTriangleFilledIcon,
79
+ action: {
80
+ label: ERROR_MESSAGES.BATCH.DUPLICATED_LABEL_ACTION,
81
+ message: ERROR_MESSAGES.BATCH.DUPLICATED_LABEL_ACTION_MESSAGE,
82
+ data: {
83
+ duplicatedIds,
84
+ },
85
+ },
86
+ };
87
+ }
88
+
89
+ if ( error?.response?.data?.code === 'batch_operation_failed' ) {
90
+ return {
91
+ field: 'label',
92
+ message: ERROR_MESSAGES.BATCH.UNEXPECTED_ERROR,
93
+ severity: 'secondary',
94
+ IconComponent: InfoCircleFilledIcon,
95
+ action: {
96
+ message: ERROR_MESSAGES.BATCH.UNEXPECTED_ERROR_ACTION_MESSAGE,
97
+ },
98
+ };
99
+ }
100
+
36
101
  return undefined;
37
102
  };
38
103
 
39
- export const validateLabel = ( name: string ): string => {
104
+ export const validateLabel = ( name: string, variables?: TVariablesList ): string => {
40
105
  if ( ! name.trim() ) {
41
106
  return ERROR_MESSAGES.MISSING_VARIABLE_NAME;
42
107
  }
@@ -55,6 +120,10 @@ export const validateLabel = ( name: string ): string => {
55
120
  return ERROR_MESSAGES.VARIABLE_LABEL_MAX_LENGTH;
56
121
  }
57
122
 
123
+ if ( Object.values( variables ?? {} ).some( ( variable: TVariable ) => variable.label === name ) ) {
124
+ return ERROR_MESSAGES.DUPLICATED_LABEL;
125
+ }
126
+
58
127
  return '';
59
128
  };
60
129
 
@@ -1,4 +1,4 @@
1
- import { type ForwardRefExoticComponent, type JSX, type RefAttributes } from 'react';
1
+ import { type ForwardRefExoticComponent, type JSX, type RefAttributes, type RefObject } from 'react';
2
2
  import { styleTransformersRegistry } from '@elementor/editor-canvas';
3
3
  import { stylesInheritanceTransformersRegistry } from '@elementor/editor-editing-panel';
4
4
  import {
@@ -17,8 +17,12 @@ import { type NormalizedVariable, type Variable } from '../types';
17
17
  export type ValueFieldProps = {
18
18
  value: string;
19
19
  onChange: ( value: string ) => void;
20
+ onPropTypeKeyChange?: ( key: string ) => void;
21
+ propTypeKey?: string;
20
22
  onValidationChange?: ( value: string ) => void;
21
23
  propType?: PropType;
24
+ error?: { value: string; message: string };
25
+ ref?: RefObject< HTMLElement | null >;
22
26
  };
23
27
 
24
28
  type FallbackPropTypeUtil = ReturnType< typeof createPropUtils >;
@@ -26,34 +30,36 @@ type FallbackPropTypeUtil = ReturnType< typeof createPropUtils >;
26
30
  type VariableTypeOptions = {
27
31
  icon: ForwardRefExoticComponent< Omit< SvgIconProps, 'ref' > & RefAttributes< SVGSVGElement > >;
28
32
  startIcon?: ( { value }: { value: string } ) => JSX.Element;
29
- valueField: ( { value, onChange, onValidationChange, propType }: ValueFieldProps ) => JSX.Element;
33
+ valueField: ( props: ValueFieldProps ) => JSX.Element;
30
34
  variableType: string;
35
+ key?: string;
36
+ defaultValue?: string;
31
37
  fallbackPropTypeUtil: FallbackPropTypeUtil;
32
38
  propTypeUtil: PropTypeUtil< string, string >;
33
39
  selectionFilter?: ( variables: NormalizedVariable[], propType: PropType ) => NormalizedVariable[];
34
- valueTransformer?: ( value: string ) => PropValue;
40
+ valueTransformer?: ( variable: Variable ) => PropValue;
35
41
  isCompatible?: ( propType: PropType, variable: Variable ) => boolean;
36
42
  };
37
43
 
38
- export type VariableTypesMap = Record< string, VariableTypeOptions >;
44
+ export type VariableTypesMap = Record< string, Omit< VariableTypeOptions, 'key' > >;
39
45
 
40
46
  export function createVariableTypeRegistry() {
41
47
  const variableTypes: VariableTypesMap = {};
42
48
 
43
49
  const registerVariableType = ( {
50
+ key,
44
51
  icon,
45
52
  startIcon,
46
53
  valueField,
47
54
  propTypeUtil,
48
55
  variableType,
56
+ defaultValue,
49
57
  selectionFilter,
50
58
  valueTransformer,
51
59
  fallbackPropTypeUtil,
52
60
  isCompatible,
53
61
  }: VariableTypeOptions ) => {
54
- if ( variableTypes[ propTypeUtil.key ] ) {
55
- throw new Error( `Variable with key "${ propTypeUtil.key }" is already registered.` );
56
- }
62
+ const variableTypeKey = key ?? propTypeUtil.key;
57
63
 
58
64
  if ( ! isCompatible ) {
59
65
  isCompatible = ( propType, variable: Variable ) => {
@@ -66,12 +72,13 @@ export function createVariableTypeRegistry() {
66
72
  };
67
73
  }
68
74
 
69
- variableTypes[ propTypeUtil.key ] = {
75
+ variableTypes[ variableTypeKey ] = {
70
76
  icon,
71
77
  startIcon,
72
78
  valueField,
73
79
  propTypeUtil,
74
80
  variableType,
81
+ defaultValue,
75
82
  selectionFilter,
76
83
  valueTransformer,
77
84
  fallbackPropTypeUtil,
@@ -94,6 +101,10 @@ export function createVariableTypeRegistry() {
94
101
  return variableTypes[ key ];
95
102
  };
96
103
 
104
+ const getVariableTypes = () => {
105
+ return variableTypes;
106
+ };
107
+
97
108
  const hasVariableType = ( key: string ) => {
98
109
  return key in variableTypes;
99
110
  };
@@ -101,6 +112,7 @@ export function createVariableTypeRegistry() {
101
112
  return {
102
113
  registerVariableType,
103
114
  getVariableType,
115
+ getVariableTypes,
104
116
  hasVariableType,
105
117
  };
106
118
  }
@@ -1,3 +1,4 @@
1
1
  import { createVariableTypeRegistry } from './create-variable-type-registry';
2
2
 
3
- export const { registerVariableType, getVariableType, hasVariableType } = createVariableTypeRegistry();
3
+ export const { registerVariableType, getVariableType, getVariableTypes, hasVariableType } =
4
+ createVariableTypeRegistry();