@elementor/editor-variables 3.33.0-98 → 3.34.2

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 (59) hide show
  1. package/dist/index.d.mts +11 -3
  2. package/dist/index.d.ts +11 -3
  3. package/dist/index.js +1874 -801
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1819 -737
  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 +10 -3
  25. package/src/components/variable-edit.tsx +11 -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 +116 -0
  32. package/src/components/variables-manager/variables-manager-panel.tsx +290 -59
  33. package/src/components/variables-manager/variables-manager-table.tsx +111 -14
  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 +11 -8
  37. package/src/hooks/use-variable-bound-prop.ts +42 -0
  38. package/src/index.ts +1 -0
  39. package/src/init.ts +19 -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 +2 -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/validations.ts +72 -3
  53. package/src/variables-registry/create-variable-type-registry.ts +10 -1
  54. package/src/variables-registry/variable-type-registry.ts +2 -1
  55. package/src/components/ui/tags/deleted-tag.tsx +0 -37
  56. package/src/components/ui/tags/mismatch-tag.tsx +0 -37
  57. package/src/components/ui/tags/missing-tag.tsx +0 -25
  58. /package/src/components/variables-manager/{variable-edit-menu.tsx → ui/variable-edit-menu.tsx} +0 -0
  59. /package/src/components/variables-manager/{variable-table-cell.tsx → ui/variable-table-cell.tsx} +0 -0
@@ -1,22 +1,24 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
- import { useBoundProp } from '@elementor/editor-controls';
4
3
  import { PopoverBody } from '@elementor/editor-editing-panel';
5
- import { PopoverHeader, PopoverMenuList, PopoverSearch, type VirtualizedItem } from '@elementor/editor-ui';
4
+ import { PopoverHeader, PopoverMenuList, SearchField, type VirtualizedItem } from '@elementor/editor-ui';
6
5
  import { ColorFilterIcon, PlusIcon, SettingsIcon } from '@elementor/icons';
7
- import { Divider, IconButton } from '@elementor/ui';
6
+ import { Divider, IconButton, Tooltip } from '@elementor/ui';
8
7
  import { __, sprintf } from '@wordpress/i18n';
9
8
 
10
9
  import { useVariableType } from '../context/variable-type-context';
11
10
  import { useFilteredVariables } from '../hooks/use-prop-variables';
11
+ import { useVariableBoundProp } from '../hooks/use-variable-bound-prop';
12
12
  import { type ExtendedVirtualizedItem } from '../types';
13
- import { trackVariableEvent } from '../utils/tracking';
13
+ import { trackVariableEvent, trackVariablesManagerEvent } from '../utils/tracking';
14
+ import { EmptyState } from './ui/empty-state';
14
15
  import { MenuItemContent } from './ui/menu-item-content';
15
16
  import { NoSearchResults } from './ui/no-search-results';
16
- import { NoVariables } from './ui/no-variables';
17
17
  import { VariablesStyledMenuList } from './ui/styled-menu-list';
18
18
 
19
19
  const SIZE = 'tiny';
20
+ const CREATE_LABEL = __( 'Create variable', 'elementor' );
21
+ const MANAGER_LABEL = __( 'Variables Manager', 'elementor' );
20
22
 
21
23
  type Props = {
22
24
  closePopover: () => void;
@@ -28,13 +30,14 @@ type Props = {
28
30
  export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }: Props ) => {
29
31
  const { icon: VariableIcon, startIcon, variableType, propTypeUtil } = useVariableType();
30
32
 
31
- const { value: variable, setValue: setVariable, path } = useBoundProp( propTypeUtil );
33
+ const { value: variable, setValue: setVariable, path } = useVariableBoundProp();
32
34
  const [ searchValue, setSearchValue ] = useState( '' );
33
35
 
34
36
  const {
35
37
  list: variables,
36
38
  hasMatches: hasSearchResults,
37
39
  isSourceNotEmpty: hasVariables,
40
+ hasNoCompatibleVariables,
38
41
  } = useFilteredVariables( searchValue, propTypeUtil.key );
39
42
 
40
43
  const handleSetVariable = ( key: string ) => {
@@ -60,17 +63,40 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
60
63
 
61
64
  if ( onAdd ) {
62
65
  actions.push(
63
- <IconButton key="add" size={ SIZE } onClick={ onAddAndTrack }>
64
- <PlusIcon fontSize={ SIZE } />
65
- </IconButton>
66
+ <Tooltip key="add" placement="top" title={ CREATE_LABEL }>
67
+ <IconButton
68
+ id="add-variable-button"
69
+ size={ SIZE }
70
+ onClick={ onAddAndTrack }
71
+ aria-label={ CREATE_LABEL }
72
+ >
73
+ <PlusIcon fontSize={ SIZE } />
74
+ </IconButton>
75
+ </Tooltip>
66
76
  );
67
77
  }
68
78
 
69
79
  if ( onSettings ) {
80
+ const handleOpenManager = () => {
81
+ onSettings();
82
+ trackVariablesManagerEvent( {
83
+ action: 'openManager',
84
+ varType: variableType,
85
+ controlPath: path.join( '.' ),
86
+ } );
87
+ };
88
+
70
89
  actions.push(
71
- <IconButton key="settings" size={ SIZE } onClick={ onSettings }>
72
- <SettingsIcon fontSize={ SIZE } />
73
- </IconButton>
90
+ <Tooltip key="settings" placement="top" title={ MANAGER_LABEL }>
91
+ <IconButton
92
+ id="variables-manager-button"
93
+ size={ SIZE }
94
+ onClick={ handleOpenManager }
95
+ aria-label={ MANAGER_LABEL }
96
+ >
97
+ <SettingsIcon fontSize={ SIZE } />
98
+ </IconButton>
99
+ </Tooltip>
74
100
  );
75
101
  }
76
102
 
@@ -109,7 +135,7 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
109
135
  />
110
136
 
111
137
  { hasVariables && (
112
- <PopoverSearch
138
+ <SearchField
113
139
  value={ searchValue }
114
140
  onSearch={ handleSearch }
115
141
  placeholder={ __( 'Search', 'elementor' ) }
@@ -140,8 +166,28 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
140
166
  />
141
167
  ) }
142
168
 
143
- { ! hasVariables && (
144
- <NoVariables title={ noVariableTitle } icon={ <VariableIcon fontSize="large" /> } onAdd={ onAdd } />
169
+ { ! hasVariables && ! hasNoCompatibleVariables && (
170
+ <EmptyState
171
+ title={ noVariableTitle }
172
+ message={ __(
173
+ 'Variables are saved attributes that you can apply anywhere on your site.',
174
+ 'elementor'
175
+ ) }
176
+ icon={ <VariableIcon fontSize="large" /> }
177
+ onAdd={ onAdd }
178
+ />
179
+ ) }
180
+
181
+ { hasNoCompatibleVariables && (
182
+ <EmptyState
183
+ title={ __( 'No compatible variables', 'elementor' ) }
184
+ message={ __(
185
+ 'Looks like none of your variables work with this control. Create a new variable to use it here.',
186
+ 'elementor'
187
+ ) }
188
+ icon={ <VariableIcon fontSize="large" /> }
189
+ onAdd={ onAdd }
190
+ />
145
191
  ) }
146
192
  </PopoverBody>
147
193
  );
@@ -12,7 +12,7 @@ import { getVariableType } from '../variables-registry/variable-type-registry';
12
12
  export const VariableControl = () => {
13
13
  const boundProp = useBoundProp();
14
14
 
15
- const boundPropValue = boundProp.value as TransformablePropValue< string, string >;
15
+ const boundPropValue = ( boundProp.value ?? boundProp.placeholder ) as TransformablePropValue< string, string >;
16
16
 
17
17
  const assignedVariable = useVariable( boundPropValue?.value );
18
18
 
@@ -5,6 +5,7 @@ import { type PropKey } from '@elementor/editor-props';
5
5
  import { useVariableType } from '../context/variable-type-context';
6
6
  import { service } from '../service';
7
7
  import { type NormalizedVariable, type Variable } from '../types';
8
+ import { filterBySearch } from '../utils/filter-by-search';
8
9
 
9
10
  export const getVariables = ( includeDeleted = true ) => {
10
11
  const variables = service.variables();
@@ -33,12 +34,18 @@ export const useFilteredVariables = ( searchValue: string, propTypeKey: string )
33
34
  const baseVariables = usePropVariables( propTypeKey );
34
35
 
35
36
  const typeFilteredVariables = useVariableSelectionFilter( baseVariables );
36
- const searchFilteredVariables = filterVariablesBySearchValue( typeFilteredVariables, searchValue );
37
+ const searchFilteredVariables = filterBySearch( typeFilteredVariables, searchValue );
38
+ const sortedVariables = searchFilteredVariables.sort( ( a, b ) => {
39
+ const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
40
+ const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
41
+ return orderA - orderB;
42
+ } );
37
43
 
38
44
  return {
39
- list: searchFilteredVariables,
45
+ list: sortedVariables,
40
46
  hasMatches: searchFilteredVariables.length > 0,
41
47
  isSourceNotEmpty: typeFilteredVariables.length > 0,
48
+ hasNoCompatibleVariables: baseVariables.length > 0 && typeFilteredVariables.length === 0,
42
49
  };
43
50
  };
44
51
 
@@ -49,11 +56,6 @@ const useVariableSelectionFilter = ( variables: NormalizedVariable[] ): Normaliz
49
56
  return selectionFilter ? selectionFilter( variables, propType ) : variables;
50
57
  };
51
58
 
52
- const filterVariablesBySearchValue = ( variables: NormalizedVariable[], searchValue: string ): NormalizedVariable[] => {
53
- const lowerSearchValue = searchValue.toLowerCase();
54
- return variables.filter( ( { label } ) => label.toLowerCase().includes( lowerSearchValue ) );
55
- };
56
-
57
59
  const usePropVariables = ( propKey: PropKey ): NormalizedVariable[] => {
58
60
  return useMemo( () => normalizeVariables( propKey ), [ propKey ] );
59
61
  };
@@ -63,10 +65,11 @@ const normalizeVariables = ( propKey: string ) => {
63
65
 
64
66
  return Object.entries( variables )
65
67
  .filter( ( [ , variable ] ) => variable.type === propKey )
66
- .map( ( [ key, { label, value } ] ) => ( {
68
+ .map( ( [ key, { label, value, order } ] ) => ( {
67
69
  key,
68
70
  label,
69
71
  value,
72
+ order,
70
73
  } ) );
71
74
  };
72
75
 
@@ -0,0 +1,42 @@
1
+ import { useBoundProp } from '@elementor/editor-controls';
2
+ import { isTransformable, type PropValue } from '@elementor/editor-props';
3
+
4
+ import { useVariableType } from '../context/variable-type-context';
5
+
6
+ type BoundProp = ReturnType< typeof useBoundProp< PropValue > >;
7
+
8
+ type VariableBoundProp = ReturnType< typeof useBoundProp< string > > & {
9
+ setVariableValue: ( value: PropValue ) => void;
10
+ variableId: string | null;
11
+ };
12
+
13
+ export const useVariableBoundProp = (): VariableBoundProp => {
14
+ const { propTypeUtil } = useVariableType();
15
+ const boundProp = useBoundProp( propTypeUtil );
16
+
17
+ return {
18
+ ...boundProp,
19
+ setVariableValue: ( value: PropValue ) => resolveBoundPropAndSetValue( value, boundProp as BoundProp ),
20
+ variableId: boundProp.value ?? boundProp.placeholder,
21
+ };
22
+ };
23
+
24
+ export const resolveBoundPropAndSetValue = ( value: PropValue, boundProp: BoundProp ) => {
25
+ const propValue = unwrapValue( boundProp.value );
26
+ const placeholder = unwrapValue( boundProp.placeholder );
27
+ const newValue = unwrapValue( value );
28
+
29
+ if ( ! propValue && placeholder === newValue ) {
30
+ return boundProp.setValue( null );
31
+ }
32
+
33
+ return boundProp.setValue( value );
34
+ };
35
+
36
+ const unwrapValue = ( input: PropValue ): PropValue => {
37
+ if ( isTransformable( input ) ) {
38
+ return input.value;
39
+ }
40
+
41
+ return input;
42
+ };
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { init } from './init';
2
2
  export { registerVariableType } from './variables-registry/variable-type-registry';
3
+ export { registerVariableTypes } from './register-variable-types';
package/src/init.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { injectIntoTop } from '@elementor/editor';
2
2
  import { controlActionsMenu, registerControlReplacement } from '@elementor/editor-editing-panel';
3
3
  import { __registerPanel as registerPanel } from '@elementor/editor-panels';
4
- import type { PropValue } from '@elementor/editor-props';
4
+ import { isTransformable, type PropValue } from '@elementor/editor-props';
5
5
 
6
6
  import { panel } from './components/variables-manager/variables-manager-panel';
7
7
  import { VariableControl } from './controls/variable-control';
8
8
  import { usePropVariableAction } from './hooks/use-prop-variable-action';
9
+ import { initMcp } from './mcp';
9
10
  import { registerVariableTypes } from './register-variable-types';
10
11
  import { StyleVariablesRenderer } from './renderers/style-variables-renderer';
11
12
  import { registerRepeaterInjections } from './repeater-injections';
@@ -20,7 +21,17 @@ export function init() {
20
21
 
21
22
  registerControlReplacement( {
22
23
  component: VariableControl,
23
- condition: ( { value } ) => hasAssignedVariable( value ),
24
+ condition: ( { value, placeholder } ) => {
25
+ if ( hasVariableAssigned( value ) ) {
26
+ return true;
27
+ }
28
+
29
+ if ( value ) {
30
+ return false;
31
+ }
32
+
33
+ return hasVariableAssigned( placeholder );
34
+ },
24
35
  } );
25
36
 
26
37
  registerPopoverAction( {
@@ -28,7 +39,9 @@ export function init() {
28
39
  useProps: usePropVariableAction,
29
40
  } );
30
41
 
31
- variablesService.init();
42
+ variablesService.init().then( () => {
43
+ initMcp();
44
+ } );
32
45
 
33
46
  injectIntoTop( {
34
47
  id: 'canvas-style-variables-render',
@@ -38,9 +51,9 @@ export function init() {
38
51
  registerPanel( panel );
39
52
  }
40
53
 
41
- function hasAssignedVariable( propValue: PropValue ) {
42
- if ( propValue && typeof propValue === 'object' && '$$type' in propValue ) {
43
- return hasVariableType( propValue.$$type );
54
+ function hasVariableAssigned( value: PropValue ) {
55
+ if ( isTransformable( value ) ) {
56
+ return hasVariableType( value.$$type );
44
57
  }
45
58
 
46
59
  return false;
@@ -0,0 +1,70 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+ import { z } from '@elementor/schema';
3
+
4
+ import { service } from '../service';
5
+
6
+ const InputSchema = {
7
+ type: z
8
+ .string()
9
+ .describe( 'The type of the variable. Example values: "global-color-variable" or "global-font-variable".' ),
10
+ label: z.string().describe( 'The label of the variable, displayed to the user' ),
11
+ value: z.string().describe( 'The value of the variable, should correspond to the type' ),
12
+ };
13
+
14
+ const OutputSchema = {
15
+ status: z.enum( [ 'ok', 'error' ] ).describe( 'The status of the operation' ),
16
+ message: z.string().optional().describe( 'Optional message providing additional information about the operation' ),
17
+ };
18
+
19
+ export const initCreateVariableTool = () => {
20
+ getMCPByDomain( 'variables' ).addTool( {
21
+ name: 'create-global-variable',
22
+ schema: InputSchema,
23
+ outputSchema: OutputSchema,
24
+ description: `Create a new global variable
25
+ ## When to use this tool:
26
+ - When a user requests to create a new global variable in the Elementor editor.
27
+ - When you need to add a new variable to be used in the editor.
28
+
29
+ ## Prequisites:
30
+ - 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.
31
+ - Make sure when creating a new variable, the label is unique and not already in use.
32
+ - If the user does not provide a label, ask them to provide one before proceeding.
33
+ - If the user does not provide a type, ask them to provide one before proceeding.
34
+ - If the user does not provide a value, ask them to provide one before proceeding.
35
+
36
+ ## Required parameters:
37
+ - type: The type of the variable. Possible values are 'global-color-variable' or 'global-font-variable'.
38
+ - label: The label of the variable, displayed to the user. Must be unique and not already in use.
39
+ - value: The value of 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').
40
+
41
+ ## Example tool call (JSON format):
42
+ \`\`\`json
43
+ { "type": "global-color-variable", "label": "My Cool Color", "value": "rgb(1,2,3)" }
44
+ \`\`\`
45
+
46
+ ## Example tool response (JSON format):
47
+ \`\`\`json
48
+ { "status": "ok" }
49
+ \`\`\`
50
+
51
+ ## Example to a failed tool response, 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.
52
+ { "status": "error", "message": "Unsupported type 'global-kuku-variable'" }
53
+
54
+ In that case, inform the user the type is unsupported and they should try another type, perhaps consult to online documentation.
55
+ `,
56
+ handler: async ( params ) => {
57
+ const { type, label, value } = params;
58
+ try {
59
+ await service.create( { type, label, value } );
60
+ } catch ( error ) {
61
+ const message: string = ( error as Error ).message || 'Unknown server error';
62
+ return {
63
+ status: 'error',
64
+ message: `There was an error creating the variable: ${ message }`,
65
+ };
66
+ }
67
+ return { status: 'ok' };
68
+ },
69
+ } );
70
+ };
@@ -0,0 +1,50 @@
1
+ import { getMCPByDomain } from '@elementor/editor-mcp';
2
+ import { z } from '@elementor/schema';
3
+
4
+ import { service } from '../service';
5
+
6
+ export const initDeleteVariableTool = () => {
7
+ getMCPByDomain( 'variables' ).addTool( {
8
+ name: 'delete-global-variable',
9
+ schema: {
10
+ id: z.string().describe( 'The unique identifier of the variable to be deleted.' ),
11
+ },
12
+ outputSchema: {
13
+ status: z.enum( [ 'ok', 'error' ] ).describe( 'The status of the operation' ),
14
+ },
15
+ description: `Delete an existing global variable
16
+
17
+ ## When to use this tool:
18
+ - When a user requests to delete an existing global variable in the Elementor editor.
19
+ - When you need to remove a variable that is no longer needed or relevant, with the user's confirmation.
20
+
21
+ ## Prerequisites:
22
+ - Ensure you have the most up-to-date list of existing global variables. You can use the "list-global-variables" tool to fetch the current variables.
23
+ - Reference the variable by the "id" property, given from the "list-global-variables" tool.
24
+ - Make sure you have the unique identifier of the variable to be deleted before using this tool.
25
+ - Confirm with the user that they want to proceed with the deletion, as this action is irreversible.
26
+
27
+ <notice>
28
+ A use might reference a variable by it's label, but you must always use the unique identifier (id) to delete it.
29
+ If you only have the label, use the "list-global-variables" tool to find the corresponding id.
30
+ </notice>
31
+
32
+ <important>
33
+ This operation is destructive and cannot be undone. Ensure that the user is fully aware of the consequences before proceeding.
34
+ When a variable is deleted, all references to it in all pages accross the website will lose their effect.
35
+ </important>`,
36
+ handler: async ( params ) => {
37
+ const { id } = params;
38
+ try {
39
+ await service.delete( id );
40
+ return { status: 'ok' };
41
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
42
+ } catch ( err: unknown ) {
43
+ return {
44
+ status: 'error',
45
+ };
46
+ }
47
+ },
48
+ isDestrcutive: true,
49
+ } );
50
+ };
@@ -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
+ };
@@ -17,6 +17,7 @@ export function registerVariableTypes() {
17
17
  fallbackPropTypeUtil: colorPropTypeUtil,
18
18
  variableType: 'color',
19
19
  startIcon: ( { value } ) => <ColorIndicator size="inherit" component="span" value={ value } />,
20
+ defaultValue: '#ffffff',
20
21
  } );
21
22
 
22
23
  registerVariableType( {
@@ -25,5 +26,6 @@ export function registerVariableTypes() {
25
26
  propTypeUtil: fontVariablePropTypeUtil,
26
27
  fallbackPropTypeUtil: stringPropTypeUtil,
27
28
  variableType: 'font',
29
+ defaultValue: 'Roboto',
28
30
  } );
29
31
  }