@elementor/editor-variables 4.1.0-838 → 4.1.0-beta2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-variables",
3
- "version": "4.1.0-838",
3
+ "version": "4.1.0-beta2",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,22 +39,22 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.1.0-838",
43
- "@elementor/editor-canvas": "4.1.0-838",
44
- "@elementor/editor-controls": "4.1.0-838",
45
- "@elementor/editor-current-user": "4.1.0-838",
46
- "@elementor/editor-mcp": "4.1.0-838",
47
- "@elementor/editor-panels": "4.1.0-838",
48
- "@elementor/editor-props": "4.1.0-838",
49
- "@elementor/editor-ui": "4.1.0-838",
50
- "@elementor/editor-v1-adapters": "4.1.0-838",
51
- "@elementor/menus": "4.1.0-838",
52
- "@elementor/http-client": "4.1.0-838",
42
+ "@elementor/editor": "4.1.0-beta2",
43
+ "@elementor/editor-canvas": "4.1.0-beta2",
44
+ "@elementor/editor-controls": "4.1.0-beta2",
45
+ "@elementor/editor-current-user": "4.1.0-beta2",
46
+ "@elementor/editor-mcp": "4.1.0-beta2",
47
+ "@elementor/editor-panels": "4.1.0-beta2",
48
+ "@elementor/editor-props": "4.1.0-beta2",
49
+ "@elementor/editor-ui": "4.1.0-beta2",
50
+ "@elementor/editor-v1-adapters": "4.1.0-beta2",
51
+ "@elementor/menus": "4.1.0-beta2",
52
+ "@elementor/http-client": "4.1.0-beta2",
53
53
  "@elementor/icons": "~1.75.1",
54
- "@elementor/events": "4.1.0-838",
55
- "@elementor/schema": "4.1.0-838",
54
+ "@elementor/events": "4.1.0-beta2",
55
+ "@elementor/schema": "4.1.0-beta2",
56
56
  "@elementor/ui": "1.37.5",
57
- "@elementor/utils": "4.1.0-838",
57
+ "@elementor/utils": "4.1.0-beta2",
58
58
  "@wordpress/i18n": "^5.13.0"
59
59
  },
60
60
  "peerDependencies": {
@@ -2,28 +2,17 @@ import { useEffect } from 'react';
2
2
  import { GLOBAL_STYLES_IMPORTED_EVENT } from '@elementor/editor-canvas';
3
3
 
4
4
  import { service } from '../service';
5
- import { styleVariablesRepository } from '../style-variables-repository';
6
5
 
7
6
  export function GlobalStylesImportListener() {
8
7
  useEffect( () => {
9
- const handleGlobalStylesImported = ( event: CustomEvent ) => {
10
- const importedVars = event.detail?.global_variables;
11
-
12
- if ( ! importedVars ) {
13
- return;
14
- }
15
-
16
- if ( importedVars.data && typeof importedVars.data === 'object' ) {
17
- styleVariablesRepository.update( importedVars.data );
18
- }
19
-
8
+ const handleGlobalStylesImported = () => {
20
9
  service.load();
21
10
  };
22
11
 
23
- window.addEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
12
+ window.addEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported );
24
13
 
25
14
  return () => {
26
- window.removeEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
15
+ window.removeEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported );
27
16
  };
28
17
  }, [] );
29
18
 
@@ -1,9 +1,9 @@
1
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  import { generateTempId } from '../../../batch-operations';
4
4
  import { getVariables } from '../../../hooks/use-prop-variables';
5
5
  import { service } from '../../../service';
6
- import { type TVariablesList } from '../../../storage';
6
+ import { STORAGE_UPDATED_EVENT, type TVariablesList } from '../../../storage';
7
7
  import { generateDuplicateLabel } from '../../../utils/duplicate-label';
8
8
  import { filterBySearch } from '../../../utils/filter-by-search';
9
9
  import { applySelectionFilters, variablesToList } from '../../../utils/variables-to-list';
@@ -17,10 +17,40 @@ export const useVariablesManagerState = () => {
17
17
  const [ isSaving, setIsSaving ] = useState( false );
18
18
  const [ searchValue, setSearchValue ] = useState( '' );
19
19
 
20
+ useEffect( () => {
21
+ const handleStorageUpdated = () => {
22
+ setVariables( getVariables( false ) );
23
+ setDeletedVariables( [] );
24
+ setIsDirty( false );
25
+ };
26
+
27
+ window.addEventListener( STORAGE_UPDATED_EVENT, handleStorageUpdated );
28
+
29
+ return () => {
30
+ window.removeEventListener( STORAGE_UPDATED_EVENT, handleStorageUpdated );
31
+ };
32
+ }, [] );
33
+
20
34
  const handleOnChange = useCallback(
21
35
  ( newVariables: TVariablesList ) => {
22
- setVariables( { ...variables, ...newVariables } );
23
- setIsDirty( true );
36
+ const hasChanges = Object.entries( newVariables ).some( ( [ id, newVar ] ) => {
37
+ const existingVar = variables[ id ];
38
+ if ( ! existingVar ) {
39
+ return true;
40
+ }
41
+ return (
42
+ existingVar.label !== newVar.label ||
43
+ existingVar.value !== newVar.value ||
44
+ existingVar.order !== newVar.order ||
45
+ existingVar.type !== newVar.type ||
46
+ ( existingVar.sync_to_v3 ?? false ) !== ( newVar.sync_to_v3 ?? false )
47
+ );
48
+ } );
49
+
50
+ if ( hasChanges ) {
51
+ setVariables( { ...variables, ...newVariables } );
52
+ setIsDirty( true );
53
+ }
24
54
  },
25
55
  [ variables ]
26
56
  );
@@ -162,7 +162,7 @@ export const VariableRow = (
162
162
  value,
163
163
  onChange,
164
164
  onPropTypeKeyChange: ( type ) => {
165
- if ( ! isDisabled ) {
165
+ if ( ! isDisabled && type !== row.type ) {
166
166
  handleOnChange( {
167
167
  ...variables,
168
168
  [ row.id ]: { ...variables[ row.id ], type },
@@ -512,7 +512,6 @@ function VariablesManagerPanelRoot( {
512
512
  flexDirection: 'column',
513
513
  flex: 1,
514
514
  minHeight: 0,
515
- overflow: 'hidden',
516
515
  } }
517
516
  >
518
517
  { bodyInner }
package/src/init.ts CHANGED
@@ -47,7 +47,15 @@ export function init() {
47
47
  } );
48
48
 
49
49
  variablesService.init().then( () => {
50
- initMcp( getMCPByDomain( 'variables' ), getMCPByDomain( 'canvas' ) );
50
+ const variablesMcpRegistry = getMCPByDomain( 'variables', {
51
+ instructions: `Everything related to V4 ( Atomic ) variables.
52
+ # Global variables
53
+ - Create/update/delete global variables
54
+ - Get list of global variables
55
+ - Get details of a global variable
56
+ `,
57
+ } );
58
+ initMcp( variablesMcpRegistry, getMCPByDomain( 'canvas' ) );
51
59
  } );
52
60
 
53
61
  injectIntoTop( {
package/src/mcp/index.ts CHANGED
@@ -4,15 +4,6 @@ import { initManageVariableTool } from './manage-variable-tool';
4
4
  import { initVariablesResource } from './variables-resource';
5
5
 
6
6
  export function initMcp( reg: MCPRegistryEntry, canvasMcpEntry: MCPRegistryEntry ) {
7
- const { setMCPDescription } = reg;
8
- setMCPDescription(
9
- `Everything related to V4 ( Atomic ) variables.
10
- # Global variables
11
- - Create/update/delete global variables
12
- - Get list of global variables
13
- - Get details of a global variable
14
- `
15
- );
16
7
  initManageVariableTool( reg );
17
8
  initVariablesResource( reg, canvasMcpEntry );
18
9
  }
@@ -1,26 +1,79 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
2
  import { z } from '@elementor/schema';
3
+ import { isProActive } from '@elementor/utils';
3
4
 
4
5
  import { service } from '../service';
5
6
  import { validateLabel } from '../utils/validations';
7
+ import { generateVariablesPrompt, MANAGE_VARIABLES_GUIDE_URI } from './variable-tool-prompt';
6
8
  import { GLOBAL_VARIABLES_URI } from './variables-resource';
7
9
 
10
+ const VARIABLE_TYPES = {
11
+ COLOR: 'global-color-variable',
12
+ FONT: 'global-font-variable',
13
+ SIZE: 'global-size-variable',
14
+ CUSTOM_SIZE: 'global-custom-size-variable',
15
+ } as const;
16
+
17
+ const LENGTH_UNIT_PATTERN = /^(auto|\d+(\.\d+)?(px|rem|em|vh|vw|%|ch|s|ms))$/i;
18
+ const COLOR_PATTERN = /^(#[0-9a-f]{3,8}|rgba?\(|hsl)/i;
19
+
20
+ function validateValueForType( type: string, value: string ): string | null {
21
+ if ( type === VARIABLE_TYPES.FONT && LENGTH_UNIT_PATTERN.test( value.trim() ) ) {
22
+ return `Font variable value must be a font family name (e.g. "Roboto"), not a size value like "${ value }". Use "global-size-variable" or "global-custom-size-variable" for spacing/size values.`;
23
+ }
24
+
25
+ if ( type === VARIABLE_TYPES.COLOR && ! COLOR_PATTERN.test( value.trim() ) ) {
26
+ return `Color variable value should be a CSS color (e.g. "#FF0000"), got "${ value }".`;
27
+ }
28
+
29
+ if ( type === VARIABLE_TYPES.SIZE && ! LENGTH_UNIT_PATTERN.test( value.trim() ) ) {
30
+ return `Size variable value should include a CSS unit (e.g. "16px") or be "auto", got "${ value }".`;
31
+ }
32
+
33
+ return null;
34
+ }
35
+
8
36
  export const initManageVariableTool = ( reg: MCPRegistryEntry ) => {
9
- const { addTool } = reg;
37
+ const { addTool, resource } = reg;
38
+
39
+ resource(
40
+ 'manage-global-variable-guide',
41
+ MANAGE_VARIABLES_GUIDE_URI,
42
+ {
43
+ title: 'Manage Global Variable Guide',
44
+ description: 'Detailed guide for using the manage-global-variable tool',
45
+ mimeType: 'text/plain',
46
+ },
47
+ async ( uri: URL ) => ( {
48
+ contents: [ { uri: uri.href, mimeType: 'text/plain', text: generateVariablesPrompt() } ],
49
+ } )
50
+ );
51
+
10
52
  addTool( {
11
53
  name: 'manage-global-variable',
54
+ description: 'Manage V4 global variables (color, font, size). Read the guide resource before use.',
12
55
  schema: {
13
56
  action: z.enum( [ 'create', 'update', 'delete' ] ).describe( 'Operation to perform' ),
14
57
  id: z
15
58
  .string()
16
59
  .optional()
17
- .describe( 'Variable id (required for update/delete). Get from list-global-variables.' ),
60
+ .describe( 'Variable id required for update/delete. Get from the global-variables resource.' ),
18
61
  type: z
19
62
  .string()
20
63
  .optional()
21
- .describe( 'Variable type: "global-color-variable" or "global-font-variable" (required for create)' ),
22
- label: z.string().optional().describe( 'Variable label (required for create/update)' ),
23
- value: z.string().optional().describe( 'Variable value (required for create/update)' ),
64
+ .describe(
65
+ 'Variable type — required for create. One of: "global-color-variable", "global-font-variable", "global-size-variable", "global-custom-size-variable" (size types require Elementor Pro). NEVER store px/rem values in a font variable.'
66
+ ),
67
+ label: z
68
+ .string()
69
+ .optional()
70
+ .describe( 'Variable label (lowercase, dash-separated) — required for create/update.' ),
71
+ value: z
72
+ .string()
73
+ .optional()
74
+ .describe(
75
+ 'Plain CSS value — required for create/update. Color: hex/rgba/hsl. Font: family name only, never px/rem. Size: value with unit e.g. "16px", or "auto" (Pro). Do NOT pass JSON.'
76
+ ),
24
77
  },
25
78
  outputSchema: {
26
79
  status: z.enum( [ 'ok' ] ).describe( 'Operation status' ),
@@ -31,31 +84,22 @@ export const initManageVariableTool = ( reg: MCPRegistryEntry ) => {
31
84
  speedPriority: 0.75,
32
85
  },
33
86
  requiredResources: [
87
+ { uri: MANAGE_VARIABLES_GUIDE_URI, description: 'Full guide for variable types, naming rules, and usage' },
34
88
  {
35
89
  uri: GLOBAL_VARIABLES_URI,
36
- description: 'Global variables',
90
+ description: 'Current global variables — check before creating to avoid duplicates',
37
91
  },
38
92
  ],
39
- description: `Manages global variables (create/update/delete). Existing variables available in resources.
40
- CREATE: requires type, label, value. Ensure label is unique.
41
- UPDATE: requires id, label, value. When renaming: keep existing value. When updating value: keep exact label.
42
- DELETE: requires id. DESTRUCTIVE - confirm with user first.
43
-
44
- # NAMING - IMPORTANT
45
- the variables names should ALWAYS be lowercased and dashed spaced. example: "Headline Primary" should be "headline-primary"
46
- `,
93
+ isDestructive: true,
47
94
  handler: async ( params ) => {
48
95
  const operations = getServiceActions( service );
49
96
  const op = operations[ params.action ];
50
97
  if ( op ) {
51
98
  await op( params );
52
- return {
53
- status: 'ok',
54
- };
99
+ return { status: 'ok' };
55
100
  }
56
101
  throw new Error( `Unknown action ${ params.action }` );
57
102
  },
58
- isDestructive: true, // Because delete is destructive
59
103
  } );
60
104
  };
61
105
 
@@ -69,10 +113,17 @@ function getServiceActions( svc: typeof service ) {
69
113
  if ( ! type || ! label || ! value ) {
70
114
  throw new Error( 'Create requires type, label, and value' );
71
115
  }
116
+ if ( ( type === VARIABLE_TYPES.SIZE || type === VARIABLE_TYPES.CUSTOM_SIZE ) && ! isProActive() ) {
117
+ throw new Error( 'Creating size variables requires Elementor Pro.' );
118
+ }
72
119
  const labelError = validateLabel( label );
73
120
  if ( labelError ) {
74
121
  throw new Error( labelError );
75
122
  }
123
+ const valueError = validateValueForType( type, value );
124
+ if ( valueError ) {
125
+ throw new Error( valueError );
126
+ }
76
127
  return svc.create( { type, label, value } );
77
128
  },
78
129
  update( { id, label, value }: Opts< { id: string; label: string; value: string } > ) {
@@ -83,6 +134,13 @@ function getServiceActions( svc: typeof service ) {
83
134
  if ( labelError ) {
84
135
  throw new Error( labelError );
85
136
  }
137
+ const existingVariable = svc.variables()[ id ];
138
+ if ( existingVariable ) {
139
+ const valueError = validateValueForType( existingVariable.type, value );
140
+ if ( valueError ) {
141
+ throw new Error( valueError );
142
+ }
143
+ }
86
144
  return svc.update( id, { label, value } );
87
145
  },
88
146
  delete( { id }: Opts< { id: string } > ) {
@@ -0,0 +1,85 @@
1
+ import { toolPrompts } from '@elementor/editor-mcp';
2
+ import { isProActive } from '@elementor/utils';
3
+
4
+ export const MANAGE_VARIABLES_GUIDE_URI = 'elementor://variables/tools/manage-global-variable-guide';
5
+
6
+ export const generateVariablesPrompt = () => {
7
+ const prompt = toolPrompts( 'manage-global-variable' );
8
+ const proIsActive = isProActive();
9
+
10
+ const sizeVariableSection = proIsActive
11
+ ? `- **global-size-variable** — A simple CSS length with a unit (Elementor Pro). Use this for fixed spacing, font sizes, or layout values. Example: \`16px\`, \`1.5rem\`, \`2em\`, \`10vh\`
12
+ - **global-custom-size-variable** — Any CSS size expression that goes beyond a simple number + unit (Elementor Pro). Use this when the value is a CSS function, a keyword, or a combination of units that \`global-size-variable\` cannot represent. Example: \`auto\`, \`clamp(1rem, 2vw, 2rem)\`, \`calc(100% - 32px)\`, \`min(50vw, 600px)\`, \`300ms\`, \`2ch\`. When in doubt: if the value contains a function call or a keyword, use \`global-custom-size-variable\`.`
13
+ : `- ~~global-size-variable~~ — requires Elementor Pro (not available on this site)
14
+ - ~~global-custom-size-variable~~ — requires Elementor Pro (not available on this site)`;
15
+
16
+ prompt.description( `
17
+ # Purpose
18
+ Create, update, or delete V4 global CSS variables. These are distinct from legacy v3 globals and map 1:1 to \`--css-var: VALUE\`.
19
+
20
+ # Available Types
21
+ - **global-color-variable** — CSS color value. Example: \`#FF0000\`, \`rgba(255,0,0,1)\`, \`hsl(0,100%,50%)\`
22
+ - **global-font-variable** — Font family name ONLY — NOT a size or px value. Example: \`Roboto\`, \`Open Sans\`. NEVER pass px/rem here.
23
+ ${ sizeVariableSection }
24
+
25
+ # Naming Rules
26
+ - Labels must be **lowercase**, using only letters (a-z), numbers, digits (0-9), dashes (-), or underscores (_)
27
+ - No spaces, no special characters
28
+ - Example: "Headline Primary" → \`headline-primary\`
29
+ - Labels must be unique — always check [elementor://global-variables] first
30
+
31
+ # Value Rules
32
+ - Provide a **plain CSS value** only — do NOT pass JSON, legacy-globals object structures, or variable references
33
+ - Values are inserted as-is: \`--css-var: <value>\`
34
+ - NEVER store a px/rem value inside a \`global-font-variable\` — use \`global-size-variable\` (Pro) instead
35
+
36
+ # Operations
37
+ - **create** — requires \`type\`, \`label\`, \`value\`. Label must be unique.
38
+ - **update** — requires \`id\`, \`label\`, \`value\`. Get \`id\` from [elementor://global-variables]. When renaming: keep existing value. When changing value: keep exact existing label.
39
+ - **delete** — requires \`id\`. DESTRUCTIVE — always confirm with user before executing.
40
+ ` );
41
+
42
+ prompt.parameter( 'action', '"create", "update", or "delete".' );
43
+ prompt.parameter( 'type', 'Variable type. Required for create. See Available Types above.' );
44
+ prompt.parameter( 'label', 'Variable name (lowercase, dash-separated). Required for create/update.' );
45
+ prompt.parameter(
46
+ 'value',
47
+ 'Plain CSS value matching the variable type. Required for create/update. Do NOT pass JSON.'
48
+ );
49
+ prompt.parameter( 'id', 'Variable ID. Required for update/delete. Obtain from [elementor://global-variables].' );
50
+
51
+ prompt.example( `
52
+ Create a brand color:
53
+ { "action": "create", "type": "global-color-variable", "label": "brand-primary", "value": "#1A73E8" }
54
+
55
+ Create a heading font:
56
+ { "action": "create", "type": "global-font-variable", "label": "font-heading", "value": "Playfair Display" }
57
+
58
+ Create a simple spacing size:
59
+ { "action": "create", "type": "global-size-variable", "label": "spacing-md", "value": "16px" }
60
+
61
+ Create a fluid/responsive size using a CSS function (use global-custom-size-variable, NOT global-size-variable):
62
+ { "action": "create", "type": "global-custom-size-variable", "label": "spacing-fluid", "value": "clamp(1rem, 2vw, 2rem)" }
63
+
64
+ Create a size that is a keyword:
65
+ { "action": "create", "type": "global-custom-size-variable", "label": "width-auto", "value": "auto" }
66
+
67
+ Create a size using calc():
68
+ { "action": "create", "type": "global-custom-size-variable", "label": "sidebar-width", "value": "calc(100% - 32px)" }
69
+
70
+ Update a variable's value (keep exact label):
71
+ { "action": "update", "id": "abc123", "label": "brand-primary", "value": "#0D47A1" }
72
+
73
+ Rename a variable (keep existing value):
74
+ { "action": "update", "id": "abc123", "label": "brand-secondary", "value": "#1A73E8" }
75
+
76
+ Delete a variable:
77
+ { "action": "delete", "id": "abc123" }
78
+ ` );
79
+
80
+ prompt.instruction(
81
+ 'Always read [elementor://global-variables] before creating to check existing variables and avoid duplicate labels.'
82
+ );
83
+
84
+ return prompt.prompt();
85
+ };
@@ -2,7 +2,7 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
2
  import { __privateListenTo as listenTo, commandEndEvent } from '@elementor/editor-v1-adapters';
3
3
 
4
4
  import { service } from '../service';
5
- import { type TVariable } from '../storage';
5
+ import { STORAGE_UPDATED_EVENT, type TVariable } from '../storage';
6
6
 
7
7
  export const GLOBAL_VARIABLES_URI = 'elementor://global-variables';
8
8
 
@@ -47,7 +47,7 @@ export const initVariablesResource = ( variablesMcpEntry: MCPRegistryEntry, canv
47
47
  }
48
48
  );
49
49
 
50
- window.addEventListener( 'variables:updated', notifyGlobalVariablesUpdated );
50
+ window.addEventListener( STORAGE_UPDATED_EVENT, notifyGlobalVariablesUpdated );
51
51
 
52
52
  listenTo( commandEndEvent( 'document/save/update' ), notifyGlobalVariablesUpdated );
53
53
  } );
package/src/storage.ts CHANGED
@@ -13,6 +13,8 @@ export type TVariablesList = Record< string, TVariable >;
13
13
  const STORAGE_KEY = 'elementor-global-variables';
14
14
  const STORAGE_WATERMARK_KEY = 'elementor-global-variables-watermark';
15
15
 
16
+ export const STORAGE_UPDATED_EVENT = 'variables:updated';
17
+
16
18
  export const OP_RW = 'RW';
17
19
  const OP_RO = 'RO';
18
20
 
@@ -23,7 +25,7 @@ export class Storage {
23
25
  };
24
26
 
25
27
  notifyChange() {
26
- window.dispatchEvent( new Event( 'variables:updated' ) );
28
+ window.dispatchEvent( new Event( STORAGE_UPDATED_EVENT ) );
27
29
  }
28
30
 
29
31
  constructor() {