@elementor/editor-variables 0.16.0 → 0.18.0
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 +50 -0
- package/dist/index.js +392 -136
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +400 -144
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/components/color-variable-creation.tsx +30 -10
- package/src/components/color-variable-edit.tsx +29 -10
- package/src/components/color-variables-selection.tsx +32 -34
- package/src/components/font-variable-creation.tsx +30 -10
- package/src/components/font-variable-edit.tsx +29 -10
- package/src/components/font-variables-selection.tsx +32 -34
- package/src/components/ui/delete-confirmation-dialog.tsx +4 -7
- package/src/components/ui/deleted-variable-alert.tsx +47 -0
- package/src/components/ui/tags/assigned-tag.tsx +21 -19
- package/src/components/ui/tags/deleted-tag.tsx +29 -18
- package/src/components/ui/variable/deleted-variable.tsx +63 -7
- package/src/controls/color-variable-control.tsx +7 -1
- package/src/controls/font-variable-control.tsx +7 -1
- package/src/create-style-variables-repository.ts +44 -5
- package/src/hooks/use-prop-variables.ts +6 -0
- package/src/renderers/style-variables-renderer.tsx +10 -4
- package/src/service.ts +23 -2
- package/src/sync/enqueue-font.ts +7 -0
- package/src/sync/types.ts +5 -0
- package/src/transformers/variable-transformer.ts +21 -3
- package/src/types.ts +1 -1
- package/src/utils/validations.ts +5 -5
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { Box,
|
|
2
|
+
import { AlertTriangleFilledIcon } from '@elementor/icons';
|
|
3
|
+
import { Box, Chip, type ChipProps, type Theme, Tooltip, Typography } from '@elementor/ui';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
|
-
export const DeletedTag = ( { label }
|
|
6
|
+
export const DeletedTag = React.forwardRef< HTMLDivElement, ChipProps >( ( { label, onClick, ...props }, ref ) => {
|
|
7
7
|
return (
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<Chip
|
|
9
|
+
ref={ ref }
|
|
10
|
+
size="tiny"
|
|
11
|
+
color="warning"
|
|
12
|
+
shape="rounded"
|
|
13
|
+
variant="standard"
|
|
14
|
+
onClick={ onClick }
|
|
15
|
+
icon={ <AlertTriangleFilledIcon /> }
|
|
11
16
|
label={
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</Typography>
|
|
17
|
+
<Tooltip title={ label } placement="top">
|
|
18
|
+
<Box sx={ { display: 'flex', gap: 0.5, alignItems: 'center' } }>
|
|
19
|
+
<Typography variant="caption" noWrap>
|
|
20
|
+
{ label }
|
|
21
|
+
</Typography>
|
|
22
|
+
<Typography variant="caption" noWrap sx={ { textOverflow: 'initial', overflow: 'visible' } }>
|
|
23
|
+
({ __( 'deleted', 'elementor' ) })
|
|
24
|
+
</Typography>
|
|
25
|
+
</Box>
|
|
26
|
+
</Tooltip>
|
|
23
27
|
}
|
|
28
|
+
sx={ {
|
|
29
|
+
height: ( theme: Theme ) => theme.spacing( 3.5 ),
|
|
30
|
+
borderRadius: ( theme: Theme ) => theme.spacing( 1 ),
|
|
31
|
+
justifyContent: 'flex-start',
|
|
32
|
+
width: '100%',
|
|
33
|
+
} }
|
|
34
|
+
{ ...props }
|
|
24
35
|
/>
|
|
25
36
|
);
|
|
26
|
-
};
|
|
37
|
+
} );
|
|
@@ -1,20 +1,76 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useBoundProp } from '@elementor/editor-controls';
|
|
4
|
+
import { type PropTypeUtil } from '@elementor/editor-props';
|
|
5
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
6
|
+
import { Backdrop, Infotip } from '@elementor/ui';
|
|
4
7
|
|
|
8
|
+
import { restoreVariable } from '../../../hooks/use-prop-variables';
|
|
5
9
|
import { type Variable } from '../../../types';
|
|
10
|
+
import { DeletedVariableAlert } from '../deleted-variable-alert';
|
|
6
11
|
import { DeletedTag } from '../tags/deleted-tag';
|
|
7
12
|
|
|
13
|
+
const isV331Active = isExperimentActive( 'e_v_3_31' );
|
|
14
|
+
|
|
8
15
|
type Props = {
|
|
9
16
|
variable: Variable;
|
|
17
|
+
variablePropTypeUtil: PropTypeUtil< string, string >;
|
|
18
|
+
fallbackPropTypeUtil: PropTypeUtil< string, string | null > | PropTypeUtil< string, string >;
|
|
10
19
|
};
|
|
11
20
|
|
|
12
|
-
export const DeletedVariable = ( { variable }: Props ) => {
|
|
13
|
-
const
|
|
21
|
+
export const DeletedVariable = ( { variable, variablePropTypeUtil, fallbackPropTypeUtil }: Props ) => {
|
|
22
|
+
const { setValue } = useBoundProp();
|
|
23
|
+
const [ showInfotip, setShowInfotip ] = useState< boolean >( false );
|
|
24
|
+
|
|
25
|
+
const toggleInfotip = () => setShowInfotip( ( prev ) => ! prev );
|
|
26
|
+
|
|
27
|
+
const closeInfotip = () => setShowInfotip( false );
|
|
28
|
+
|
|
29
|
+
const unlinkVariable = () => {
|
|
30
|
+
setValue( fallbackPropTypeUtil.create( variable.value ) );
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleRestore = () => {
|
|
34
|
+
if ( ! variable.key ) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
restoreVariable( variable.key ).then( ( key ) => {
|
|
39
|
+
setValue( variablePropTypeUtil.create( key ) );
|
|
40
|
+
closeInfotip();
|
|
41
|
+
} );
|
|
42
|
+
};
|
|
14
43
|
|
|
15
44
|
return (
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
45
|
+
<>
|
|
46
|
+
{ showInfotip && <Backdrop open onClick={ closeInfotip } invisible /> }
|
|
47
|
+
<Infotip
|
|
48
|
+
color="warning"
|
|
49
|
+
placement="right-start"
|
|
50
|
+
open={ showInfotip }
|
|
51
|
+
disableHoverListener
|
|
52
|
+
onClose={ closeInfotip }
|
|
53
|
+
content={
|
|
54
|
+
<DeletedVariableAlert
|
|
55
|
+
onClose={ closeInfotip }
|
|
56
|
+
onUnlink={ unlinkVariable }
|
|
57
|
+
onRestore={ isV331Active ? handleRestore : undefined }
|
|
58
|
+
label={ variable.label }
|
|
59
|
+
/>
|
|
60
|
+
}
|
|
61
|
+
slotProps={ {
|
|
62
|
+
popper: {
|
|
63
|
+
modifiers: [
|
|
64
|
+
{
|
|
65
|
+
name: 'offset',
|
|
66
|
+
options: { offset: [ 0, 24 ] },
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
} }
|
|
71
|
+
>
|
|
72
|
+
<DeletedTag label={ variable.label } onClick={ toggleInfotip } />
|
|
73
|
+
</Infotip>
|
|
74
|
+
</>
|
|
19
75
|
);
|
|
20
76
|
};
|
|
@@ -19,7 +19,13 @@ export const ColorVariableControl = () => {
|
|
|
19
19
|
const isVariableDeleted = assignedVariable?.deleted;
|
|
20
20
|
|
|
21
21
|
if ( isVariableDeleted ) {
|
|
22
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
<DeletedVariable
|
|
24
|
+
variable={ assignedVariable }
|
|
25
|
+
variablePropTypeUtil={ colorVariablePropTypeUtil }
|
|
26
|
+
fallbackPropTypeUtil={ colorPropTypeUtil }
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
return (
|
|
@@ -18,7 +18,13 @@ export const FontVariableControl = () => {
|
|
|
18
18
|
const isVariableDeleted = assignedVariable?.deleted;
|
|
19
19
|
|
|
20
20
|
if ( isVariableDeleted ) {
|
|
21
|
-
return
|
|
21
|
+
return (
|
|
22
|
+
<DeletedVariable
|
|
23
|
+
variable={ assignedVariable }
|
|
24
|
+
variablePropTypeUtil={ fontVariablePropTypeUtil }
|
|
25
|
+
fallbackPropTypeUtil={ stringPropTypeUtil }
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
return (
|
|
@@ -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,
|
|
25
|
-
|
|
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,
|
|
32
|
-
if ( shouldUpdate( key,
|
|
33
|
-
variables[ key ] =
|
|
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();
|
|
@@ -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
|
-
|
|
54
|
-
|
|
55
|
-
.join( '' );
|
|
60
|
+
const listOfVariables = Object.entries( variables );
|
|
61
|
+
return listOfVariables.map( ( [ key, variable ] ) => cssVariableDeclaration( key, variable ) ).join( '' );
|
|
56
62
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { type AxiosResponse } from '@elementor/http-client';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
|
|
1
4
|
import { apiClient } from './api';
|
|
2
5
|
import { OP_RW, Storage, type TVariablesList } from './storage';
|
|
3
6
|
import { styleVariablesRepository } from './style-variables-repository';
|
|
@@ -44,7 +47,8 @@ export const service = {
|
|
|
44
47
|
const { success, data: payload } = response.data;
|
|
45
48
|
|
|
46
49
|
if ( ! success ) {
|
|
47
|
-
|
|
50
|
+
const errorMessage = payload?.message || __( 'Unexpected response from server', 'elementor' );
|
|
51
|
+
throw new Error( errorMessage );
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
return payload;
|
|
@@ -66,6 +70,10 @@ export const service = {
|
|
|
66
70
|
id: variableId,
|
|
67
71
|
variable: createdVariable,
|
|
68
72
|
};
|
|
73
|
+
} )
|
|
74
|
+
.catch( ( error ) => {
|
|
75
|
+
const message = getErrorMessage( error.response );
|
|
76
|
+
throw message ? new Error( message ) : error;
|
|
69
77
|
} );
|
|
70
78
|
},
|
|
71
79
|
|
|
@@ -76,7 +84,8 @@ export const service = {
|
|
|
76
84
|
const { success, data: payload } = response.data;
|
|
77
85
|
|
|
78
86
|
if ( ! success ) {
|
|
79
|
-
|
|
87
|
+
const errorMessage = payload?.message || __( 'Unexpected response from server', 'elementor' );
|
|
88
|
+
throw new Error( errorMessage );
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
return payload;
|
|
@@ -98,6 +107,10 @@ export const service = {
|
|
|
98
107
|
id: variableId,
|
|
99
108
|
variable: updatedVariable,
|
|
100
109
|
};
|
|
110
|
+
} )
|
|
111
|
+
.catch( ( error ) => {
|
|
112
|
+
const message = getErrorMessage( error.response );
|
|
113
|
+
throw message ? new Error( message ) : error;
|
|
101
114
|
} );
|
|
102
115
|
},
|
|
103
116
|
|
|
@@ -172,3 +185,11 @@ const handleWatermark = ( operation: string, newWatermark: number ) => {
|
|
|
172
185
|
}
|
|
173
186
|
storage.watermark( newWatermark );
|
|
174
187
|
};
|
|
188
|
+
|
|
189
|
+
const getErrorMessage = ( response: AxiosResponse ) => {
|
|
190
|
+
if ( response?.data?.code === 'duplicated_label' ) {
|
|
191
|
+
return __( 'This variable name already exists. Please choose a unique name.', 'elementor' );
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return __( 'There was a glitch. Try saving your variable again.', 'elementor' );
|
|
195
|
+
};
|
|
@@ -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,9 +1,27 @@
|
|
|
1
1
|
import { createTransformer } from '@elementor/editor-canvas';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { service } from '../service';
|
|
4
|
+
|
|
5
|
+
export const variableTransformer = createTransformer( ( id: string ) => {
|
|
6
|
+
const variables = service.variables();
|
|
7
|
+
|
|
8
|
+
let name = id;
|
|
9
|
+
let fallbackValue = '';
|
|
10
|
+
|
|
11
|
+
if ( variables[ id ] ) {
|
|
12
|
+
fallbackValue = variables[ id ].value;
|
|
13
|
+
if ( ! variables[ id ]?.deleted ) {
|
|
14
|
+
name = variables[ id ].label;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if ( ! name.trim() ) {
|
|
5
19
|
return null;
|
|
6
20
|
}
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
if ( ! fallbackValue.trim() ) {
|
|
23
|
+
return `var(--${ name })`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `var(--${ name }, ${ fallbackValue })`;
|
|
9
27
|
} );
|
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,
|
|
13
|
+
export type StyleVariables = Record< string, Variable >;
|
|
14
14
|
|
|
15
15
|
export type ExtendedVirtualizedItem = VirtualizedItem< 'item', string > & {
|
|
16
16
|
icon: React.ReactNode;
|
package/src/utils/validations.ts
CHANGED
|
@@ -4,12 +4,12 @@ export const VARIABLE_LABEL_MAX_LENGTH = 50;
|
|
|
4
4
|
|
|
5
5
|
export const validateLabel = ( name: string ): string => {
|
|
6
6
|
if ( ! name.trim() ) {
|
|
7
|
-
return __( '
|
|
7
|
+
return __( 'Give your variable a name.', 'elementor' );
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const allowedChars = /^[a-zA-Z0-9_-]+$/;
|
|
11
11
|
if ( ! allowedChars.test( name ) ) {
|
|
12
|
-
return __( '
|
|
12
|
+
return __( 'Use letters, numbers, dashes (-), or underscores (_) for the name.', 'elementor' );
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const hasAlphanumeric = /[a-zA-Z0-9]/;
|
|
@@ -18,7 +18,7 @@ export const validateLabel = ( name: string ): string => {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if ( VARIABLE_LABEL_MAX_LENGTH < name.length ) {
|
|
21
|
-
return __( '
|
|
21
|
+
return __( 'Keep names up to 50 characters.', 'elementor' );
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return '';
|
|
@@ -27,7 +27,7 @@ export const validateLabel = ( name: string ): string => {
|
|
|
27
27
|
export const labelHint = ( name: string ): string => {
|
|
28
28
|
const hintThreshold = VARIABLE_LABEL_MAX_LENGTH * 0.8 - 1;
|
|
29
29
|
if ( hintThreshold < name.length ) {
|
|
30
|
-
return __( '
|
|
30
|
+
return __( 'Keep names up to 50 characters.', 'elementor' );
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return '';
|
|
@@ -35,7 +35,7 @@ export const labelHint = ( name: string ): string => {
|
|
|
35
35
|
|
|
36
36
|
export const validateValue = ( value: string ): string => {
|
|
37
37
|
if ( ! value.trim() ) {
|
|
38
|
-
return __( '
|
|
38
|
+
return __( 'Add a value to complete your variable.', 'elementor' );
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return '';
|