@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.
- package/CHANGELOG.md +22 -0
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +1397 -885
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1399 -871
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -14
- package/src/api.ts +18 -2
- package/src/components/fields/color-field.tsx +3 -3
- package/src/components/fields/font-field.tsx +21 -10
- package/src/components/fields/label-field.tsx +31 -5
- package/src/components/ui/delete-confirmation-dialog.tsx +4 -7
- package/src/components/ui/deleted-variable-alert.tsx +47 -0
- package/src/components/ui/edit-confirmation-dialog.tsx +75 -0
- package/src/components/ui/missing-variable-alert.tsx +39 -0
- package/src/components/ui/no-variables.tsx +59 -26
- package/src/components/ui/tags/assigned-tag.tsx +21 -19
- package/src/components/ui/tags/deleted-tag.tsx +29 -18
- package/src/components/ui/tags/missing-tag.tsx +25 -0
- package/src/components/ui/variable/assigned-variable.tsx +11 -14
- package/src/components/ui/variable/deleted-variable.tsx +115 -7
- package/src/components/ui/variable/missing-variable.tsx +44 -0
- package/src/components/variable-creation.tsx +135 -0
- package/src/components/variable-edit.tsx +221 -0
- package/src/components/variable-restore.tsx +117 -0
- package/src/components/variable-selection-popover.tsx +91 -92
- package/src/components/variables-manager/variables-manager-panel.tsx +115 -0
- package/src/components/variables-selection.tsx +148 -0
- package/src/context/variable-selection-popover.context.tsx +19 -0
- package/src/context/variable-type-context.tsx +23 -0
- package/src/controls/variable-control.tsx +26 -0
- package/src/create-style-variables-repository.ts +44 -5
- package/src/hooks/use-initial-value.ts +22 -0
- package/src/hooks/use-permissions.ts +15 -0
- package/src/hooks/use-prop-variable-action.tsx +53 -0
- package/src/hooks/use-prop-variables.ts +6 -0
- package/src/index.ts +1 -0
- package/src/init.ts +33 -4
- package/src/register-variable-types.tsx +29 -0
- package/src/renderers/style-variables-renderer.tsx +10 -4
- package/src/repeater-injections.ts +5 -1
- package/src/service.ts +8 -4
- package/src/sync/enqueue-font.ts +7 -0
- package/src/sync/types.ts +5 -0
- package/src/transformers/inheritance-transformer.tsx +30 -0
- package/src/transformers/utils/resolve-css-variable.ts +24 -0
- package/src/transformers/variable-transformer.ts +8 -3
- package/src/types.ts +1 -1
- package/src/utils/tracking.ts +39 -0
- package/src/utils/validations.ts +40 -6
- package/src/variables-registry/create-variable-type-registry.ts +77 -0
- package/src/variables-registry/variable-type-registry.ts +3 -0
- package/src/components/color-variable-creation.tsx +0 -86
- package/src/components/color-variable-edit.tsx +0 -138
- package/src/components/color-variables-selection.tsx +0 -130
- package/src/components/font-variable-creation.tsx +0 -86
- package/src/components/font-variable-edit.tsx +0 -138
- package/src/components/font-variables-selection.tsx +0 -129
- package/src/components/variable-selection-popover.context.ts +0 -7
- package/src/controls/color-variable-control.tsx +0 -33
- package/src/controls/font-variable-control.tsx +0 -31
- package/src/hooks/use-prop-color-variable-action.tsx +0 -25
- package/src/hooks/use-prop-font-variable-action.tsx +0 -25
- package/src/init-color-variables.ts +0 -27
- package/src/init-font-variables.ts +0 -24
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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 {
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
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
|
+
};
|