@elementor/editor-variables 0.16.0 → 3.32.0-20

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 (67) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.d.mts +19 -1
  3. package/dist/index.d.ts +19 -1
  4. package/dist/index.js +1397 -885
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1399 -871
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +16 -14
  9. package/src/api.ts +18 -2
  10. package/src/components/fields/color-field.tsx +3 -3
  11. package/src/components/fields/font-field.tsx +21 -10
  12. package/src/components/fields/label-field.tsx +31 -5
  13. package/src/components/ui/delete-confirmation-dialog.tsx +4 -7
  14. package/src/components/ui/deleted-variable-alert.tsx +47 -0
  15. package/src/components/ui/edit-confirmation-dialog.tsx +75 -0
  16. package/src/components/ui/missing-variable-alert.tsx +39 -0
  17. package/src/components/ui/no-variables.tsx +59 -26
  18. package/src/components/ui/tags/assigned-tag.tsx +21 -19
  19. package/src/components/ui/tags/deleted-tag.tsx +29 -18
  20. package/src/components/ui/tags/missing-tag.tsx +25 -0
  21. package/src/components/ui/variable/assigned-variable.tsx +11 -14
  22. package/src/components/ui/variable/deleted-variable.tsx +115 -7
  23. package/src/components/ui/variable/missing-variable.tsx +44 -0
  24. package/src/components/variable-creation.tsx +135 -0
  25. package/src/components/variable-edit.tsx +221 -0
  26. package/src/components/variable-restore.tsx +117 -0
  27. package/src/components/variable-selection-popover.tsx +91 -92
  28. package/src/components/variables-manager/variables-manager-panel.tsx +115 -0
  29. package/src/components/variables-selection.tsx +148 -0
  30. package/src/context/variable-selection-popover.context.tsx +19 -0
  31. package/src/context/variable-type-context.tsx +23 -0
  32. package/src/controls/variable-control.tsx +26 -0
  33. package/src/create-style-variables-repository.ts +44 -5
  34. package/src/hooks/use-initial-value.ts +22 -0
  35. package/src/hooks/use-permissions.ts +15 -0
  36. package/src/hooks/use-prop-variable-action.tsx +53 -0
  37. package/src/hooks/use-prop-variables.ts +6 -0
  38. package/src/index.ts +1 -0
  39. package/src/init.ts +33 -4
  40. package/src/register-variable-types.tsx +29 -0
  41. package/src/renderers/style-variables-renderer.tsx +10 -4
  42. package/src/repeater-injections.ts +5 -1
  43. package/src/service.ts +8 -4
  44. package/src/sync/enqueue-font.ts +7 -0
  45. package/src/sync/types.ts +5 -0
  46. package/src/transformers/inheritance-transformer.tsx +30 -0
  47. package/src/transformers/utils/resolve-css-variable.ts +24 -0
  48. package/src/transformers/variable-transformer.ts +8 -3
  49. package/src/types.ts +1 -1
  50. package/src/utils/tracking.ts +39 -0
  51. package/src/utils/validations.ts +40 -6
  52. package/src/variables-registry/create-variable-type-registry.ts +77 -0
  53. package/src/variables-registry/variable-type-registry.ts +3 -0
  54. package/src/components/color-variable-creation.tsx +0 -86
  55. package/src/components/color-variable-edit.tsx +0 -138
  56. package/src/components/color-variables-selection.tsx +0 -130
  57. package/src/components/font-variable-creation.tsx +0 -86
  58. package/src/components/font-variable-edit.tsx +0 -138
  59. package/src/components/font-variables-selection.tsx +0 -129
  60. package/src/components/variable-selection-popover.context.ts +0 -7
  61. package/src/controls/color-variable-control.tsx +0 -33
  62. package/src/controls/font-variable-control.tsx +0 -31
  63. package/src/hooks/use-prop-color-variable-action.tsx +0 -25
  64. package/src/hooks/use-prop-font-variable-action.tsx +0 -25
  65. package/src/init-color-variables.ts +0 -27
  66. package/src/init-font-variables.ts +0 -24
  67. package/src/utils.ts +0 -20
@@ -1,3 +1,5 @@
1
+ import { fontVariablePropTypeUtil } from './prop-types/font-variable-prop-type';
2
+ import { enqueueFont } from './sync/enqueue-font';
1
3
  import { type StyleVariables, type Variable } from './types';
2
4
 
3
5
  type VariablesChangeCallback = ( variables: StyleVariables ) => void;
@@ -21,16 +23,41 @@ export const createStyleVariablesRepository = () => {
21
23
  }
22
24
  };
23
25
 
24
- const shouldUpdate = ( key: string, newValue: string ): boolean => {
25
- return ! ( key in variables ) || variables[ key ] !== newValue;
26
+ const shouldUpdate = ( key: string, maybeUpdated: Variable ): boolean => {
27
+ if ( ! ( key in variables ) ) {
28
+ return true;
29
+ }
30
+
31
+ if ( variables[ key ].label !== maybeUpdated.label ) {
32
+ return true;
33
+ }
34
+
35
+ if ( variables[ key ].value !== maybeUpdated.value ) {
36
+ return true;
37
+ }
38
+
39
+ if ( ! variables[ key ]?.deleted && maybeUpdated?.deleted ) {
40
+ return true;
41
+ }
42
+
43
+ if ( variables[ key ]?.deleted && ! maybeUpdated?.deleted ) {
44
+ return true;
45
+ }
46
+
47
+ return false;
26
48
  };
27
49
 
28
50
  const applyUpdates = ( updatedVars: Variables ): boolean => {
29
51
  let hasChanges = false;
30
52
 
31
- for ( const [ key, { value } ] of Object.entries( updatedVars ) ) {
32
- if ( shouldUpdate( key, value ) ) {
33
- variables[ key ] = value;
53
+ for ( const [ key, variable ] of Object.entries( updatedVars ) ) {
54
+ if ( shouldUpdate( key, variable ) ) {
55
+ variables[ key ] = variable;
56
+
57
+ if ( variable.type === fontVariablePropTypeUtil.key ) {
58
+ fontEnqueue( variable.value );
59
+ }
60
+
34
61
  hasChanges = true;
35
62
  }
36
63
  }
@@ -38,6 +65,18 @@ export const createStyleVariablesRepository = () => {
38
65
  return hasChanges;
39
66
  };
40
67
 
68
+ const fontEnqueue = ( value: string ): void => {
69
+ if ( ! value ) {
70
+ return;
71
+ }
72
+
73
+ try {
74
+ enqueueFont( value );
75
+ } catch {
76
+ // This prevents font enqueueing failures from breaking variable updates
77
+ }
78
+ };
79
+
41
80
  const update = ( updatedVars: Variables ) => {
42
81
  if ( applyUpdates( updatedVars ) ) {
43
82
  notify();
@@ -0,0 +1,22 @@
1
+ import { useBoundProp } from '@elementor/editor-controls';
2
+
3
+ import { hasVariableType } from '../variables-registry/variable-type-registry';
4
+ import { useVariable } from './use-prop-variables';
5
+
6
+ type PropValue = {
7
+ $$type: string;
8
+ value: string;
9
+ };
10
+
11
+ export const useInitialValue = () => {
12
+ const { value: initial }: { value: PropValue } = useBoundProp();
13
+
14
+ const hasAssignedVariable = hasVariableType( initial?.$$type ) && Boolean( initial?.value );
15
+ const variable = useVariable( hasAssignedVariable ? initial.value : '' );
16
+
17
+ if ( hasAssignedVariable ) {
18
+ return variable ? variable.value : '';
19
+ }
20
+
21
+ return initial?.value ?? '';
22
+ };
@@ -0,0 +1,15 @@
1
+ import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
2
+
3
+ export const usePermissions = () => {
4
+ const { canUser } = useCurrentUserCapabilities();
5
+
6
+ return {
7
+ canAssign: () => canUser( 'edit_posts' ),
8
+ canUnlink: () => canUser( 'edit_posts' ),
9
+ canAdd: () => canUser( 'manage_options' ),
10
+ canDelete: () => canUser( 'manage_options' ),
11
+ canEdit: () => canUser( 'manage_options' ),
12
+ canRestore: () => canUser( 'manage_options' ),
13
+ canManageSettings: () => canUser( 'manage_options' ),
14
+ };
15
+ };
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { type PopoverActionProps, useBoundProp } from '@elementor/editor-editing-panel';
3
+ import { type PropType } from '@elementor/editor-props';
4
+ import { ColorFilterIcon } from '@elementor/icons';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { VariableSelectionPopover } from '../components/variable-selection-popover';
8
+ import { trackVariableEvent } from '../utils/tracking';
9
+ import { getVariableType } from '../variables-registry/variable-type-registry';
10
+
11
+ export const usePropVariableAction = (): PopoverActionProps => {
12
+ const { propType, path } = useBoundProp();
13
+ const variable = resolveVariableFromPropType( propType );
14
+
15
+ return {
16
+ visible: Boolean( variable ),
17
+ icon: ColorFilterIcon,
18
+ title: __( 'Variables', 'elementor' ),
19
+ content: ( { close: closePopover } ) => {
20
+ if ( ! variable ) {
21
+ return null;
22
+ }
23
+
24
+ trackOpenVariablePopover( path, variable.variableType );
25
+
26
+ return <VariableSelectionPopover closePopover={ closePopover } propTypeKey={ variable.propTypeUtil.key } />;
27
+ },
28
+ };
29
+ };
30
+
31
+ const resolveVariableFromPropType = ( propType: PropType ) => {
32
+ if ( propType.kind !== 'union' ) {
33
+ return undefined;
34
+ }
35
+
36
+ for ( const key of Object.keys( propType.prop_types ) ) {
37
+ const variable = getVariableType( key );
38
+
39
+ if ( variable ) {
40
+ return variable;
41
+ }
42
+ }
43
+
44
+ return undefined;
45
+ };
46
+
47
+ const trackOpenVariablePopover = ( path: string[], variableType: string ) => {
48
+ trackVariableEvent( {
49
+ varType: variableType,
50
+ controlPath: path.join( '.' ),
51
+ action: 'open',
52
+ } );
53
+ };
@@ -66,3 +66,9 @@ export const deleteVariable = ( deleteId: string ) => {
66
66
  return id;
67
67
  } );
68
68
  };
69
+
70
+ export const restoreVariable = ( restoreId: string, label?: string, value?: string ) => {
71
+ return service.restore( restoreId, label, value ).then( ( { id }: { id: string } ) => {
72
+ return id;
73
+ } );
74
+ };
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { init } from './init';
2
+ export { registerVariableType } from './variables-registry/variable-type-registry';
package/src/init.ts CHANGED
@@ -1,13 +1,32 @@
1
1
  import { injectIntoTop } from '@elementor/editor';
2
+ import { controlActionsMenu, registerControlReplacement } from '@elementor/editor-editing-panel';
3
+ import { __registerPanel as registerPanel } from '@elementor/editor-panels';
4
+ import type { PropValue } from '@elementor/editor-props';
2
5
 
3
- import { initColorVariables } from './init-color-variables';
4
- import { initFontVariables } from './init-font-variables';
6
+ import { panel } from './components/variables-manager/variables-manager-panel';
7
+ import { VariableControl } from './controls/variable-control';
8
+ import { usePropVariableAction } from './hooks/use-prop-variable-action';
9
+ import { registerVariableTypes } from './register-variable-types';
5
10
  import { StyleVariablesRenderer } from './renderers/style-variables-renderer';
11
+ import { registerRepeaterInjections } from './repeater-injections';
6
12
  import { service as variablesService } from './service';
13
+ import { hasVariableType } from './variables-registry/variable-type-registry';
14
+
15
+ const { registerPopoverAction } = controlActionsMenu;
7
16
 
8
17
  export function init() {
9
- initColorVariables();
10
- initFontVariables();
18
+ registerVariableTypes();
19
+ registerRepeaterInjections();
20
+
21
+ registerControlReplacement( {
22
+ component: VariableControl,
23
+ condition: ( { value } ) => hasAssignedVariable( value ),
24
+ } );
25
+
26
+ registerPopoverAction( {
27
+ id: 'variables',
28
+ useProps: usePropVariableAction,
29
+ } );
11
30
 
12
31
  variablesService.init();
13
32
 
@@ -15,4 +34,14 @@ export function init() {
15
34
  id: 'canvas-style-variables-render',
16
35
  component: StyleVariablesRenderer,
17
36
  } );
37
+
38
+ registerPanel( panel );
39
+ }
40
+
41
+ function hasAssignedVariable( propValue: PropValue ) {
42
+ if ( propValue && typeof propValue === 'object' && '$$type' in propValue ) {
43
+ return hasVariableType( propValue.$$type );
44
+ }
45
+
46
+ return false;
18
47
  }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { colorPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
3
+ import { BrushIcon, TextIcon } from '@elementor/icons';
4
+
5
+ import { ColorField } from './components/fields/color-field';
6
+ import { FontField } from './components/fields/font-field';
7
+ import { ColorIndicator } from './components/ui/color-indicator';
8
+ import { colorVariablePropTypeUtil } from './prop-types/color-variable-prop-type';
9
+ import { fontVariablePropTypeUtil } from './prop-types/font-variable-prop-type';
10
+ import { registerVariableType } from './variables-registry/variable-type-registry';
11
+
12
+ export function registerVariableTypes() {
13
+ registerVariableType( {
14
+ valueField: ColorField,
15
+ icon: BrushIcon,
16
+ propTypeUtil: colorVariablePropTypeUtil,
17
+ fallbackPropTypeUtil: colorPropTypeUtil,
18
+ variableType: 'color',
19
+ startIcon: ( { value } ) => <ColorIndicator size="inherit" component="span" value={ value } />,
20
+ } );
21
+
22
+ registerVariableType( {
23
+ valueField: FontField,
24
+ icon: TextIcon,
25
+ propTypeUtil: fontVariablePropTypeUtil,
26
+ fallbackPropTypeUtil: stringPropTypeUtil,
27
+ variableType: 'font',
28
+ } );
29
+ }
@@ -5,7 +5,7 @@ import { Portal } from '@elementor/ui';
5
5
 
6
6
  import { styleVariablesRepository } from '../style-variables-repository';
7
7
  import { getCanvasIframeDocument } from '../sync/get-canvas-iframe-document';
8
- import { type StyleVariables } from '../types';
8
+ import { type StyleVariables, type Variable } from '../types';
9
9
 
10
10
  const VARIABLES_WRAPPER = 'body';
11
11
 
@@ -49,8 +49,14 @@ function useStyleVariables() {
49
49
  return variables;
50
50
  }
51
51
 
52
+ function cssVariableDeclaration( key: string, variable: Variable ) {
53
+ const variableName = variable?.deleted ? key : variable.label;
54
+ const value = variable.value;
55
+
56
+ return `--${ variableName }:${ value };`;
57
+ }
58
+
52
59
  function convertToCssVariables( variables: StyleVariables ): string {
53
- return Object.entries( variables )
54
- .map( ( [ key, value ] ) => `--${ key }:${ value };` )
55
- .join( '' );
60
+ const listOfVariables = Object.entries( variables );
61
+ return listOfVariables.map( ( [ key, variable ] ) => cssVariableDeclaration( key, variable ) ).join( '' );
56
62
  }
@@ -6,7 +6,7 @@ import {
6
6
  BackgroundRepeaterLabel,
7
7
  BoxShadowRepeaterColorIndicator,
8
8
  } from './components/variables-repeater-item-slot';
9
- import { hasAssignedColorVariable } from './utils';
9
+ import { colorVariablePropTypeUtil } from './prop-types/color-variable-prop-type';
10
10
 
11
11
  export function registerRepeaterInjections() {
12
12
  injectIntoRepeaterItemIcon( {
@@ -33,3 +33,7 @@ export function registerRepeaterInjections() {
33
33
  },
34
34
  } );
35
35
  }
36
+
37
+ const hasAssignedColorVariable = ( propValue: PropValue ): boolean => {
38
+ return !! colorVariablePropTypeUtil.isValid( propValue );
39
+ };
package/src/service.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { __ } from '@wordpress/i18n';
2
+
1
3
  import { apiClient } from './api';
2
4
  import { OP_RW, Storage, type TVariablesList } from './storage';
3
5
  import { styleVariablesRepository } from './style-variables-repository';
@@ -44,7 +46,8 @@ export const service = {
44
46
  const { success, data: payload } = response.data;
45
47
 
46
48
  if ( ! success ) {
47
- throw new Error( 'Unexpected response from server' );
49
+ const errorMessage = payload?.message || __( 'Unexpected response from server', 'elementor' );
50
+ throw new Error( errorMessage );
48
51
  }
49
52
 
50
53
  return payload;
@@ -76,7 +79,8 @@ export const service = {
76
79
  const { success, data: payload } = response.data;
77
80
 
78
81
  if ( ! success ) {
79
- throw new Error( 'Unexpected response from server' );
82
+ const errorMessage = payload?.message || __( 'Unexpected response from server', 'elementor' );
83
+ throw new Error( errorMessage );
80
84
  }
81
85
 
82
86
  return payload;
@@ -133,9 +137,9 @@ export const service = {
133
137
  } );
134
138
  },
135
139
 
136
- restore: ( id: string ) => {
140
+ restore: ( id: string, label?: string, value?: string ) => {
137
141
  return apiClient
138
- .restore( id )
142
+ .restore( id, label, value )
139
143
  .then( ( response ) => {
140
144
  const { success, data: payload } = response.data;
141
145
 
@@ -0,0 +1,7 @@
1
+ import { type CanvasExtendedWindow, type EnqueueFont } from './types';
2
+
3
+ export const enqueueFont: EnqueueFont = ( fontFamily, context = 'preview' ) => {
4
+ const extendedWindow = window as unknown as CanvasExtendedWindow;
5
+
6
+ return extendedWindow.elementor?.helpers?.enqueueFont?.( fontFamily, context ) ?? null;
7
+ };
package/src/sync/types.ts CHANGED
@@ -1,5 +1,10 @@
1
+ export type EnqueueFont = ( fontFamily: string, context?: 'preview' | 'editor' ) => void;
2
+
1
3
  export type CanvasExtendedWindow = Window & {
2
4
  elementor?: {
3
5
  $preview?: [ HTMLIFrameElement ];
6
+ helpers?: {
7
+ enqueueFont?: EnqueueFont;
8
+ };
4
9
  };
5
10
  };
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import { createTransformer } from '@elementor/editor-canvas';
3
+ import { Stack, Typography } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { ColorIndicator } from '../components/ui/color-indicator';
7
+ import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
8
+ import { service } from '../service';
9
+ import { resolveCssVariable } from './utils/resolve-css-variable';
10
+
11
+ export const inheritanceTransformer = createTransformer( ( id: string ) => {
12
+ const variables = service.variables();
13
+ const variable = variables[ id ];
14
+
15
+ if ( ! variable ) {
16
+ return <span>{ __( 'Missing variable', 'elementor' ) }</span>;
17
+ }
18
+
19
+ const showColorIndicator = variable.type === colorVariablePropTypeUtil.key;
20
+ const css = resolveCssVariable( id, variable );
21
+
22
+ return (
23
+ <Stack direction="row" spacing={ 0.5 } sx={ { paddingInline: '1px' } } alignItems="center">
24
+ { showColorIndicator && <ColorIndicator size="inherit" value={ variable.value } /> }
25
+ <Typography variant="caption" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">
26
+ { css }
27
+ </Typography>
28
+ </Stack>
29
+ );
30
+ } );
@@ -0,0 +1,24 @@
1
+ import { type TVariable } from '../../storage';
2
+
3
+ export const resolveCssVariable = ( id: string, variable: TVariable ) => {
4
+ let name = id;
5
+ let fallbackValue = '';
6
+
7
+ if ( variable ) {
8
+ fallbackValue = variable.value;
9
+ }
10
+
11
+ if ( variable && ! variable.deleted ) {
12
+ name = variable.label;
13
+ }
14
+
15
+ if ( ! name.trim() ) {
16
+ return null;
17
+ }
18
+
19
+ if ( ! fallbackValue.trim() ) {
20
+ return `var(--${ name })`;
21
+ }
22
+
23
+ return `var(--${ name }, ${ fallbackValue })`;
24
+ };
@@ -1,9 +1,14 @@
1
1
  import { createTransformer } from '@elementor/editor-canvas';
2
2
 
3
- export const variableTransformer = createTransformer( ( value: string ) => {
4
- if ( ! value.trim() ) {
3
+ import { service } from '../service';
4
+ import { resolveCssVariable } from './utils/resolve-css-variable';
5
+
6
+ export const variableTransformer = createTransformer( ( id: string ) => {
7
+ const variables = service.variables();
8
+
9
+ if ( ! variables[ id ] ) {
5
10
  return null;
6
11
  }
7
12
 
8
- return `var(--${ value })`;
13
+ return resolveCssVariable( id, variables[ id ] );
9
14
  } );
package/src/types.ts CHANGED
@@ -10,7 +10,7 @@ export type Variable = {
10
10
  deleted_at?: string;
11
11
  };
12
12
 
13
- export type StyleVariables = Record< string, string >;
13
+ export type StyleVariables = Record< string, Variable >;
14
14
 
15
15
  export type ExtendedVirtualizedItem = VirtualizedItem< 'item', string > & {
16
16
  icon: React.ReactNode;
@@ -0,0 +1,39 @@
1
+ type VariableEventData = {
2
+ varType: string;
3
+ controlPath: string;
4
+ action: 'open' | 'add' | 'connect' | 'save';
5
+ };
6
+
7
+ 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;
26
+ if ( ! config?.names?.variables?.[ action ] ) {
27
+ return;
28
+ }
29
+
30
+ 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,
35
+ var_type: varType,
36
+ control_path: controlPath,
37
+ action_type: name,
38
+ } );
39
+ };
@@ -1,24 +1,58 @@
1
1
  import { __ } from '@wordpress/i18n';
2
2
 
3
+ export const ERROR_MESSAGES = {
4
+ MISSING_VARIABLE_NAME: __( 'Give your variable a name.', 'elementor' ),
5
+ MISSING_VARIABLE_VALUE: __( 'Add a value to complete your variable.', 'elementor' ),
6
+ INVALID_CHARACTERS: __( 'Use letters, numbers, dashes (-), or underscores (_) for the name.', 'elementor' ),
7
+ NO_NON_SPECIAL_CHARACTER: __( 'Names have to include at least one non-special character.', 'elementor' ),
8
+ VARIABLE_LABEL_MAX_LENGTH: __( 'Keep names up to 50 characters.', 'elementor' ),
9
+ DUPLICATED_LABEL: __( 'This variable name already exists. Please choose a unique name.', 'elementor' ),
10
+ UNEXPECTED_ERROR: __( 'There was a glitch. Try saving your variable again.', 'elementor' ),
11
+ } as const;
12
+
3
13
  export const VARIABLE_LABEL_MAX_LENGTH = 50;
4
14
 
15
+ type ErrorResponse = {
16
+ response?: {
17
+ data?: {
18
+ code?: string;
19
+ };
20
+ };
21
+ };
22
+
23
+ export type MappedError = {
24
+ field: string;
25
+ message: string;
26
+ };
27
+
28
+ export const mapServerError = ( error: ErrorResponse ): MappedError | undefined => {
29
+ if ( error?.response?.data?.code === 'duplicated_label' ) {
30
+ return {
31
+ field: 'label',
32
+ message: ERROR_MESSAGES.DUPLICATED_LABEL,
33
+ };
34
+ }
35
+
36
+ return undefined;
37
+ };
38
+
5
39
  export const validateLabel = ( name: string ): string => {
6
40
  if ( ! name.trim() ) {
7
- return __( 'Missing variable name.', 'elementor' );
41
+ return ERROR_MESSAGES.MISSING_VARIABLE_NAME;
8
42
  }
9
43
 
10
44
  const allowedChars = /^[a-zA-Z0-9_-]+$/;
11
45
  if ( ! allowedChars.test( name ) ) {
12
- return __( 'Names can only use letters, numbers, dashes (-) and underscores (_).', 'elementor' );
46
+ return ERROR_MESSAGES.INVALID_CHARACTERS;
13
47
  }
14
48
 
15
49
  const hasAlphanumeric = /[a-zA-Z0-9]/;
16
50
  if ( ! hasAlphanumeric.test( name ) ) {
17
- return __( 'Names have to include at least one non-special character.', 'elementor' );
51
+ return ERROR_MESSAGES.NO_NON_SPECIAL_CHARACTER;
18
52
  }
19
53
 
20
54
  if ( VARIABLE_LABEL_MAX_LENGTH < name.length ) {
21
- return __( 'Variable names can contain up to 50 characters.', 'elementor' );
55
+ return ERROR_MESSAGES.VARIABLE_LABEL_MAX_LENGTH;
22
56
  }
23
57
 
24
58
  return '';
@@ -27,7 +61,7 @@ export const validateLabel = ( name: string ): string => {
27
61
  export const labelHint = ( name: string ): string => {
28
62
  const hintThreshold = VARIABLE_LABEL_MAX_LENGTH * 0.8 - 1;
29
63
  if ( hintThreshold < name.length ) {
30
- return __( 'Variable names can contain up to 50 characters.', 'elementor' );
64
+ return ERROR_MESSAGES.VARIABLE_LABEL_MAX_LENGTH;
31
65
  }
32
66
 
33
67
  return '';
@@ -35,7 +69,7 @@ export const labelHint = ( name: string ): string => {
35
69
 
36
70
  export const validateValue = ( value: string ): string => {
37
71
  if ( ! value.trim() ) {
38
- return __( 'Missing variable value.', 'elementor' );
72
+ return ERROR_MESSAGES.MISSING_VARIABLE_VALUE;
39
73
  }
40
74
 
41
75
  return '';
@@ -0,0 +1,77 @@
1
+ import { type ForwardRefExoticComponent, type JSX, type RefAttributes } from 'react';
2
+ import { styleTransformersRegistry } from '@elementor/editor-canvas';
3
+ import { stylesInheritanceTransformersRegistry } from '@elementor/editor-editing-panel';
4
+ import { type createPropUtils, type PropTypeKey, type PropTypeUtil } from '@elementor/editor-props';
5
+ import { type SvgIconProps } from '@elementor/ui';
6
+
7
+ import { inheritanceTransformer } from '../transformers/inheritance-transformer';
8
+ import { variableTransformer } from '../transformers/variable-transformer';
9
+
10
+ type ValueFieldProps = {
11
+ value: string;
12
+ onChange: ( value: string ) => void;
13
+ };
14
+
15
+ type FallbackPropTypeUtil = ReturnType< typeof createPropUtils >;
16
+
17
+ type VariableTypeOptions = {
18
+ icon: ForwardRefExoticComponent< Omit< SvgIconProps, 'ref' > & RefAttributes< SVGSVGElement > >;
19
+ startIcon?: ( { value }: { value: string } ) => JSX.Element;
20
+ valueField: ( { value, onChange }: ValueFieldProps ) => JSX.Element;
21
+ variableType: string;
22
+ fallbackPropTypeUtil: FallbackPropTypeUtil;
23
+ propTypeUtil: PropTypeUtil< string, string >;
24
+ };
25
+
26
+ export type VariableTypesMap = Record< string, VariableTypeOptions >;
27
+
28
+ export function createVariableTypeRegistry() {
29
+ const variableTypes: VariableTypesMap = {};
30
+
31
+ const registerVariableType = ( {
32
+ icon,
33
+ startIcon,
34
+ valueField,
35
+ propTypeUtil,
36
+ variableType,
37
+ fallbackPropTypeUtil,
38
+ }: VariableTypeOptions ) => {
39
+ if ( variableTypes[ propTypeUtil.key ] ) {
40
+ throw new Error( `Variable with key "${ propTypeUtil.key }" is already registered.` );
41
+ }
42
+
43
+ variableTypes[ propTypeUtil.key ] = {
44
+ icon,
45
+ startIcon,
46
+ valueField,
47
+ propTypeUtil,
48
+ variableType,
49
+ fallbackPropTypeUtil,
50
+ };
51
+
52
+ registerTransformer( propTypeUtil.key );
53
+ registerInheritanceTransformer( propTypeUtil.key );
54
+ };
55
+
56
+ const registerTransformer = ( key: PropTypeKey ) => {
57
+ styleTransformersRegistry.register( key, variableTransformer );
58
+ };
59
+
60
+ const registerInheritanceTransformer = ( key: PropTypeKey ) => {
61
+ stylesInheritanceTransformersRegistry.register( key, inheritanceTransformer );
62
+ };
63
+
64
+ const getVariableType = ( key: string ) => {
65
+ return variableTypes[ key ];
66
+ };
67
+
68
+ const hasVariableType = ( key: string ) => {
69
+ return key in variableTypes;
70
+ };
71
+
72
+ return {
73
+ registerVariableType,
74
+ getVariableType,
75
+ hasVariableType,
76
+ };
77
+ }
@@ -0,0 +1,3 @@
1
+ import { createVariableTypeRegistry } from './create-variable-type-registry';
2
+
3
+ export const { registerVariableType, getVariableType, hasVariableType } = createVariableTypeRegistry();