@elementor/editor-variables 3.33.0-98 → 3.35.0-324
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/dist/index.d.mts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +1903 -809
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1850 -747
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -14
- package/src/api.ts +24 -0
- package/src/batch-operations.ts +86 -0
- package/src/components/fields/color-field.tsx +1 -0
- package/src/components/fields/font-field.tsx +2 -1
- package/src/components/fields/label-field.tsx +42 -6
- package/src/components/ui/deleted-variable-alert.tsx +14 -10
- package/src/components/ui/{no-variables.tsx → empty-state.tsx} +8 -13
- package/src/components/ui/menu-item-content.tsx +14 -11
- package/src/components/ui/mismatch-variable-alert.tsx +5 -9
- package/src/components/ui/missing-variable-alert.tsx +8 -9
- package/src/components/ui/no-search-results.tsx +1 -2
- package/src/components/ui/tags/assigned-tag.tsx +6 -3
- package/src/components/ui/tags/warning-variable-tag.tsx +44 -0
- package/src/components/ui/variable/deleted-variable.tsx +13 -6
- package/src/components/ui/variable/mismatch-variable.tsx +11 -4
- package/src/components/ui/variable/missing-variable.tsx +2 -2
- package/src/components/variable-creation.tsx +13 -4
- package/src/components/variable-edit.tsx +12 -12
- package/src/components/variable-restore.tsx +3 -2
- package/src/components/variables-manager/hooks/use-auto-edit.ts +21 -0
- package/src/components/variables-manager/hooks/use-error-navigation.ts +49 -0
- package/src/components/variables-manager/hooks/use-variables-manager-state.ts +89 -0
- package/src/components/variables-manager/variable-editable-cell.tsx +131 -67
- package/src/components/variables-manager/variables-manager-create-menu.tsx +118 -0
- package/src/components/variables-manager/variables-manager-panel.tsx +290 -59
- package/src/components/variables-manager/variables-manager-table.tsx +124 -14
- package/src/components/variables-selection.tsx +61 -15
- package/src/controls/variable-control.tsx +1 -1
- package/src/hooks/use-prop-variables.ts +28 -9
- package/src/hooks/use-variable-bound-prop.ts +42 -0
- package/src/index.ts +1 -0
- package/src/init.ts +9 -6
- package/src/mcp/create-variable-tool.ts +70 -0
- package/src/mcp/delete-variable-tool.ts +50 -0
- package/src/mcp/index.ts +17 -0
- package/src/mcp/list-variables-tool.ts +58 -0
- package/src/mcp/update-variable-tool.ts +81 -0
- package/src/mcp/variables-resource.ts +28 -0
- package/src/register-variable-types.tsx +4 -0
- package/src/service.ts +60 -1
- package/src/storage.ts +8 -0
- package/src/types.ts +1 -0
- package/src/utils/filter-by-search.ts +5 -0
- package/src/utils/tracking.ts +37 -22
- package/src/utils/unlink-variable.ts +1 -1
- package/src/utils/validations.ts +72 -3
- package/src/variables-registry/create-variable-type-registry.ts +20 -8
- package/src/variables-registry/variable-type-registry.ts +2 -1
- package/src/components/ui/tags/deleted-tag.tsx +0 -37
- package/src/components/ui/tags/mismatch-tag.tsx +0 -37
- package/src/components/ui/tags/missing-tag.tsx +0 -25
- /package/src/components/variables-manager/{variable-edit-menu.tsx → ui/variable-edit-menu.tsx} +0 -0
- /package/src/components/variables-manager/{variable-table-cell.tsx → ui/variable-table-cell.tsx} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { createElement,
|
|
2
|
+
import { createElement, useEffect, useRef } from 'react';
|
|
3
3
|
import { EllipsisWithTooltip } from '@elementor/editor-ui';
|
|
4
4
|
import { GripVerticalIcon } from '@elementor/icons';
|
|
5
5
|
import {
|
|
@@ -20,18 +20,54 @@ import { __ } from '@wordpress/i18n';
|
|
|
20
20
|
import { type TVariablesList } from '../../storage';
|
|
21
21
|
import { getVariableType } from '../../variables-registry/variable-type-registry';
|
|
22
22
|
import { LabelField } from '../fields/label-field';
|
|
23
|
-
import { VariableEditMenu, type VariableManagerMenuAction } from './variable-edit-menu';
|
|
23
|
+
import { VariableEditMenu, type VariableManagerMenuAction } from './ui/variable-edit-menu';
|
|
24
|
+
import { VariableTableCell } from './ui/variable-table-cell';
|
|
24
25
|
import { VariableEditableCell } from './variable-editable-cell';
|
|
25
|
-
import { VariableTableCell } from './variable-table-cell';
|
|
26
26
|
|
|
27
27
|
type Props = {
|
|
28
28
|
menuActions: VariableManagerMenuAction[];
|
|
29
29
|
variables: TVariablesList;
|
|
30
30
|
onChange: ( variables: TVariablesList ) => void;
|
|
31
|
+
autoEditVariableId?: string;
|
|
32
|
+
onAutoEditComplete?: () => void;
|
|
33
|
+
onFieldError?: ( hasError: boolean ) => void;
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
export const VariablesManagerTable = ( {
|
|
34
|
-
|
|
36
|
+
export const VariablesManagerTable = ( {
|
|
37
|
+
menuActions,
|
|
38
|
+
variables,
|
|
39
|
+
onChange: handleOnChange,
|
|
40
|
+
autoEditVariableId,
|
|
41
|
+
onAutoEditComplete,
|
|
42
|
+
onFieldError,
|
|
43
|
+
}: Props ) => {
|
|
44
|
+
const tableContainerRef = useRef< HTMLDivElement >( null );
|
|
45
|
+
const variableRowRefs = useRef< Map< string, HTMLTableRowElement > >( new Map() );
|
|
46
|
+
|
|
47
|
+
useEffect( () => {
|
|
48
|
+
if ( autoEditVariableId && tableContainerRef.current ) {
|
|
49
|
+
const rowElement = variableRowRefs.current.get( autoEditVariableId );
|
|
50
|
+
if ( rowElement ) {
|
|
51
|
+
setTimeout( () => {
|
|
52
|
+
rowElement.scrollIntoView( {
|
|
53
|
+
behavior: 'smooth',
|
|
54
|
+
block: 'center',
|
|
55
|
+
inline: 'nearest',
|
|
56
|
+
} );
|
|
57
|
+
}, 100 );
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, [ autoEditVariableId ] );
|
|
61
|
+
|
|
62
|
+
const handleRowRef = ( id: string ) => ( ref: HTMLTableRowElement | null ) => {
|
|
63
|
+
if ( ref ) {
|
|
64
|
+
variableRowRefs.current.set( id, ref );
|
|
65
|
+
} else {
|
|
66
|
+
variableRowRefs.current.delete( id );
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const ids = Object.keys( variables ).sort( sortVariablesOrder( variables ) );
|
|
35
71
|
const rows = ids
|
|
36
72
|
.filter( ( id ) => ! variables[ id ].deleted )
|
|
37
73
|
.map( ( id ) => {
|
|
@@ -40,9 +76,9 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
40
76
|
|
|
41
77
|
return {
|
|
42
78
|
id,
|
|
79
|
+
type: variable.type,
|
|
43
80
|
name: variable.label,
|
|
44
81
|
value: variable.value,
|
|
45
|
-
type: variable.type,
|
|
46
82
|
...variableType,
|
|
47
83
|
};
|
|
48
84
|
} );
|
|
@@ -52,8 +88,24 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
52
88
|
tableLayout: 'fixed',
|
|
53
89
|
};
|
|
54
90
|
|
|
91
|
+
const handleReorder = ( newIds: string[] ) => {
|
|
92
|
+
const updatedVariables = { ...variables };
|
|
93
|
+
|
|
94
|
+
newIds.forEach( ( id, index ) => {
|
|
95
|
+
const current = updatedVariables[ id ];
|
|
96
|
+
|
|
97
|
+
if ( ! current ) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
updatedVariables[ id ] = Object.assign( {}, current, { order: index + 1 } );
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
handleOnChange( updatedVariables );
|
|
105
|
+
};
|
|
106
|
+
|
|
55
107
|
return (
|
|
56
|
-
<TableContainer sx={ { overflow: 'initial' } }>
|
|
108
|
+
<TableContainer ref={ tableContainerRef } sx={ { overflow: 'initial' } }>
|
|
57
109
|
<Table sx={ tableSX } aria-label="Variables manager list with drag and drop reordering" stickyHeader>
|
|
58
110
|
<TableHead>
|
|
59
111
|
<TableRow>
|
|
@@ -66,7 +118,7 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
66
118
|
<TableBody>
|
|
67
119
|
<UnstableSortableProvider
|
|
68
120
|
value={ ids }
|
|
69
|
-
onChange={
|
|
121
|
+
onChange={ handleReorder }
|
|
70
122
|
variant="static"
|
|
71
123
|
restrictAxis
|
|
72
124
|
dragOverlay={ ( { children: dragOverlayChildren, ...dragOverlayProps } ) => (
|
|
@@ -88,9 +140,7 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
88
140
|
isDragged,
|
|
89
141
|
dropPosition,
|
|
90
142
|
setTriggerRef,
|
|
91
|
-
isDragOverlay,
|
|
92
143
|
isSorting,
|
|
93
|
-
index,
|
|
94
144
|
}: UnstableSortableItemRenderProps ) => {
|
|
95
145
|
const showIndicationBefore = showDropIndication && dropPosition === 'before';
|
|
96
146
|
const showIndicationAfter = showDropIndication && dropPosition === 'after';
|
|
@@ -98,6 +148,7 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
98
148
|
return (
|
|
99
149
|
<TableRow
|
|
100
150
|
{ ...itemProps }
|
|
151
|
+
ref={ handleRowRef( 'table-ref-' + row.id ) }
|
|
101
152
|
selected={ isDragged }
|
|
102
153
|
sx={ {
|
|
103
154
|
...( showIndicationBefore && {
|
|
@@ -123,7 +174,6 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
123
174
|
},
|
|
124
175
|
} }
|
|
125
176
|
style={ { ...itemStyle, ...triggerStyle } }
|
|
126
|
-
disableDivider={ isDragOverlay || index === rows.length - 1 }
|
|
127
177
|
>
|
|
128
178
|
<VariableTableCell noPadding width={ 10 } maxWidth={ 10 }>
|
|
129
179
|
<IconButton
|
|
@@ -148,15 +198,34 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
148
198
|
}
|
|
149
199
|
} }
|
|
150
200
|
prefixElement={ createElement( row.icon, { fontSize: 'inherit' } ) }
|
|
151
|
-
editableElement={ ( {
|
|
201
|
+
editableElement={ ( {
|
|
202
|
+
value,
|
|
203
|
+
onChange,
|
|
204
|
+
onValidationChange,
|
|
205
|
+
error,
|
|
206
|
+
} ) => (
|
|
152
207
|
<LabelField
|
|
153
208
|
id={ 'variable-label-' + row.id }
|
|
154
209
|
size="tiny"
|
|
155
210
|
value={ value }
|
|
156
211
|
onChange={ onChange }
|
|
212
|
+
onErrorChange={ ( errorMsg ) => {
|
|
213
|
+
onValidationChange?.( errorMsg );
|
|
214
|
+
onFieldError?.( !! errorMsg );
|
|
215
|
+
} }
|
|
216
|
+
error={ error }
|
|
157
217
|
focusOnShow
|
|
218
|
+
selectOnShow={ autoEditVariableId === row.id }
|
|
219
|
+
showWarningInfotip={ true }
|
|
220
|
+
variables={ variables }
|
|
158
221
|
/>
|
|
159
222
|
) }
|
|
223
|
+
autoEdit={ autoEditVariableId === row.id }
|
|
224
|
+
onRowRef={ handleRowRef( row.id ) }
|
|
225
|
+
onAutoEditComplete={
|
|
226
|
+
autoEditVariableId === row.id ? onAutoEditComplete : undefined
|
|
227
|
+
}
|
|
228
|
+
fieldType="label"
|
|
160
229
|
>
|
|
161
230
|
<EllipsisWithTooltip
|
|
162
231
|
title={ row.name }
|
|
@@ -177,12 +246,46 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
177
246
|
} );
|
|
178
247
|
}
|
|
179
248
|
} }
|
|
180
|
-
editableElement={
|
|
249
|
+
editableElement={ ( {
|
|
250
|
+
value,
|
|
251
|
+
onChange,
|
|
252
|
+
onValidationChange,
|
|
253
|
+
error,
|
|
254
|
+
} ) =>
|
|
255
|
+
row.valueField( {
|
|
256
|
+
ref: {
|
|
257
|
+
current: variableRowRefs.current.get(
|
|
258
|
+
'table-ref-' + row.id
|
|
259
|
+
) as HTMLElement,
|
|
260
|
+
},
|
|
261
|
+
value,
|
|
262
|
+
onChange,
|
|
263
|
+
onPropTypeKeyChange: ( type ) => {
|
|
264
|
+
handleOnChange( {
|
|
265
|
+
...variables,
|
|
266
|
+
[ row.id ]: { ...variables[ row.id ], type },
|
|
267
|
+
} );
|
|
268
|
+
},
|
|
269
|
+
propTypeKey: row.type,
|
|
270
|
+
onValidationChange: ( errorMsg ) => {
|
|
271
|
+
onValidationChange?.( errorMsg );
|
|
272
|
+
onFieldError?.( !! errorMsg );
|
|
273
|
+
},
|
|
274
|
+
error,
|
|
275
|
+
} )
|
|
276
|
+
}
|
|
277
|
+
onRowRef={ handleRowRef( row.id ) }
|
|
278
|
+
gap={ 0.25 }
|
|
279
|
+
fieldType="value"
|
|
181
280
|
>
|
|
182
281
|
{ row.startIcon && row.startIcon( { value: row.value } ) }
|
|
183
282
|
<EllipsisWithTooltip
|
|
184
283
|
title={ row.value }
|
|
185
|
-
sx={ {
|
|
284
|
+
sx={ {
|
|
285
|
+
border: '4px solid transparent',
|
|
286
|
+
lineHeight: '1',
|
|
287
|
+
pt: 0.25,
|
|
288
|
+
} }
|
|
186
289
|
>
|
|
187
290
|
{ row.value }
|
|
188
291
|
</EllipsisWithTooltip>
|
|
@@ -214,3 +317,10 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
|
|
|
214
317
|
</TableContainer>
|
|
215
318
|
);
|
|
216
319
|
};
|
|
320
|
+
function sortVariablesOrder( variables: TVariablesList ): ( a: string, b: string ) => number {
|
|
321
|
+
return ( a, b ) => {
|
|
322
|
+
const orderA = variables[ a ]?.order ?? Number.MAX_SAFE_INTEGER;
|
|
323
|
+
const orderB = variables[ b ]?.order ?? Number.MAX_SAFE_INTEGER;
|
|
324
|
+
return orderA - orderB;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { useBoundProp } from '@elementor/editor-controls';
|
|
4
3
|
import { PopoverBody } from '@elementor/editor-editing-panel';
|
|
5
|
-
import { PopoverHeader, PopoverMenuList,
|
|
4
|
+
import { PopoverHeader, PopoverMenuList, SearchField, type VirtualizedItem } from '@elementor/editor-ui';
|
|
6
5
|
import { ColorFilterIcon, PlusIcon, SettingsIcon } from '@elementor/icons';
|
|
7
|
-
import { Divider, IconButton } from '@elementor/ui';
|
|
6
|
+
import { Divider, IconButton, Tooltip } from '@elementor/ui';
|
|
8
7
|
import { __, sprintf } from '@wordpress/i18n';
|
|
9
8
|
|
|
10
9
|
import { useVariableType } from '../context/variable-type-context';
|
|
11
10
|
import { useFilteredVariables } from '../hooks/use-prop-variables';
|
|
11
|
+
import { useVariableBoundProp } from '../hooks/use-variable-bound-prop';
|
|
12
12
|
import { type ExtendedVirtualizedItem } from '../types';
|
|
13
|
-
import { trackVariableEvent } from '../utils/tracking';
|
|
13
|
+
import { trackVariableEvent, trackVariablesManagerEvent } from '../utils/tracking';
|
|
14
|
+
import { EmptyState } from './ui/empty-state';
|
|
14
15
|
import { MenuItemContent } from './ui/menu-item-content';
|
|
15
16
|
import { NoSearchResults } from './ui/no-search-results';
|
|
16
|
-
import { NoVariables } from './ui/no-variables';
|
|
17
17
|
import { VariablesStyledMenuList } from './ui/styled-menu-list';
|
|
18
18
|
|
|
19
19
|
const SIZE = 'tiny';
|
|
20
|
+
const CREATE_LABEL = __( 'Create variable', 'elementor' );
|
|
21
|
+
const MANAGER_LABEL = __( 'Variables Manager', 'elementor' );
|
|
20
22
|
|
|
21
23
|
type Props = {
|
|
22
24
|
closePopover: () => void;
|
|
@@ -28,13 +30,14 @@ type Props = {
|
|
|
28
30
|
export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }: Props ) => {
|
|
29
31
|
const { icon: VariableIcon, startIcon, variableType, propTypeUtil } = useVariableType();
|
|
30
32
|
|
|
31
|
-
const { value: variable, setValue: setVariable, path } =
|
|
33
|
+
const { value: variable, setValue: setVariable, path } = useVariableBoundProp();
|
|
32
34
|
const [ searchValue, setSearchValue ] = useState( '' );
|
|
33
35
|
|
|
34
36
|
const {
|
|
35
37
|
list: variables,
|
|
36
38
|
hasMatches: hasSearchResults,
|
|
37
39
|
isSourceNotEmpty: hasVariables,
|
|
40
|
+
hasNoCompatibleVariables,
|
|
38
41
|
} = useFilteredVariables( searchValue, propTypeUtil.key );
|
|
39
42
|
|
|
40
43
|
const handleSetVariable = ( key: string ) => {
|
|
@@ -60,17 +63,40 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
|
|
|
60
63
|
|
|
61
64
|
if ( onAdd ) {
|
|
62
65
|
actions.push(
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
+
<Tooltip key="add" placement="top" title={ CREATE_LABEL }>
|
|
67
|
+
<IconButton
|
|
68
|
+
id="add-variable-button"
|
|
69
|
+
size={ SIZE }
|
|
70
|
+
onClick={ onAddAndTrack }
|
|
71
|
+
aria-label={ CREATE_LABEL }
|
|
72
|
+
>
|
|
73
|
+
<PlusIcon fontSize={ SIZE } />
|
|
74
|
+
</IconButton>
|
|
75
|
+
</Tooltip>
|
|
66
76
|
);
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
if ( onSettings ) {
|
|
80
|
+
const handleOpenManager = () => {
|
|
81
|
+
onSettings();
|
|
82
|
+
trackVariablesManagerEvent( {
|
|
83
|
+
action: 'openManager',
|
|
84
|
+
varType: variableType,
|
|
85
|
+
controlPath: path.join( '.' ),
|
|
86
|
+
} );
|
|
87
|
+
};
|
|
88
|
+
|
|
70
89
|
actions.push(
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
90
|
+
<Tooltip key="settings" placement="top" title={ MANAGER_LABEL }>
|
|
91
|
+
<IconButton
|
|
92
|
+
id="variables-manager-button"
|
|
93
|
+
size={ SIZE }
|
|
94
|
+
onClick={ handleOpenManager }
|
|
95
|
+
aria-label={ MANAGER_LABEL }
|
|
96
|
+
>
|
|
97
|
+
<SettingsIcon fontSize={ SIZE } />
|
|
98
|
+
</IconButton>
|
|
99
|
+
</Tooltip>
|
|
74
100
|
);
|
|
75
101
|
}
|
|
76
102
|
|
|
@@ -109,7 +135,7 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
|
|
|
109
135
|
/>
|
|
110
136
|
|
|
111
137
|
{ hasVariables && (
|
|
112
|
-
<
|
|
138
|
+
<SearchField
|
|
113
139
|
value={ searchValue }
|
|
114
140
|
onSearch={ handleSearch }
|
|
115
141
|
placeholder={ __( 'Search', 'elementor' ) }
|
|
@@ -140,8 +166,28 @@ export const VariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }:
|
|
|
140
166
|
/>
|
|
141
167
|
) }
|
|
142
168
|
|
|
143
|
-
{ ! hasVariables && (
|
|
144
|
-
<
|
|
169
|
+
{ ! hasVariables && ! hasNoCompatibleVariables && (
|
|
170
|
+
<EmptyState
|
|
171
|
+
title={ noVariableTitle }
|
|
172
|
+
message={ __(
|
|
173
|
+
'Variables are saved attributes that you can apply anywhere on your site.',
|
|
174
|
+
'elementor'
|
|
175
|
+
) }
|
|
176
|
+
icon={ <VariableIcon fontSize="large" /> }
|
|
177
|
+
onAdd={ onAdd }
|
|
178
|
+
/>
|
|
179
|
+
) }
|
|
180
|
+
|
|
181
|
+
{ hasNoCompatibleVariables && (
|
|
182
|
+
<EmptyState
|
|
183
|
+
title={ __( 'No compatible variables', 'elementor' ) }
|
|
184
|
+
message={ __(
|
|
185
|
+
'Looks like none of your variables work with this control. Create a new variable to use it here.',
|
|
186
|
+
'elementor'
|
|
187
|
+
) }
|
|
188
|
+
icon={ <VariableIcon fontSize="large" /> }
|
|
189
|
+
onAdd={ onAdd }
|
|
190
|
+
/>
|
|
145
191
|
) }
|
|
146
192
|
</PopoverBody>
|
|
147
193
|
);
|
|
@@ -12,7 +12,7 @@ import { getVariableType } from '../variables-registry/variable-type-registry';
|
|
|
12
12
|
export const VariableControl = () => {
|
|
13
13
|
const boundProp = useBoundProp();
|
|
14
14
|
|
|
15
|
-
const boundPropValue = boundProp.value as TransformablePropValue< string, string >;
|
|
15
|
+
const boundPropValue = ( boundProp.value ?? boundProp.placeholder ) as TransformablePropValue< string, string >;
|
|
16
16
|
|
|
17
17
|
const assignedVariable = useVariable( boundPropValue?.value );
|
|
18
18
|
|
|
@@ -5,6 +5,8 @@ import { type PropKey } from '@elementor/editor-props';
|
|
|
5
5
|
import { useVariableType } from '../context/variable-type-context';
|
|
6
6
|
import { service } from '../service';
|
|
7
7
|
import { type NormalizedVariable, type Variable } from '../types';
|
|
8
|
+
import { filterBySearch } from '../utils/filter-by-search';
|
|
9
|
+
import { getVariableType, getVariableTypes } from '../variables-registry/variable-type-registry';
|
|
8
10
|
|
|
9
11
|
export const getVariables = ( includeDeleted = true ) => {
|
|
10
12
|
const variables = service.variables();
|
|
@@ -33,12 +35,18 @@ export const useFilteredVariables = ( searchValue: string, propTypeKey: string )
|
|
|
33
35
|
const baseVariables = usePropVariables( propTypeKey );
|
|
34
36
|
|
|
35
37
|
const typeFilteredVariables = useVariableSelectionFilter( baseVariables );
|
|
36
|
-
const searchFilteredVariables =
|
|
38
|
+
const searchFilteredVariables = filterBySearch( typeFilteredVariables, searchValue );
|
|
39
|
+
const sortedVariables = searchFilteredVariables.sort( ( a, b ) => {
|
|
40
|
+
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
41
|
+
const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
|
|
42
|
+
return orderA - orderB;
|
|
43
|
+
} );
|
|
37
44
|
|
|
38
45
|
return {
|
|
39
|
-
list:
|
|
46
|
+
list: sortedVariables,
|
|
40
47
|
hasMatches: searchFilteredVariables.length > 0,
|
|
41
48
|
isSourceNotEmpty: typeFilteredVariables.length > 0,
|
|
49
|
+
hasNoCompatibleVariables: baseVariables.length > 0 && typeFilteredVariables.length === 0,
|
|
42
50
|
};
|
|
43
51
|
};
|
|
44
52
|
|
|
@@ -49,24 +57,35 @@ const useVariableSelectionFilter = ( variables: NormalizedVariable[] ): Normaliz
|
|
|
49
57
|
return selectionFilter ? selectionFilter( variables, propType ) : variables;
|
|
50
58
|
};
|
|
51
59
|
|
|
52
|
-
const filterVariablesBySearchValue = ( variables: NormalizedVariable[], searchValue: string ): NormalizedVariable[] => {
|
|
53
|
-
const lowerSearchValue = searchValue.toLowerCase();
|
|
54
|
-
return variables.filter( ( { label } ) => label.toLowerCase().includes( lowerSearchValue ) );
|
|
55
|
-
};
|
|
56
|
-
|
|
57
60
|
const usePropVariables = ( propKey: PropKey ): NormalizedVariable[] => {
|
|
58
61
|
return useMemo( () => normalizeVariables( propKey ), [ propKey ] );
|
|
59
62
|
};
|
|
60
63
|
|
|
64
|
+
const getMatchingTypes = ( propKey: string ): string[] => {
|
|
65
|
+
const matchingTypes: string[] = [];
|
|
66
|
+
const allTypes = getVariableTypes();
|
|
67
|
+
const variableType = getVariableType( propKey );
|
|
68
|
+
|
|
69
|
+
Object.entries( allTypes ).forEach( ( [ key, typeOptions ] ) => {
|
|
70
|
+
if ( variableType.variableType === typeOptions.variableType ) {
|
|
71
|
+
matchingTypes.push( key );
|
|
72
|
+
}
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
return matchingTypes;
|
|
76
|
+
};
|
|
77
|
+
|
|
61
78
|
const normalizeVariables = ( propKey: string ) => {
|
|
62
79
|
const variables = getVariables( false );
|
|
80
|
+
const matchingTypes = getMatchingTypes( propKey );
|
|
63
81
|
|
|
64
82
|
return Object.entries( variables )
|
|
65
|
-
.filter( ( [ , variable ] ) => variable.type
|
|
66
|
-
.map( ( [ key, { label, value } ] ) => ( {
|
|
83
|
+
.filter( ( [ , variable ] ) => matchingTypes.includes( variable.type ) )
|
|
84
|
+
.map( ( [ key, { label, value, order } ] ) => ( {
|
|
67
85
|
key,
|
|
68
86
|
label,
|
|
69
87
|
value,
|
|
88
|
+
order,
|
|
70
89
|
} ) );
|
|
71
90
|
};
|
|
72
91
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useBoundProp } from '@elementor/editor-controls';
|
|
2
|
+
import { isTransformable, type PropValue } from '@elementor/editor-props';
|
|
3
|
+
|
|
4
|
+
import { useVariableType } from '../context/variable-type-context';
|
|
5
|
+
|
|
6
|
+
type BoundProp = ReturnType< typeof useBoundProp< PropValue > >;
|
|
7
|
+
|
|
8
|
+
type VariableBoundProp = ReturnType< typeof useBoundProp< string > > & {
|
|
9
|
+
setVariableValue: ( value: PropValue ) => void;
|
|
10
|
+
variableId: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const useVariableBoundProp = (): VariableBoundProp => {
|
|
14
|
+
const { propTypeUtil } = useVariableType();
|
|
15
|
+
const boundProp = useBoundProp( propTypeUtil );
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
...boundProp,
|
|
19
|
+
setVariableValue: ( value: PropValue ) => resolveBoundPropAndSetValue( value, boundProp as BoundProp ),
|
|
20
|
+
variableId: boundProp.value ?? boundProp.placeholder,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const resolveBoundPropAndSetValue = ( value: PropValue, boundProp: BoundProp ) => {
|
|
25
|
+
const propValue = unwrapValue( boundProp.value );
|
|
26
|
+
const placeholder = unwrapValue( boundProp.placeholder );
|
|
27
|
+
const newValue = unwrapValue( value );
|
|
28
|
+
|
|
29
|
+
if ( ! propValue && placeholder === newValue ) {
|
|
30
|
+
return boundProp.setValue( null );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return boundProp.setValue( value );
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const unwrapValue = ( input: PropValue ): PropValue => {
|
|
37
|
+
if ( isTransformable( input ) ) {
|
|
38
|
+
return input.value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return input;
|
|
42
|
+
};
|
package/src/index.ts
CHANGED
package/src/init.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { injectIntoTop } from '@elementor/editor';
|
|
2
2
|
import { controlActionsMenu, registerControlReplacement } from '@elementor/editor-editing-panel';
|
|
3
3
|
import { __registerPanel as registerPanel } from '@elementor/editor-panels';
|
|
4
|
-
import type
|
|
4
|
+
import { isTransformable, type PropValue } from '@elementor/editor-props';
|
|
5
5
|
|
|
6
6
|
import { panel } from './components/variables-manager/variables-manager-panel';
|
|
7
7
|
import { VariableControl } from './controls/variable-control';
|
|
8
8
|
import { usePropVariableAction } from './hooks/use-prop-variable-action';
|
|
9
|
+
import { initMcp } from './mcp';
|
|
9
10
|
import { registerVariableTypes } from './register-variable-types';
|
|
10
11
|
import { StyleVariablesRenderer } from './renderers/style-variables-renderer';
|
|
11
12
|
import { registerRepeaterInjections } from './repeater-injections';
|
|
@@ -20,7 +21,7 @@ export function init() {
|
|
|
20
21
|
|
|
21
22
|
registerControlReplacement( {
|
|
22
23
|
component: VariableControl,
|
|
23
|
-
condition: ( { value } ) =>
|
|
24
|
+
condition: ( { value, placeholder } ) => hasVariable( value ) || hasVariable( placeholder ),
|
|
24
25
|
} );
|
|
25
26
|
|
|
26
27
|
registerPopoverAction( {
|
|
@@ -28,7 +29,9 @@ export function init() {
|
|
|
28
29
|
useProps: usePropVariableAction,
|
|
29
30
|
} );
|
|
30
31
|
|
|
31
|
-
variablesService.init()
|
|
32
|
+
variablesService.init().then( () => {
|
|
33
|
+
initMcp();
|
|
34
|
+
} );
|
|
32
35
|
|
|
33
36
|
injectIntoTop( {
|
|
34
37
|
id: 'canvas-style-variables-render',
|
|
@@ -38,9 +41,9 @@ export function init() {
|
|
|
38
41
|
registerPanel( panel );
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function
|
|
42
|
-
if (
|
|
43
|
-
return hasVariableType(
|
|
44
|
+
function hasVariable( value: PropValue ) {
|
|
45
|
+
if ( isTransformable( value ) ) {
|
|
46
|
+
return hasVariableType( value.$$type );
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
return false;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
2
|
+
import { z } from '@elementor/schema';
|
|
3
|
+
|
|
4
|
+
import { service } from '../service';
|
|
5
|
+
|
|
6
|
+
const InputSchema = {
|
|
7
|
+
type: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe( 'The type of the variable. Example values: "global-color-variable" or "global-font-variable".' ),
|
|
10
|
+
label: z.string().describe( 'The label of the variable, displayed to the user' ),
|
|
11
|
+
value: z.string().describe( 'The value of the variable, should correspond to the type' ),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const OutputSchema = {
|
|
15
|
+
status: z.enum( [ 'ok', 'error' ] ).describe( 'The status of the operation' ),
|
|
16
|
+
message: z.string().optional().describe( 'Optional message providing additional information about the operation' ),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const initCreateVariableTool = () => {
|
|
20
|
+
getMCPByDomain( 'variables' ).addTool( {
|
|
21
|
+
name: 'create-global-variable',
|
|
22
|
+
schema: InputSchema,
|
|
23
|
+
outputSchema: OutputSchema,
|
|
24
|
+
description: `Create a new global variable
|
|
25
|
+
## When to use this tool:
|
|
26
|
+
- When a user requests to create a new global variable in the Elementor editor.
|
|
27
|
+
- When you need to add a new variable to be used in the editor.
|
|
28
|
+
|
|
29
|
+
## Prequisites:
|
|
30
|
+
- Ensure you have the most up-to-date list of existing global variables to avoid label duplication. You can use the "list-global-variables" tool to fetch the current variables.
|
|
31
|
+
- Make sure when creating a new variable, the label is unique and not already in use.
|
|
32
|
+
- If the user does not provide a label, ask them to provide one before proceeding.
|
|
33
|
+
- If the user does not provide a type, ask them to provide one before proceeding.
|
|
34
|
+
- If the user does not provide a value, ask them to provide one before proceeding.
|
|
35
|
+
|
|
36
|
+
## Required parameters:
|
|
37
|
+
- type: The type of the variable. Possible values are 'global-color-variable' or 'global-font-variable'.
|
|
38
|
+
- label: The label of the variable, displayed to the user. Must be unique and not already in use.
|
|
39
|
+
- value: The value of the variable. For color variables, this should be a valid CSS color (e.g., 'rgb(255,0,0)', '#ff0000', 'red'). For font variables, this should be a valid font family (e.g., 'Arial', 'serif').
|
|
40
|
+
|
|
41
|
+
## Example tool call (JSON format):
|
|
42
|
+
\`\`\`json
|
|
43
|
+
{ "type": "global-color-variable", "label": "My Cool Color", "value": "rgb(1,2,3)" }
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
## Example tool response (JSON format):
|
|
47
|
+
\`\`\`json
|
|
48
|
+
{ "status": "ok" }
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Example to a failed tool response, which must be displayed to the end user. If the error message is not plain, attempt to find the most useful part of the message and display it.
|
|
52
|
+
{ "status": "error", "message": "Unsupported type 'global-kuku-variable'" }
|
|
53
|
+
|
|
54
|
+
In that case, inform the user the type is unsupported and they should try another type, perhaps consult to online documentation.
|
|
55
|
+
`,
|
|
56
|
+
handler: async ( params ) => {
|
|
57
|
+
const { type, label, value } = params;
|
|
58
|
+
try {
|
|
59
|
+
await service.create( { type, label, value } );
|
|
60
|
+
} catch ( error ) {
|
|
61
|
+
const message: string = ( error as Error ).message || 'Unknown server error';
|
|
62
|
+
return {
|
|
63
|
+
status: 'error',
|
|
64
|
+
message: `There was an error creating the variable: ${ message }`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { status: 'ok' };
|
|
68
|
+
},
|
|
69
|
+
} );
|
|
70
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
2
|
+
import { z } from '@elementor/schema';
|
|
3
|
+
|
|
4
|
+
import { service } from '../service';
|
|
5
|
+
|
|
6
|
+
export const initDeleteVariableTool = () => {
|
|
7
|
+
getMCPByDomain( 'variables' ).addTool( {
|
|
8
|
+
name: 'delete-global-variable',
|
|
9
|
+
schema: {
|
|
10
|
+
id: z.string().describe( 'The unique identifier of the variable to be deleted.' ),
|
|
11
|
+
},
|
|
12
|
+
outputSchema: {
|
|
13
|
+
status: z.enum( [ 'ok', 'error' ] ).describe( 'The status of the operation' ),
|
|
14
|
+
},
|
|
15
|
+
description: `Delete an existing global variable
|
|
16
|
+
|
|
17
|
+
## When to use this tool:
|
|
18
|
+
- When a user requests to delete an existing global variable in the Elementor editor.
|
|
19
|
+
- When you need to remove a variable that is no longer needed or relevant, with the user's confirmation.
|
|
20
|
+
|
|
21
|
+
## Prerequisites:
|
|
22
|
+
- Ensure you have the most up-to-date list of existing global variables. You can use the "list-global-variables" tool to fetch the current variables.
|
|
23
|
+
- Reference the variable by the "id" property, given from the "list-global-variables" tool.
|
|
24
|
+
- Make sure you have the unique identifier of the variable to be deleted before using this tool.
|
|
25
|
+
- Confirm with the user that they want to proceed with the deletion, as this action is irreversible.
|
|
26
|
+
|
|
27
|
+
<notice>
|
|
28
|
+
A use might reference a variable by it's label, but you must always use the unique identifier (id) to delete it.
|
|
29
|
+
If you only have the label, use the "list-global-variables" tool to find the corresponding id.
|
|
30
|
+
</notice>
|
|
31
|
+
|
|
32
|
+
<important>
|
|
33
|
+
This operation is destructive and cannot be undone. Ensure that the user is fully aware of the consequences before proceeding.
|
|
34
|
+
When a variable is deleted, all references to it in all pages accross the website will lose their effect.
|
|
35
|
+
</important>`,
|
|
36
|
+
handler: async ( params ) => {
|
|
37
|
+
const { id } = params;
|
|
38
|
+
try {
|
|
39
|
+
await service.delete( id );
|
|
40
|
+
return { status: 'ok' };
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
42
|
+
} catch ( err: unknown ) {
|
|
43
|
+
return {
|
|
44
|
+
status: 'error',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
isDestrcutive: true,
|
|
49
|
+
} );
|
|
50
|
+
};
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
2
|
+
|
|
3
|
+
import { initCreateVariableTool } from './create-variable-tool';
|
|
4
|
+
import { initDeleteVariableTool } from './delete-variable-tool';
|
|
5
|
+
import { initListVariablesTool } from './list-variables-tool';
|
|
6
|
+
import { initUpdateVariableTool } from './update-variable-tool';
|
|
7
|
+
import { initVariablesResource } from './variables-resource';
|
|
8
|
+
|
|
9
|
+
export function initMcp() {
|
|
10
|
+
const { setMCPDescription } = getMCPByDomain( 'variables' );
|
|
11
|
+
setMCPDescription( `Elementor Editor Variables MCP` );
|
|
12
|
+
initListVariablesTool();
|
|
13
|
+
initCreateVariableTool();
|
|
14
|
+
initUpdateVariableTool();
|
|
15
|
+
initDeleteVariableTool();
|
|
16
|
+
initVariablesResource();
|
|
17
|
+
}
|