@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
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { AlertTriangleFilledIcon } from '@elementor/icons';
3
+ import { Chip, type ChipProps, type Theme } from '@elementor/ui';
4
+
5
+ export const MissingTag = React.forwardRef< HTMLDivElement, ChipProps >( ( { label, onClick, ...props }, ref ) => {
6
+ return (
7
+ <Chip
8
+ ref={ ref }
9
+ size="tiny"
10
+ color="warning"
11
+ shape="rounded"
12
+ variant="standard"
13
+ onClick={ onClick }
14
+ icon={ <AlertTriangleFilledIcon /> }
15
+ label={ label }
16
+ sx={ {
17
+ height: ( theme: Theme ) => theme.spacing( 3.5 ),
18
+ borderRadius: ( theme: Theme ) => theme.spacing( 1 ),
19
+ justifyContent: 'flex-start',
20
+ width: '100%',
21
+ } }
22
+ { ...props }
23
+ />
24
+ );
25
+ } );
@@ -1,27 +1,22 @@
1
1
  import { useId, useRef } from 'react';
2
2
  import * as React from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
- import { type PropTypeUtil } from '@elementor/editor-props';
4
+ import { type PropTypeKey } from '@elementor/editor-props';
5
5
  import { ColorFilterIcon } from '@elementor/icons';
6
6
  import { bindPopover, bindTrigger, Box, Popover, usePopupState } from '@elementor/ui';
7
7
 
8
8
  import { type Variable } from '../../../types';
9
+ import { getVariableType } from '../../../variables-registry/variable-type-registry';
9
10
  import { VariableSelectionPopover } from '../../variable-selection-popover';
10
11
  import { AssignedTag, SIZE } from '../tags/assigned-tag';
11
12
 
12
13
  type Props = {
13
- variablePropTypeUtil: PropTypeUtil< string, string >;
14
- fallbackPropTypeUtil: PropTypeUtil< string, string | null > | PropTypeUtil< string, string >;
15
- additionalStartIcon?: React.ReactNode;
14
+ propTypeKey: PropTypeKey;
16
15
  variable: Variable;
17
16
  };
18
17
 
19
- export const AssignedVariable = ( {
20
- variable,
21
- variablePropTypeUtil,
22
- fallbackPropTypeUtil,
23
- additionalStartIcon,
24
- }: Props ) => {
18
+ export const AssignedVariable = ( { variable, propTypeKey }: Props ) => {
19
+ const { fallbackPropTypeUtil, startIcon, propTypeUtil } = getVariableType( propTypeKey );
25
20
  const { setValue } = useBoundProp();
26
21
  const anchorRef = useRef< HTMLDivElement >( null );
27
22
 
@@ -32,9 +27,12 @@ export const AssignedVariable = ( {
32
27
  } );
33
28
 
34
29
  const unlinkVariable = () => {
35
- setValue( fallbackPropTypeUtil.create( variable.value ) );
30
+ const fallbackValue = fallbackPropTypeUtil.create( variable.value );
31
+ setValue( fallbackValue );
36
32
  };
37
33
 
34
+ const StartIcon = startIcon || ( () => null );
35
+
38
36
  return (
39
37
  <Box ref={ anchorRef }>
40
38
  <AssignedTag
@@ -42,8 +40,7 @@ export const AssignedVariable = ( {
42
40
  startIcon={
43
41
  <>
44
42
  <ColorFilterIcon fontSize={ SIZE } />
45
-
46
- { additionalStartIcon }
43
+ <StartIcon value={ variable.value } />
47
44
  </>
48
45
  }
49
46
  onUnlink={ unlinkVariable }
@@ -62,7 +59,7 @@ export const AssignedVariable = ( {
62
59
  <VariableSelectionPopover
63
60
  selectedVariable={ variable }
64
61
  closePopover={ popupState.close }
65
- propTypeKey={ variablePropTypeUtil.key }
62
+ propTypeKey={ propTypeUtil.key }
66
63
  />
67
64
  </Popover>
68
65
  </Box>
@@ -1,20 +1,128 @@
1
1
  import * as React from 'react';
2
- import { useRef } from 'react';
3
- import { Box } from '@elementor/ui';
2
+ import { useId, useRef, useState } from 'react';
3
+ import { useBoundProp } from '@elementor/editor-controls';
4
+ import { type PropTypeKey } from '@elementor/editor-props';
5
+ import { Backdrop, bindPopover, Box, Infotip, Popover, usePopupState } from '@elementor/ui';
4
6
 
7
+ import { VariableTypeProvider } from '../../../context/variable-type-context';
8
+ import { usePermissions } from '../../../hooks/use-permissions';
9
+ import { restoreVariable } from '../../../hooks/use-prop-variables';
5
10
  import { type Variable } from '../../../types';
11
+ import { getVariableType } from '../../../variables-registry/variable-type-registry';
12
+ import { VariableRestore } from '../../variable-restore';
13
+ import { DeletedVariableAlert } from '../deleted-variable-alert';
6
14
  import { DeletedTag } from '../tags/deleted-tag';
7
15
 
8
16
  type Props = {
9
17
  variable: Variable;
18
+ propTypeKey: PropTypeKey;
10
19
  };
11
20
 
12
- export const DeletedVariable = ( { variable }: Props ) => {
13
- const anchorRef = useRef< HTMLDivElement >( null );
21
+ type Handlers = {
22
+ onUnlink?: () => void;
23
+ onRestore?: () => void;
24
+ };
25
+
26
+ export const DeletedVariable = ( { variable, propTypeKey }: Props ) => {
27
+ const { fallbackPropTypeUtil, propTypeUtil } = getVariableType( propTypeKey );
28
+
29
+ const { setValue } = useBoundProp();
30
+
31
+ const userPermissions = usePermissions();
32
+
33
+ const [ showInfotip, setShowInfotip ] = useState< boolean >( false );
34
+ const toggleInfotip = () => setShowInfotip( ( prev ) => ! prev );
35
+ const closeInfotip = () => setShowInfotip( false );
36
+
37
+ const deletedChipAnchorRef = useRef< HTMLDivElement >( null );
38
+
39
+ const popupId = useId();
40
+ const popupState = usePopupState( {
41
+ variant: 'popover',
42
+ popupId: `elementor-variables-restore-${ popupId }`,
43
+ } );
44
+
45
+ const handlers: Handlers = {};
46
+
47
+ if ( userPermissions.canUnlink() ) {
48
+ handlers.onUnlink = () => {
49
+ setValue( fallbackPropTypeUtil.create( variable.value ) );
50
+ };
51
+ }
52
+
53
+ if ( userPermissions.canRestore() ) {
54
+ handlers.onRestore = () => {
55
+ if ( ! variable.key ) {
56
+ return;
57
+ }
58
+
59
+ restoreVariable( variable.key )
60
+ .then( ( key ) => {
61
+ setValue( propTypeUtil.create( key ) );
62
+ closeInfotip();
63
+ } )
64
+ .catch( () => {
65
+ closeInfotip();
66
+ popupState.setAnchorEl( deletedChipAnchorRef.current );
67
+ popupState.open();
68
+ } );
69
+ };
70
+ }
71
+
72
+ const handleRestoreWithOverrides = () => {
73
+ popupState.close();
74
+ };
14
75
 
15
76
  return (
16
- <Box ref={ anchorRef }>
17
- <DeletedTag label={ variable.label } />
18
- </Box>
77
+ <>
78
+ <Box ref={ deletedChipAnchorRef }>
79
+ { showInfotip && <Backdrop open onClick={ closeInfotip } invisible /> }
80
+ <Infotip
81
+ color="warning"
82
+ placement="right-start"
83
+ open={ showInfotip }
84
+ disableHoverListener
85
+ onClose={ closeInfotip }
86
+ content={
87
+ <DeletedVariableAlert
88
+ onClose={ closeInfotip }
89
+ onUnlink={ handlers.onUnlink }
90
+ onRestore={ handlers.onRestore }
91
+ label={ variable.label }
92
+ />
93
+ }
94
+ slotProps={ {
95
+ popper: {
96
+ modifiers: [
97
+ {
98
+ name: 'offset',
99
+ options: { offset: [ 0, 24 ] },
100
+ },
101
+ ],
102
+ },
103
+ } }
104
+ >
105
+ <DeletedTag label={ variable.label } onClick={ toggleInfotip } />
106
+ </Infotip>
107
+
108
+ <Popover
109
+ disableScrollLock
110
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
111
+ transformOrigin={ { vertical: 'top', horizontal: 'right' } }
112
+ PaperProps={ {
113
+ sx: { my: 1 },
114
+ } }
115
+ { ...bindPopover( popupState ) }
116
+ >
117
+ <VariableTypeProvider propTypeKey={ propTypeKey }>
118
+ <VariableRestore
119
+ variableId={ variable.key ?? '' }
120
+ onClose={ popupState.close }
121
+ onSubmit={ handleRestoreWithOverrides }
122
+ />
123
+ </VariableTypeProvider>
124
+ </Popover>
125
+ </Box>
126
+ </>
19
127
  );
20
128
  };
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { useBoundProp } from '@elementor/editor-controls';
4
+ import { Backdrop, Infotip } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { MissingVariableAlert } from '../missing-variable-alert';
8
+ import { MissingTag } from '../tags/missing-tag';
9
+
10
+ export const MissingVariable = () => {
11
+ const { setValue } = useBoundProp();
12
+
13
+ const [ infotipVisible, setInfotipVisible ] = useState< boolean >( false );
14
+ const toggleInfotip = () => setInfotipVisible( ( prev ) => ! prev );
15
+ const closeInfotip = () => setInfotipVisible( false );
16
+
17
+ const clearValue = () => setValue( null );
18
+
19
+ return (
20
+ <>
21
+ { infotipVisible && <Backdrop open onClick={ closeInfotip } invisible /> }
22
+ <Infotip
23
+ color="warning"
24
+ placement="right-start"
25
+ open={ infotipVisible }
26
+ disableHoverListener
27
+ onClose={ closeInfotip }
28
+ content={ <MissingVariableAlert onClose={ closeInfotip } onClear={ clearValue } /> }
29
+ slotProps={ {
30
+ popper: {
31
+ modifiers: [
32
+ {
33
+ name: 'offset',
34
+ options: { offset: [ 0, 24 ] },
35
+ },
36
+ ],
37
+ },
38
+ } }
39
+ >
40
+ <MissingTag label={ __( 'Missing variable', 'elementor' ) } onClick={ toggleInfotip } />
41
+ </Infotip>
42
+ </>
43
+ );
44
+ };
@@ -0,0 +1,135 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { PopoverContent, useBoundProp } from '@elementor/editor-controls';
4
+ import { PopoverBody } from '@elementor/editor-editing-panel';
5
+ import { PopoverHeader } from '@elementor/editor-ui';
6
+ import { ArrowLeftIcon } from '@elementor/icons';
7
+ import { Button, CardActions, Divider, FormHelperText, IconButton } from '@elementor/ui';
8
+ import { __ } from '@wordpress/i18n';
9
+
10
+ import { useVariableType } from '../context/variable-type-context';
11
+ import { useInitialValue } from '../hooks/use-initial-value';
12
+ import { createVariable } from '../hooks/use-prop-variables';
13
+ import { trackVariableEvent } from '../utils/tracking';
14
+ import { ERROR_MESSAGES, mapServerError } from '../utils/validations';
15
+ import { LabelField, useLabelError } from './fields/label-field';
16
+
17
+ const SIZE = 'tiny';
18
+
19
+ type Props = {
20
+ onGoBack?: () => void;
21
+ onClose: () => void;
22
+ };
23
+
24
+ export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
25
+ const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
26
+
27
+ const { setValue: setVariable, path } = useBoundProp( propTypeUtil );
28
+
29
+ const initialValue = useInitialValue();
30
+
31
+ const [ value, setValue ] = useState( initialValue );
32
+ const [ label, setLabel ] = useState( '' );
33
+ const [ errorMessage, setErrorMessage ] = useState( '' );
34
+
35
+ const { labelFieldError, setLabelFieldError } = useLabelError();
36
+
37
+ const resetFields = () => {
38
+ setValue( '' );
39
+ setLabel( '' );
40
+ setErrorMessage( '' );
41
+ };
42
+
43
+ const closePopover = () => {
44
+ resetFields();
45
+ onClose();
46
+ };
47
+
48
+ const handleCreateAndTrack = () => {
49
+ createVariable( {
50
+ value,
51
+ label,
52
+ type: propTypeUtil.key,
53
+ } )
54
+ .then( ( key ) => {
55
+ setVariable( key );
56
+ closePopover();
57
+ } )
58
+ .catch( ( error ) => {
59
+ const mappedError = mapServerError( error );
60
+ if ( mappedError && 'label' === mappedError.field ) {
61
+ setLabel( '' );
62
+ setLabelFieldError( {
63
+ value: label,
64
+ message: mappedError.message,
65
+ } );
66
+ return;
67
+ }
68
+
69
+ setErrorMessage( ERROR_MESSAGES.UNEXPECTED_ERROR );
70
+ } );
71
+
72
+ trackVariableEvent( {
73
+ varType: variableType,
74
+ controlPath: path.join( '.' ),
75
+ action: 'save',
76
+ } );
77
+ };
78
+
79
+ const hasEmptyValue = () => {
80
+ return '' === value.trim() || '' === label.trim();
81
+ };
82
+
83
+ const hasErrors = () => {
84
+ return !! errorMessage;
85
+ };
86
+
87
+ const isSubmitDisabled = hasEmptyValue() || hasErrors();
88
+
89
+ return (
90
+ <PopoverBody height="auto">
91
+ <PopoverHeader
92
+ icon={
93
+ <>
94
+ { onGoBack && (
95
+ <IconButton size={ SIZE } aria-label={ __( 'Go Back', 'elementor' ) } onClick={ onGoBack }>
96
+ <ArrowLeftIcon fontSize={ SIZE } />
97
+ </IconButton>
98
+ ) }
99
+ <VariableIcon fontSize={ SIZE } />
100
+ </>
101
+ }
102
+ title={ __( 'Create variable', 'elementor' ) }
103
+ onClose={ closePopover }
104
+ />
105
+
106
+ <Divider />
107
+
108
+ <PopoverContent p={ 2 }>
109
+ <LabelField
110
+ value={ label }
111
+ error={ labelFieldError }
112
+ onChange={ ( newValue ) => {
113
+ setLabel( newValue );
114
+ setErrorMessage( '' );
115
+ } }
116
+ />
117
+ <ValueField
118
+ value={ value }
119
+ onChange={ ( newValue ) => {
120
+ setValue( newValue );
121
+ setErrorMessage( '' );
122
+ } }
123
+ />
124
+
125
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
126
+ </PopoverContent>
127
+
128
+ <CardActions sx={ { pt: 0.5, pb: 1 } }>
129
+ <Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleCreateAndTrack }>
130
+ { __( 'Create', 'elementor' ) }
131
+ </Button>
132
+ </CardActions>
133
+ </PopoverBody>
134
+ );
135
+ };
@@ -0,0 +1,221 @@
1
+ import * as React from 'react';
2
+ import { useEffect, useState } from 'react';
3
+ import { PopoverContent, useBoundProp } from '@elementor/editor-controls';
4
+ import { useSuppressedMessage } from '@elementor/editor-current-user';
5
+ import { PopoverBody } from '@elementor/editor-editing-panel';
6
+ import { PopoverHeader } from '@elementor/editor-ui';
7
+ import { ArrowLeftIcon, TrashIcon } from '@elementor/icons';
8
+ import { Button, CardActions, Divider, FormHelperText, IconButton } from '@elementor/ui';
9
+ import { __ } from '@wordpress/i18n';
10
+
11
+ import { useVariableType } from '../context/variable-type-context';
12
+ import { usePermissions } from '../hooks/use-permissions';
13
+ import { deleteVariable, updateVariable, useVariable } from '../hooks/use-prop-variables';
14
+ import { styleVariablesRepository } from '../style-variables-repository';
15
+ import { ERROR_MESSAGES, mapServerError } from '../utils/validations';
16
+ import { LabelField, useLabelError } from './fields/label-field';
17
+ import { DeleteConfirmationDialog } from './ui/delete-confirmation-dialog';
18
+ import { EDIT_CONFIRMATION_DIALOG_ID, EditConfirmationDialog } from './ui/edit-confirmation-dialog';
19
+
20
+ const SIZE = 'tiny';
21
+
22
+ type Props = {
23
+ editId: string;
24
+ onClose: () => void;
25
+ onGoBack?: () => void;
26
+ onSubmit?: () => void;
27
+ };
28
+
29
+ export const VariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) => {
30
+ const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
31
+
32
+ const { setValue: notifyBoundPropChange, value: assignedValue } = useBoundProp( propTypeUtil );
33
+ const [ isMessageSuppressed, suppressMessage ] = useSuppressedMessage( EDIT_CONFIRMATION_DIALOG_ID );
34
+ const [ deleteConfirmation, setDeleteConfirmation ] = useState( false );
35
+ const [ editConfirmation, setEditConfirmation ] = useState( false );
36
+ const [ errorMessage, setErrorMessage ] = useState( '' );
37
+
38
+ const { labelFieldError, setLabelFieldError } = useLabelError();
39
+ const variable = useVariable( editId );
40
+
41
+ if ( ! variable ) {
42
+ throw new Error( `Global ${ variableType } variable not found` );
43
+ }
44
+
45
+ const userPermissions = usePermissions();
46
+
47
+ const [ value, setValue ] = useState( variable.value );
48
+ const [ label, setLabel ] = useState( variable.label );
49
+
50
+ useEffect( () => {
51
+ styleVariablesRepository.update( {
52
+ [ editId ]: {
53
+ ...variable,
54
+ value,
55
+ },
56
+ } );
57
+
58
+ return () => {
59
+ styleVariablesRepository.update( {
60
+ [ editId ]: { ...variable },
61
+ } );
62
+ };
63
+ }, [ editId, value, variable ] );
64
+
65
+ const handleUpdate = () => {
66
+ if ( isMessageSuppressed ) {
67
+ handleSaveVariable();
68
+ } else {
69
+ setEditConfirmation( true );
70
+ }
71
+ };
72
+
73
+ const handleSaveVariable = () => {
74
+ updateVariable( editId, {
75
+ value,
76
+ label,
77
+ } )
78
+ .then( () => {
79
+ maybeTriggerBoundPropChange();
80
+ onSubmit?.();
81
+ } )
82
+ .catch( ( error ) => {
83
+ const mappedError = mapServerError( error );
84
+ if ( mappedError && 'label' === mappedError.field ) {
85
+ setLabel( '' );
86
+ setLabelFieldError( {
87
+ value: label,
88
+ message: mappedError.message,
89
+ } );
90
+ return;
91
+ }
92
+
93
+ setErrorMessage( ERROR_MESSAGES.UNEXPECTED_ERROR );
94
+ } );
95
+ };
96
+
97
+ const handleDelete = () => {
98
+ deleteVariable( editId ).then( () => {
99
+ maybeTriggerBoundPropChange();
100
+ onSubmit?.();
101
+ } );
102
+ };
103
+
104
+ const maybeTriggerBoundPropChange = () => {
105
+ if ( editId === assignedValue ) {
106
+ notifyBoundPropChange( editId );
107
+ }
108
+ };
109
+
110
+ const handleDeleteConfirmation = () => {
111
+ setDeleteConfirmation( true );
112
+ };
113
+
114
+ const closeDeleteDialog = () => () => {
115
+ setDeleteConfirmation( false );
116
+ };
117
+
118
+ const closeEditDialog = () => () => {
119
+ setEditConfirmation( false );
120
+ };
121
+
122
+ const actions = [];
123
+
124
+ if ( userPermissions.canDelete() ) {
125
+ actions.push(
126
+ <IconButton
127
+ key="delete"
128
+ size={ SIZE }
129
+ aria-label={ __( 'Delete', 'elementor' ) }
130
+ onClick={ handleDeleteConfirmation }
131
+ >
132
+ <TrashIcon fontSize={ SIZE } />
133
+ </IconButton>
134
+ );
135
+ }
136
+
137
+ const hasEmptyValues = () => {
138
+ return ! value.trim() || ! label.trim();
139
+ };
140
+
141
+ const noValueChanged = () => {
142
+ return value === variable.value && label === variable.label;
143
+ };
144
+
145
+ const hasErrors = () => {
146
+ return !! errorMessage;
147
+ };
148
+
149
+ const isSubmitDisabled = noValueChanged() || hasEmptyValues() || hasErrors();
150
+
151
+ return (
152
+ <>
153
+ <PopoverBody height="auto">
154
+ <PopoverHeader
155
+ title={ __( 'Edit variable', 'elementor' ) }
156
+ onClose={ onClose }
157
+ icon={
158
+ <>
159
+ { onGoBack && (
160
+ <IconButton
161
+ size={ SIZE }
162
+ aria-label={ __( 'Go Back', 'elementor' ) }
163
+ onClick={ onGoBack }
164
+ >
165
+ <ArrowLeftIcon fontSize={ SIZE } />
166
+ </IconButton>
167
+ ) }
168
+ <VariableIcon fontSize={ SIZE } />
169
+ </>
170
+ }
171
+ actions={ actions }
172
+ />
173
+
174
+ <Divider />
175
+
176
+ <PopoverContent p={ 2 }>
177
+ <LabelField
178
+ value={ label }
179
+ error={ labelFieldError }
180
+ onChange={ ( newValue ) => {
181
+ setLabel( newValue );
182
+ setErrorMessage( '' );
183
+ } }
184
+ />
185
+ <ValueField
186
+ value={ value }
187
+ onChange={ ( newValue ) => {
188
+ setValue( newValue );
189
+ setErrorMessage( '' );
190
+ } }
191
+ />
192
+
193
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
194
+ </PopoverContent>
195
+
196
+ <CardActions sx={ { pt: 0.5, pb: 1 } }>
197
+ <Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleUpdate }>
198
+ { __( 'Save', 'elementor' ) }
199
+ </Button>
200
+ </CardActions>
201
+ </PopoverBody>
202
+
203
+ { deleteConfirmation && (
204
+ <DeleteConfirmationDialog
205
+ open
206
+ label={ label }
207
+ onConfirm={ handleDelete }
208
+ closeDialog={ closeDeleteDialog() }
209
+ />
210
+ ) }
211
+
212
+ { editConfirmation && ! isMessageSuppressed && (
213
+ <EditConfirmationDialog
214
+ closeDialog={ closeEditDialog() }
215
+ onConfirm={ handleSaveVariable }
216
+ onSuppressMessage={ suppressMessage }
217
+ />
218
+ ) }
219
+ </>
220
+ );
221
+ };