@elementor/editor-variables 0.18.0 → 3.32.0-21
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 +0 -28
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +1282 -1026
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1262 -990
- 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/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/missing-tag.tsx +25 -0
- package/src/components/ui/variable/assigned-variable.tsx +11 -14
- package/src/components/ui/variable/deleted-variable.tsx +102 -50
- package/src/components/ui/variable/missing-variable.tsx +44 -0
- package/src/components/{color-variable-creation.tsx → variable-creation.tsx} +51 -22
- 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/{font-variables-selection.tsx → variables-selection.tsx} +38 -17
- 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/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 +2 -2
- package/src/index.ts +1 -0
- package/src/init.ts +33 -4
- package/src/register-variable-types.tsx +29 -0
- package/src/repeater-injections.ts +5 -1
- package/src/service.ts +2 -19
- package/src/transformers/inheritance-transformer.tsx +30 -0
- package/src/transformers/utils/resolve-css-variable.ts +24 -0
- package/src/transformers/variable-transformer.ts +3 -16
- 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-edit.tsx +0 -157
- package/src/components/color-variables-selection.tsx +0 -128
- package/src/components/font-variable-creation.tsx +0 -106
- package/src/components/font-variable-edit.tsx +0 -157
- package/src/components/variable-selection-popover.context.ts +0 -7
- package/src/controls/color-variable-control.tsx +0 -39
- package/src/controls/font-variable-control.tsx +0 -37
- 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,117 @@
|
|
|
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 { Button, CardActions, Divider, FormHelperText } from '@elementor/ui';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
|
|
9
|
+
import { PopoverContentRefContextProvider } from '../context/variable-selection-popover.context';
|
|
10
|
+
import { useVariableType } from '../context/variable-type-context';
|
|
11
|
+
import { restoreVariable, useVariable } from '../hooks/use-prop-variables';
|
|
12
|
+
import { ERROR_MESSAGES, mapServerError } from '../utils/validations';
|
|
13
|
+
import { LabelField, useLabelError } from './fields/label-field';
|
|
14
|
+
|
|
15
|
+
const SIZE = 'tiny';
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
variableId: string;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
onSubmit?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const VariableRestore = ( { variableId, onClose, onSubmit }: Props ) => {
|
|
24
|
+
const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
|
|
25
|
+
|
|
26
|
+
const { setValue: notifyBoundPropChange } = useBoundProp( propTypeUtil );
|
|
27
|
+
|
|
28
|
+
const variable = useVariable( variableId );
|
|
29
|
+
|
|
30
|
+
if ( ! variable ) {
|
|
31
|
+
throw new Error( `Global ${ variableType } variable not found` );
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [ errorMessage, setErrorMessage ] = useState( '' );
|
|
35
|
+
const [ label, setLabel ] = useState( variable.label );
|
|
36
|
+
const [ value, setValue ] = useState( variable.value );
|
|
37
|
+
|
|
38
|
+
const { labelFieldError, setLabelFieldError } = useLabelError( {
|
|
39
|
+
value: variable.label,
|
|
40
|
+
message: ERROR_MESSAGES.DUPLICATED_LABEL,
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
const handleRestore = () => {
|
|
44
|
+
restoreVariable( variableId, label, value )
|
|
45
|
+
.then( () => {
|
|
46
|
+
notifyBoundPropChange( variableId );
|
|
47
|
+
onSubmit?.();
|
|
48
|
+
} )
|
|
49
|
+
.catch( ( error ) => {
|
|
50
|
+
const mappedError = mapServerError( error );
|
|
51
|
+
if ( mappedError && 'label' === mappedError.field ) {
|
|
52
|
+
setLabel( '' );
|
|
53
|
+
setLabelFieldError( {
|
|
54
|
+
value: label,
|
|
55
|
+
message: mappedError.message,
|
|
56
|
+
} );
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setErrorMessage( ERROR_MESSAGES.UNEXPECTED_ERROR );
|
|
61
|
+
} );
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const hasEmptyValues = () => {
|
|
65
|
+
return ! value.trim() || ! label.trim();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const noValueChanged = () => {
|
|
69
|
+
return value === variable.value && label === variable.label;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const hasErrors = () => {
|
|
73
|
+
return !! errorMessage;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const isSubmitDisabled = noValueChanged() || hasEmptyValues() || hasErrors();
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<PopoverContentRefContextProvider>
|
|
80
|
+
<PopoverBody height="auto">
|
|
81
|
+
<PopoverHeader
|
|
82
|
+
icon={ <VariableIcon fontSize={ SIZE } /> }
|
|
83
|
+
title={ __( 'Restore variable', 'elementor' ) }
|
|
84
|
+
onClose={ onClose }
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
<Divider />
|
|
88
|
+
|
|
89
|
+
<PopoverContent p={ 2 }>
|
|
90
|
+
<LabelField
|
|
91
|
+
value={ label }
|
|
92
|
+
error={ labelFieldError }
|
|
93
|
+
onChange={ ( newValue ) => {
|
|
94
|
+
setLabel( newValue );
|
|
95
|
+
setErrorMessage( '' );
|
|
96
|
+
} }
|
|
97
|
+
/>
|
|
98
|
+
<ValueField
|
|
99
|
+
value={ value }
|
|
100
|
+
onChange={ ( newValue ) => {
|
|
101
|
+
setValue( newValue );
|
|
102
|
+
setErrorMessage( '' );
|
|
103
|
+
} }
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
{ errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
|
|
107
|
+
</PopoverContent>
|
|
108
|
+
|
|
109
|
+
<CardActions sx={ { pt: 0.5, pb: 1 } }>
|
|
110
|
+
<Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleRestore }>
|
|
111
|
+
{ __( 'Restore', 'elementor' ) }
|
|
112
|
+
</Button>
|
|
113
|
+
</CardActions>
|
|
114
|
+
</PopoverBody>
|
|
115
|
+
</PopoverContentRefContextProvider>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import type { PropTypeKey } from '@elementor/editor-props';
|
|
4
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { PopoverContentRefContextProvider } from '../context/variable-selection-popover.context';
|
|
7
|
+
import { VariableTypeProvider } from '../context/variable-type-context';
|
|
8
|
+
import { usePermissions } from '../hooks/use-permissions';
|
|
7
9
|
import { type Variable } from '../types';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { FontVariableEdit } from './font-variable-edit';
|
|
13
|
-
import { FontVariablesSelection } from './font-variables-selection';
|
|
14
|
-
import { PopoverContentRefContext } from './variable-selection-popover.context';
|
|
10
|
+
import { VariableCreation } from './variable-creation';
|
|
11
|
+
import { VariableEdit } from './variable-edit';
|
|
12
|
+
import { usePanelActions } from './variables-manager/variables-manager-panel';
|
|
13
|
+
import { VariablesSelection } from './variables-selection';
|
|
15
14
|
|
|
16
15
|
const VIEW_LIST = 'list';
|
|
17
16
|
const VIEW_ADD = 'add';
|
|
@@ -21,121 +20,121 @@ type View = typeof VIEW_LIST | typeof VIEW_ADD | typeof VIEW_EDIT;
|
|
|
21
20
|
|
|
22
21
|
type Props = {
|
|
23
22
|
closePopover: () => void;
|
|
24
|
-
propTypeKey: string;
|
|
25
23
|
selectedVariable?: Variable;
|
|
24
|
+
propTypeKey: PropTypeKey;
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
export const VariableSelectionPopover = ( { closePopover, propTypeKey, selectedVariable }: Props ) => {
|
|
29
28
|
const [ currentView, setCurrentView ] = useState< View >( VIEW_LIST );
|
|
30
|
-
const
|
|
31
|
-
const
|
|
29
|
+
const [ editId, setEditId ] = useState< string >( '' );
|
|
30
|
+
const { open } = usePanelActions();
|
|
31
|
+
const onSettingsAvailable = isExperimentActive( 'e_variables_settings' )
|
|
32
|
+
? () => {
|
|
33
|
+
open();
|
|
34
|
+
}
|
|
35
|
+
: undefined;
|
|
32
36
|
|
|
33
37
|
return (
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
{
|
|
38
|
+
<VariableTypeProvider propTypeKey={ propTypeKey }>
|
|
39
|
+
<PopoverContentRefContextProvider>
|
|
40
|
+
{ RenderView( {
|
|
37
41
|
propTypeKey,
|
|
38
42
|
currentView,
|
|
39
43
|
selectedVariable,
|
|
40
|
-
|
|
44
|
+
editId,
|
|
45
|
+
setEditId,
|
|
41
46
|
setCurrentView,
|
|
42
47
|
closePopover,
|
|
48
|
+
onSettings: onSettingsAvailable,
|
|
43
49
|
} ) }
|
|
44
|
-
</
|
|
45
|
-
</
|
|
50
|
+
</PopoverContentRefContextProvider>
|
|
51
|
+
</VariableTypeProvider>
|
|
46
52
|
);
|
|
47
53
|
};
|
|
48
54
|
|
|
49
|
-
type
|
|
55
|
+
type ViewProps = {
|
|
50
56
|
propTypeKey: string;
|
|
51
57
|
currentView: View;
|
|
52
58
|
selectedVariable?: Variable;
|
|
53
|
-
|
|
59
|
+
editId: string;
|
|
60
|
+
setEditId: ( id: string ) => void;
|
|
54
61
|
setCurrentView: ( stage: View ) => void;
|
|
55
62
|
closePopover: () => void;
|
|
63
|
+
onSettings?: () => void;
|
|
56
64
|
};
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
type Handlers = {
|
|
67
|
+
onClose: () => void;
|
|
68
|
+
onGoBack?: () => void;
|
|
69
|
+
onAdd?: () => void;
|
|
70
|
+
onEdit?: ( key: string ) => void;
|
|
71
|
+
onSettings?: () => void;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function RenderView( props: ViewProps ): React.ReactNode {
|
|
75
|
+
const userPermissions = usePermissions();
|
|
76
|
+
|
|
77
|
+
const handlers: Handlers = {
|
|
78
|
+
onClose: () => {
|
|
61
79
|
props.closePopover();
|
|
62
|
-
}
|
|
80
|
+
},
|
|
81
|
+
onGoBack: () => {
|
|
63
82
|
props.setCurrentView( VIEW_LIST );
|
|
64
|
-
}
|
|
83
|
+
},
|
|
65
84
|
};
|
|
66
85
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
onAdd={ () => {
|
|
73
|
-
props.setCurrentView( VIEW_ADD );
|
|
74
|
-
} }
|
|
75
|
-
onEdit={ ( key ) => {
|
|
76
|
-
props.editIdRef.current = key;
|
|
77
|
-
props.setCurrentView( VIEW_EDIT );
|
|
78
|
-
} }
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
86
|
+
if ( userPermissions.canAdd() ) {
|
|
87
|
+
handlers.onAdd = () => {
|
|
88
|
+
props.setCurrentView( VIEW_ADD );
|
|
89
|
+
};
|
|
90
|
+
}
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
}
|
|
92
|
+
if ( userPermissions.canEdit() ) {
|
|
93
|
+
handlers.onEdit = ( key: string ) => {
|
|
94
|
+
props.setEditId( key );
|
|
95
|
+
props.setCurrentView( VIEW_EDIT );
|
|
96
|
+
};
|
|
97
|
+
}
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
onClose={ props.closePopover }
|
|
98
|
-
onSubmit={ handleSubmitOnEdit }
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
99
|
+
if ( userPermissions.canManageSettings() && props.onSettings ) {
|
|
100
|
+
handlers.onSettings = () => {
|
|
101
|
+
props.onSettings?.();
|
|
102
|
+
props.closePopover();
|
|
103
|
+
};
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
onAdd={ () => {
|
|
110
|
-
props.setCurrentView( VIEW_ADD );
|
|
111
|
-
} }
|
|
112
|
-
onEdit={ ( key ) => {
|
|
113
|
-
props.editIdRef.current = key;
|
|
114
|
-
props.setCurrentView( VIEW_EDIT );
|
|
115
|
-
} }
|
|
116
|
-
/>
|
|
117
|
-
);
|
|
106
|
+
const handleSubmitOnEdit = () => {
|
|
107
|
+
if ( props?.selectedVariable?.key === props.editId ) {
|
|
108
|
+
handlers.onClose();
|
|
109
|
+
} else {
|
|
110
|
+
handlers.onGoBack?.();
|
|
118
111
|
}
|
|
112
|
+
};
|
|
119
113
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
if ( VIEW_LIST === props.currentView ) {
|
|
115
|
+
return (
|
|
116
|
+
<VariablesSelection
|
|
117
|
+
closePopover={ handlers.onClose }
|
|
118
|
+
onAdd={ handlers.onAdd }
|
|
119
|
+
onEdit={ handlers.onEdit }
|
|
120
|
+
onSettings={ handlers.onSettings }
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
if ( VIEW_ADD === props.currentView ) {
|
|
126
|
+
return <VariableCreation onGoBack={ handlers.onGoBack } onClose={ handlers.onClose } />;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if ( VIEW_EDIT === props.currentView ) {
|
|
130
|
+
return (
|
|
131
|
+
<VariableEdit
|
|
132
|
+
editId={ props.editId }
|
|
133
|
+
onGoBack={ handlers.onGoBack }
|
|
134
|
+
onClose={ handlers.onClose }
|
|
135
|
+
onSubmit={ handleSubmitOnEdit }
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
return null;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
__createPanel as createPanel,
|
|
5
|
+
Panel,
|
|
6
|
+
PanelBody,
|
|
7
|
+
PanelFooter,
|
|
8
|
+
PanelHeader,
|
|
9
|
+
PanelHeaderTitle,
|
|
10
|
+
} from '@elementor/editor-panels';
|
|
11
|
+
import { ThemeProvider } from '@elementor/editor-ui';
|
|
12
|
+
import { changeEditMode } from '@elementor/editor-v1-adapters';
|
|
13
|
+
import { FilterIcon, XIcon } from '@elementor/icons';
|
|
14
|
+
import { Alert, Box, Button, Divider, ErrorBoundary, IconButton, type IconButtonProps, Stack } from '@elementor/ui';
|
|
15
|
+
import { __ } from '@wordpress/i18n';
|
|
16
|
+
|
|
17
|
+
const id = 'variables-manager';
|
|
18
|
+
|
|
19
|
+
export const { panel, usePanelActions } = createPanel( {
|
|
20
|
+
id,
|
|
21
|
+
component: VariablesManagerPanel,
|
|
22
|
+
allowedEditModes: [ 'edit', id ],
|
|
23
|
+
onOpen: () => {
|
|
24
|
+
changeEditMode( id );
|
|
25
|
+
},
|
|
26
|
+
onClose: () => {
|
|
27
|
+
changeEditMode( 'edit' );
|
|
28
|
+
},
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
export function VariablesManagerPanel() {
|
|
32
|
+
const { close: closePanel } = usePanelActions();
|
|
33
|
+
const isDirty = false;
|
|
34
|
+
|
|
35
|
+
usePreventUnload( isDirty );
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ThemeProvider>
|
|
39
|
+
<ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
|
|
40
|
+
<Panel>
|
|
41
|
+
<PanelHeader>
|
|
42
|
+
<Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
|
|
43
|
+
<Stack width="100%" direction="row" gap={ 1 }>
|
|
44
|
+
<PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
|
|
45
|
+
<FilterIcon fontSize="inherit" />
|
|
46
|
+
{ __( 'Variables Manager', 'elementor' ) }
|
|
47
|
+
</PanelHeaderTitle>
|
|
48
|
+
</Stack>
|
|
49
|
+
<CloseButton
|
|
50
|
+
sx={ { marginLeft: 'auto' } }
|
|
51
|
+
onClose={ () => {
|
|
52
|
+
closePanel();
|
|
53
|
+
} }
|
|
54
|
+
/>
|
|
55
|
+
</Stack>
|
|
56
|
+
</PanelHeader>
|
|
57
|
+
<PanelBody
|
|
58
|
+
sx={ {
|
|
59
|
+
display: 'flex',
|
|
60
|
+
flexDirection: 'column',
|
|
61
|
+
height: '100%',
|
|
62
|
+
} }
|
|
63
|
+
>
|
|
64
|
+
<Divider />
|
|
65
|
+
<Box
|
|
66
|
+
px={ 2 }
|
|
67
|
+
sx={ {
|
|
68
|
+
flexGrow: 1,
|
|
69
|
+
overflowY: 'auto',
|
|
70
|
+
} }
|
|
71
|
+
>
|
|
72
|
+
List
|
|
73
|
+
</Box>
|
|
74
|
+
</PanelBody>
|
|
75
|
+
|
|
76
|
+
<PanelFooter>
|
|
77
|
+
<Button fullWidth size="small" color="global" variant="contained" disabled={ ! isDirty }>
|
|
78
|
+
{ __( 'Save changes', 'elementor' ) }
|
|
79
|
+
</Button>
|
|
80
|
+
</PanelFooter>
|
|
81
|
+
</Panel>
|
|
82
|
+
</ErrorBoundary>
|
|
83
|
+
</ThemeProvider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const CloseButton = ( { onClose, ...props }: IconButtonProps & { onClose: () => void } ) => (
|
|
88
|
+
<IconButton size="small" color="secondary" onClick={ onClose } aria-label="Close" { ...props }>
|
|
89
|
+
<XIcon fontSize="small" />
|
|
90
|
+
</IconButton>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const ErrorBoundaryFallback = () => (
|
|
94
|
+
<Box role="alert" sx={ { minHeight: '100%', p: 2 } }>
|
|
95
|
+
<Alert severity="error" sx={ { mb: 2, maxWidth: 400, textAlign: 'center' } }>
|
|
96
|
+
<strong>{ __( 'Something went wrong', 'elementor' ) }</strong>
|
|
97
|
+
</Alert>
|
|
98
|
+
</Box>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const usePreventUnload = ( isDirty: boolean ) => {
|
|
102
|
+
useEffect( () => {
|
|
103
|
+
const handleBeforeUnload = ( event: BeforeUnloadEvent ) => {
|
|
104
|
+
if ( isDirty ) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
window.addEventListener( 'beforeunload', handleBeforeUnload );
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
window.removeEventListener( 'beforeunload', handleBeforeUnload );
|
|
113
|
+
};
|
|
114
|
+
}, [ isDirty ] );
|
|
115
|
+
};
|
|
@@ -3,13 +3,14 @@ import { useState } from 'react';
|
|
|
3
3
|
import { useBoundProp } from '@elementor/editor-controls';
|
|
4
4
|
import { PopoverBody } from '@elementor/editor-editing-panel';
|
|
5
5
|
import { PopoverHeader, PopoverMenuList, PopoverSearch, type VirtualizedItem } from '@elementor/editor-ui';
|
|
6
|
-
import { ColorFilterIcon, PlusIcon, SettingsIcon
|
|
6
|
+
import { ColorFilterIcon, PlusIcon, SettingsIcon } from '@elementor/icons';
|
|
7
7
|
import { Divider, IconButton } from '@elementor/ui';
|
|
8
|
-
import { __ } from '@wordpress/i18n';
|
|
8
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
9
9
|
|
|
10
|
+
import { useVariableType } from '../context/variable-type-context';
|
|
10
11
|
import { useFilteredVariables } from '../hooks/use-prop-variables';
|
|
11
|
-
import { fontVariablePropTypeUtil } from '../prop-types/font-variable-prop-type';
|
|
12
12
|
import { type ExtendedVirtualizedItem } from '../types';
|
|
13
|
+
import { trackVariableEvent } from '../utils/tracking';
|
|
13
14
|
import { MenuItemContent } from './ui/menu-item-content';
|
|
14
15
|
import { NoSearchResults } from './ui/no-search-results';
|
|
15
16
|
import { NoVariables } from './ui/no-variables';
|
|
@@ -24,26 +25,42 @@ type Props = {
|
|
|
24
25
|
onSettings?: () => void;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
export const
|
|
28
|
-
const {
|
|
28
|
+
export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }: Props ) => {
|
|
29
|
+
const { icon: VariableIcon, startIcon, variableType, propTypeUtil } = useVariableType();
|
|
30
|
+
|
|
31
|
+
const { value: variable, setValue: setVariable, path } = useBoundProp( propTypeUtil );
|
|
29
32
|
const [ searchValue, setSearchValue ] = useState( '' );
|
|
30
33
|
|
|
31
34
|
const {
|
|
32
35
|
list: variables,
|
|
33
36
|
hasMatches: hasSearchResults,
|
|
34
37
|
isSourceNotEmpty: hasVariables,
|
|
35
|
-
} = useFilteredVariables( searchValue,
|
|
38
|
+
} = useFilteredVariables( searchValue, propTypeUtil.key );
|
|
36
39
|
|
|
37
40
|
const handleSetVariable = ( key: string ) => {
|
|
38
41
|
setVariable( key );
|
|
42
|
+
trackVariableEvent( {
|
|
43
|
+
varType: variableType,
|
|
44
|
+
controlPath: path.join( '.' ),
|
|
45
|
+
action: 'connect',
|
|
46
|
+
} );
|
|
39
47
|
closePopover();
|
|
40
48
|
};
|
|
41
49
|
|
|
50
|
+
const onAddAndTrack = () => {
|
|
51
|
+
onAdd?.();
|
|
52
|
+
trackVariableEvent( {
|
|
53
|
+
varType: variableType,
|
|
54
|
+
controlPath: path.join( '.' ),
|
|
55
|
+
action: 'add',
|
|
56
|
+
} );
|
|
57
|
+
};
|
|
58
|
+
|
|
42
59
|
const actions = [];
|
|
43
60
|
|
|
44
61
|
if ( onAdd ) {
|
|
45
62
|
actions.push(
|
|
46
|
-
<IconButton key="add" size={ SIZE } onClick={
|
|
63
|
+
<IconButton key="add" size={ SIZE } onClick={ onAddAndTrack }>
|
|
47
64
|
<PlusIcon fontSize={ SIZE } />
|
|
48
65
|
</IconButton>
|
|
49
66
|
);
|
|
@@ -57,13 +74,15 @@ export const FontVariablesSelection = ( { closePopover, onAdd, onEdit, onSetting
|
|
|
57
74
|
);
|
|
58
75
|
}
|
|
59
76
|
|
|
77
|
+
const StartIcon = startIcon || ( () => <VariableIcon fontSize={ SIZE } /> );
|
|
78
|
+
|
|
60
79
|
const items: ExtendedVirtualizedItem[] = variables.map( ( { value, label, key } ) => ( {
|
|
61
80
|
type: 'item' as const,
|
|
62
81
|
value: key,
|
|
63
82
|
label,
|
|
64
|
-
icon: <
|
|
83
|
+
icon: <StartIcon value={ value } />,
|
|
65
84
|
secondaryText: value,
|
|
66
|
-
onEdit: () => onEdit?.( key ),
|
|
85
|
+
onEdit: onEdit ? () => onEdit?.( key ) : undefined,
|
|
67
86
|
} ) );
|
|
68
87
|
|
|
69
88
|
const handleSearch = ( search: string ) => {
|
|
@@ -74,12 +93,18 @@ export const FontVariablesSelection = ( { closePopover, onAdd, onEdit, onSetting
|
|
|
74
93
|
setSearchValue( '' );
|
|
75
94
|
};
|
|
76
95
|
|
|
96
|
+
const noVariableTitle = sprintf(
|
|
97
|
+
/* translators: %s: Variable Type. */
|
|
98
|
+
__( 'Create your first %s variable', 'elementor' ),
|
|
99
|
+
variableType
|
|
100
|
+
);
|
|
101
|
+
|
|
77
102
|
return (
|
|
78
103
|
<PopoverBody>
|
|
79
104
|
<PopoverHeader
|
|
80
105
|
title={ __( 'Variables', 'elementor' ) }
|
|
81
|
-
onClose={ closePopover }
|
|
82
106
|
icon={ <ColorFilterIcon fontSize={ SIZE } /> }
|
|
107
|
+
onClose={ closePopover }
|
|
83
108
|
actions={ actions }
|
|
84
109
|
/>
|
|
85
110
|
|
|
@@ -99,7 +124,7 @@ export const FontVariablesSelection = ( { closePopover, onAdd, onEdit, onSetting
|
|
|
99
124
|
onSelect={ handleSetVariable }
|
|
100
125
|
onClose={ () => {} }
|
|
101
126
|
selectedValue={ variable }
|
|
102
|
-
data-testid=
|
|
127
|
+
data-testid={ `${ variableType }-variables-list` }
|
|
103
128
|
menuListTemplate={ VariablesStyledMenuList }
|
|
104
129
|
menuItemContentTemplate={ ( item: VirtualizedItem< 'item', string > ) => (
|
|
105
130
|
<MenuItemContent item={ item } />
|
|
@@ -111,16 +136,12 @@ export const FontVariablesSelection = ( { closePopover, onAdd, onEdit, onSetting
|
|
|
111
136
|
<NoSearchResults
|
|
112
137
|
searchValue={ searchValue }
|
|
113
138
|
onClear={ handleClearSearch }
|
|
114
|
-
icon={ <
|
|
139
|
+
icon={ <VariableIcon fontSize="large" /> }
|
|
115
140
|
/>
|
|
116
141
|
) }
|
|
117
142
|
|
|
118
143
|
{ ! hasVariables && (
|
|
119
|
-
<NoVariables
|
|
120
|
-
title={ __( 'Create your first font variable', 'elementor' ) }
|
|
121
|
-
icon={ <TextIcon fontSize="large" /> }
|
|
122
|
-
onAdd={ onAdd }
|
|
123
|
-
/>
|
|
144
|
+
<NoVariables title={ noVariableTitle } icon={ <VariableIcon fontSize="large" /> } onAdd={ onAdd } />
|
|
124
145
|
) }
|
|
125
146
|
</PopoverBody>
|
|
126
147
|
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, type PropsWithChildren, type RefObject, useContext, useState } from 'react';
|
|
3
|
+
import { Box } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
const PopoverContentRefContext = createContext< RefObject< HTMLDivElement > | null >( null );
|
|
6
|
+
|
|
7
|
+
export const PopoverContentRefContextProvider = ( { children }: PropsWithChildren ) => {
|
|
8
|
+
const [ anchorRef, setAnchorRef ] = useState< RefObject< HTMLDivElement > | null >( null );
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<PopoverContentRefContext.Provider value={ anchorRef }>
|
|
12
|
+
<Box ref={ setAnchorRef }>{ children }</Box>
|
|
13
|
+
</PopoverContentRefContext.Provider>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const usePopoverContentRef = () => {
|
|
18
|
+
return useContext( PopoverContentRefContext );
|
|
19
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, type PropsWithChildren, useContext } from 'react';
|
|
3
|
+
import { type PropTypeKey } from '@elementor/editor-props';
|
|
4
|
+
|
|
5
|
+
import { getVariableType } from '../variables-registry/variable-type-registry';
|
|
6
|
+
|
|
7
|
+
type Props = PropsWithChildren< { propTypeKey: PropTypeKey } >;
|
|
8
|
+
|
|
9
|
+
const VariableTypeContext = createContext< PropTypeKey | null >( null );
|
|
10
|
+
|
|
11
|
+
export function VariableTypeProvider( { children, propTypeKey }: Props ) {
|
|
12
|
+
return <VariableTypeContext.Provider value={ propTypeKey }>{ children }</VariableTypeContext.Provider>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useVariableType() {
|
|
16
|
+
const context = useContext( VariableTypeContext );
|
|
17
|
+
|
|
18
|
+
if ( context === null ) {
|
|
19
|
+
throw new Error( 'useVariableType must be used within a VariableTypeProvider' );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return getVariableType( context );
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useBoundProp } from '@elementor/editor-controls';
|
|
3
|
+
import { type TransformablePropValue } from '@elementor/editor-props';
|
|
4
|
+
|
|
5
|
+
import { AssignedVariable } from '../components/ui/variable/assigned-variable';
|
|
6
|
+
import { DeletedVariable } from '../components/ui/variable/deleted-variable';
|
|
7
|
+
import { MissingVariable } from '../components/ui/variable/missing-variable';
|
|
8
|
+
import { useVariable } from '../hooks/use-prop-variables';
|
|
9
|
+
|
|
10
|
+
export const VariableControl = () => {
|
|
11
|
+
const boundProp = useBoundProp().value as TransformablePropValue< string, string >;
|
|
12
|
+
|
|
13
|
+
const assignedVariable = useVariable( boundProp?.value );
|
|
14
|
+
|
|
15
|
+
if ( ! assignedVariable ) {
|
|
16
|
+
return <MissingVariable />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { $$type: propTypeKey } = boundProp;
|
|
20
|
+
|
|
21
|
+
if ( assignedVariable?.deleted ) {
|
|
22
|
+
return <DeletedVariable variable={ assignedVariable } propTypeKey={ propTypeKey } />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return <AssignedVariable variable={ assignedVariable } propTypeKey={ propTypeKey } />;
|
|
26
|
+
};
|