@elementor/editor-variables 3.33.0-98 → 3.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/index.d.mts +11 -3
  2. package/dist/index.d.ts +11 -3
  3. package/dist/index.js +1874 -801
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1819 -737
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +16 -14
  8. package/src/api.ts +24 -0
  9. package/src/batch-operations.ts +86 -0
  10. package/src/components/fields/color-field.tsx +1 -0
  11. package/src/components/fields/font-field.tsx +2 -1
  12. package/src/components/fields/label-field.tsx +42 -6
  13. package/src/components/ui/deleted-variable-alert.tsx +14 -10
  14. package/src/components/ui/{no-variables.tsx → empty-state.tsx} +8 -13
  15. package/src/components/ui/menu-item-content.tsx +14 -11
  16. package/src/components/ui/mismatch-variable-alert.tsx +5 -9
  17. package/src/components/ui/missing-variable-alert.tsx +8 -9
  18. package/src/components/ui/no-search-results.tsx +1 -2
  19. package/src/components/ui/tags/assigned-tag.tsx +6 -3
  20. package/src/components/ui/tags/warning-variable-tag.tsx +44 -0
  21. package/src/components/ui/variable/deleted-variable.tsx +13 -6
  22. package/src/components/ui/variable/mismatch-variable.tsx +11 -4
  23. package/src/components/ui/variable/missing-variable.tsx +2 -2
  24. package/src/components/variable-creation.tsx +10 -3
  25. package/src/components/variable-edit.tsx +11 -12
  26. package/src/components/variable-restore.tsx +3 -2
  27. package/src/components/variables-manager/hooks/use-auto-edit.ts +21 -0
  28. package/src/components/variables-manager/hooks/use-error-navigation.ts +49 -0
  29. package/src/components/variables-manager/hooks/use-variables-manager-state.ts +89 -0
  30. package/src/components/variables-manager/variable-editable-cell.tsx +131 -67
  31. package/src/components/variables-manager/variables-manager-create-menu.tsx +116 -0
  32. package/src/components/variables-manager/variables-manager-panel.tsx +290 -59
  33. package/src/components/variables-manager/variables-manager-table.tsx +111 -14
  34. package/src/components/variables-selection.tsx +61 -15
  35. package/src/controls/variable-control.tsx +1 -1
  36. package/src/hooks/use-prop-variables.ts +11 -8
  37. package/src/hooks/use-variable-bound-prop.ts +42 -0
  38. package/src/index.ts +1 -0
  39. package/src/init.ts +19 -6
  40. package/src/mcp/create-variable-tool.ts +70 -0
  41. package/src/mcp/delete-variable-tool.ts +50 -0
  42. package/src/mcp/index.ts +17 -0
  43. package/src/mcp/list-variables-tool.ts +58 -0
  44. package/src/mcp/update-variable-tool.ts +81 -0
  45. package/src/mcp/variables-resource.ts +28 -0
  46. package/src/register-variable-types.tsx +2 -0
  47. package/src/service.ts +60 -1
  48. package/src/storage.ts +8 -0
  49. package/src/types.ts +1 -0
  50. package/src/utils/filter-by-search.ts +5 -0
  51. package/src/utils/tracking.ts +37 -22
  52. package/src/utils/validations.ts +72 -3
  53. package/src/variables-registry/create-variable-type-registry.ts +10 -1
  54. package/src/variables-registry/variable-type-registry.ts +2 -1
  55. package/src/components/ui/tags/deleted-tag.tsx +0 -37
  56. package/src/components/ui/tags/mismatch-tag.tsx +0 -37
  57. package/src/components/ui/tags/missing-tag.tsx +0 -25
  58. /package/src/components/variables-manager/{variable-edit-menu.tsx → ui/variable-edit-menu.tsx} +0 -0
  59. /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 { useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
3
  import {
4
4
  __createPanel as createPanel,
5
5
  Panel,
@@ -8,14 +8,32 @@ import {
8
8
  PanelHeader,
9
9
  PanelHeaderTitle,
10
10
  } from '@elementor/editor-panels';
11
- import { ThemeProvider } from '@elementor/editor-ui';
11
+ import { SaveChangesDialog, SearchField, ThemeProvider, useDialog } from '@elementor/editor-ui';
12
12
  import { changeEditMode } from '@elementor/editor-v1-adapters';
13
- import { ColorFilterIcon, TrashIcon, XIcon } from '@elementor/icons';
14
- import { Alert, Box, Button, Divider, ErrorBoundary, IconButton, type IconButtonProps, Stack } from '@elementor/ui';
13
+ import { AlertTriangleFilledIcon, ColorFilterIcon, TrashIcon } from '@elementor/icons';
14
+ import {
15
+ Alert,
16
+ AlertAction,
17
+ AlertTitle,
18
+ Button,
19
+ CloseButton,
20
+ Divider,
21
+ Infotip,
22
+ Stack,
23
+ usePopupState,
24
+ } from '@elementor/ui';
15
25
  import { __ } from '@wordpress/i18n';
16
26
 
17
- import { getVariables } from '../../hooks/use-prop-variables';
18
- import { type TVariablesList } from '../../storage';
27
+ import { trackVariablesManagerEvent } from '../../utils/tracking';
28
+ import { type ErrorResponse, type MappedError, mapServerError } from '../../utils/validations';
29
+ import { getVariableType } from '../../variables-registry/variable-type-registry';
30
+ import { DeleteConfirmationDialog } from '../ui/delete-confirmation-dialog';
31
+ import { EmptyState } from '../ui/empty-state';
32
+ import { NoSearchResults } from '../ui/no-search-results';
33
+ import { useAutoEdit } from './hooks/use-auto-edit';
34
+ import { useErrorNavigation } from './hooks/use-error-navigation';
35
+ import { useVariablesManagerState } from './hooks/use-variables-manager-state';
36
+ import { SIZE, VariableManagerCreateMenu } from './variables-manager-create-menu';
19
37
  import { VariablesManagerTable } from './variables-manager-table';
20
38
 
21
39
  const id = 'variables-manager';
@@ -30,96 +48,309 @@ export const { panel, usePanelActions } = createPanel( {
30
48
  onClose: () => {
31
49
  changeEditMode( 'edit' );
32
50
  },
51
+ isOpenPreviousElement: true,
33
52
  } );
34
53
 
35
54
  export function VariablesManagerPanel() {
36
55
  const { close: closePanel } = usePanelActions();
37
- const [ isDirty, setIsDirty ] = useState( false );
38
- const [ variables, setVariables ] = useState( getVariables( false ) );
39
- const [ deletedVariables, setDeletedVariables ] = useState< string[] >( [] );
56
+ const { open: openSaveChangesDialog, close: closeSaveChangesDialog, isOpen: isSaveChangesDialogOpen } = useDialog();
57
+
58
+ const createMenuState = usePopupState( {
59
+ variant: 'popover',
60
+ } );
61
+
62
+ const {
63
+ variables,
64
+ isDirty,
65
+ searchValue,
66
+ isSaveDisabled,
67
+ handleOnChange,
68
+ createVariable,
69
+ handleDeleteVariable,
70
+ handleSave,
71
+ isSaving,
72
+ handleSearch,
73
+ setIsSaving,
74
+ setIsSaveDisabled,
75
+ } = useVariablesManagerState();
76
+
77
+ const { autoEditVariableId, startAutoEdit, handleAutoEditComplete } = useAutoEdit();
78
+ const { createNavigationCallback, resetNavigation } = useErrorNavigation();
79
+
80
+ const [ deleteConfirmation, setDeleteConfirmation ] = useState< { id: string; label: string } | null >( null );
81
+ const [ serverError, setServerError ] = useState< MappedError | null >( null );
40
82
 
41
83
  usePreventUnload( isDirty );
42
84
 
85
+ const handleClosePanel = () => {
86
+ if ( isDirty ) {
87
+ openSaveChangesDialog();
88
+ return;
89
+ }
90
+
91
+ closePanel();
92
+ };
93
+
94
+ const handleCreateVariable = useCallback(
95
+ ( type: string, defaultName: string, defaultValue: string ) => {
96
+ const newId = createVariable( type, defaultName, defaultValue );
97
+ if ( newId ) {
98
+ startAutoEdit( newId );
99
+ }
100
+ },
101
+ [ createVariable, startAutoEdit ]
102
+ );
103
+
104
+ const handleSaveClick = async () => {
105
+ try {
106
+ setServerError( null );
107
+ resetNavigation();
108
+
109
+ const result = await handleSave();
110
+ trackVariablesManagerEvent( { action: 'saveChanges' } );
111
+ return result;
112
+ } catch ( error ) {
113
+ const mappedError = mapServerError( error as ErrorResponse );
114
+ const duplicatedIds = mappedError?.action?.data?.duplicatedIds;
115
+
116
+ if ( mappedError && 'label' === mappedError.field ) {
117
+ if ( duplicatedIds && mappedError.action ) {
118
+ mappedError.action.callback = createNavigationCallback( duplicatedIds, startAutoEdit, () => {
119
+ setIsSaveDisabled( false );
120
+ } );
121
+ }
122
+
123
+ setServerError( mappedError );
124
+ setIsSaveDisabled( true );
125
+ resetNavigation();
126
+ }
127
+
128
+ return { success: false, error: mappedError };
129
+ } finally {
130
+ setIsSaving( false );
131
+ }
132
+ };
133
+
134
+ const handleDeleteVariableWithConfirmation = useCallback(
135
+ ( itemId: string ) => {
136
+ handleDeleteVariable( itemId );
137
+ setDeleteConfirmation( null );
138
+ },
139
+ [ handleDeleteVariable ]
140
+ );
141
+
43
142
  const menuActions = [
44
143
  {
45
144
  name: __( 'Delete', 'elementor' ),
46
145
  icon: TrashIcon,
47
146
  color: 'error.main',
48
147
  onClick: ( itemId: string ) => {
49
- setDeletedVariables( [ ...deletedVariables, itemId ] );
50
- setVariables( { ...variables, [ itemId ]: { ...variables[ itemId ], deleted: true } } );
51
- setIsDirty( true );
148
+ const variable = variables[ itemId ];
149
+ if ( variable ) {
150
+ setDeleteConfirmation( { id: itemId, label: variable.label } );
151
+
152
+ const variableTypeOptions = getVariableType( variable.type );
153
+ trackVariablesManagerEvent( { action: 'delete', varType: variableTypeOptions?.variableType } );
154
+ }
52
155
  },
53
156
  },
54
157
  ];
55
158
 
56
- const handleOnChange = ( newVariables: TVariablesList ) => {
57
- setVariables( newVariables );
58
- setIsDirty( true );
59
- };
159
+ const hasVariables = Object.values( variables ).some( ( variable ) => ! variable.deleted );
60
160
 
61
161
  return (
62
162
  <ThemeProvider>
63
- <ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
64
- <Panel>
65
- <PanelHeader>
66
- <Stack width="100%" direction="column" alignItems="center">
67
- <Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
68
- <Stack width="100%" direction="row" gap={ 1 }>
69
- <PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
70
- <ColorFilterIcon fontSize="inherit" />
71
- { __( 'Variable Manager', 'elementor' ) }
72
- </PanelHeaderTitle>
73
- </Stack>
163
+ <Panel>
164
+ <PanelHeader
165
+ sx={ {
166
+ height: 'unset',
167
+ } }
168
+ >
169
+ <Stack width="100%" direction="column" alignItems="center">
170
+ <Stack p={ 1 } pl={ 2 } width="100%" direction="row" alignItems="center">
171
+ <Stack width="100%" direction="row" gap={ 1 }>
172
+ <PanelHeaderTitle sx={ { display: 'flex', alignItems: 'center', gap: 0.5 } }>
173
+ <ColorFilterIcon fontSize="inherit" />
174
+ { __( 'Variables Manager', 'elementor' ) }
175
+ </PanelHeaderTitle>
176
+ </Stack>
177
+ <Stack direction="row" gap={ 0.5 } alignItems="center">
178
+ <VariableManagerCreateMenu
179
+ onCreate={ handleCreateVariable }
180
+ variables={ variables }
181
+ menuState={ createMenuState }
182
+ />
74
183
  <CloseButton
75
- sx={ { marginLeft: 'auto' } }
76
- onClose={ () => {
77
- closePanel();
184
+ aria-label="Close"
185
+ slotProps={ { icon: { fontSize: SIZE } } }
186
+ onClick={ () => {
187
+ handleClosePanel();
78
188
  } }
79
189
  />
80
190
  </Stack>
81
- <Divider sx={ { width: '100%' } } />
82
191
  </Stack>
83
- </PanelHeader>
84
- <PanelBody
85
- sx={ {
86
- display: 'flex',
87
- flexDirection: 'column',
88
- height: '100%',
89
- } }
90
- >
192
+ <Stack width="100%" direction="row" gap={ 1 }>
193
+ <SearchField
194
+ sx={ {
195
+ display: 'flex',
196
+ flex: 1,
197
+ } }
198
+ placeholder={ __( 'Search', 'elementor' ) }
199
+ value={ searchValue }
200
+ onSearch={ handleSearch }
201
+ />
202
+ </Stack>
203
+ <Divider sx={ { width: '100%' } } />
204
+ </Stack>
205
+ </PanelHeader>
206
+ <PanelBody
207
+ sx={ {
208
+ display: 'flex',
209
+ flexDirection: 'column',
210
+ height: '100%',
211
+ } }
212
+ >
213
+ { hasVariables && (
91
214
  <VariablesManagerTable
92
215
  menuActions={ menuActions }
93
216
  variables={ variables }
94
217
  onChange={ handleOnChange }
218
+ autoEditVariableId={ autoEditVariableId }
219
+ onAutoEditComplete={ handleAutoEditComplete }
220
+ onFieldError={ setIsSaveDisabled }
221
+ />
222
+ ) }
223
+
224
+ { ! hasVariables && searchValue && (
225
+ <NoSearchResults
226
+ searchValue={ searchValue }
227
+ onClear={ () => handleSearch( '' ) }
228
+ icon={ <ColorFilterIcon fontSize="large" /> }
229
+ />
230
+ ) }
231
+
232
+ { ! hasVariables && ! searchValue && (
233
+ <EmptyState
234
+ title={ __( 'Create your first variable', 'elementor' ) }
235
+ message={ __(
236
+ 'Variables are saved attributes that you can apply anywhere on your site.',
237
+ 'elementor'
238
+ ) }
239
+ icon={ <ColorFilterIcon fontSize="large" /> }
240
+ onAdd={ createMenuState.open }
95
241
  />
96
- </PanelBody>
242
+ ) }
243
+ </PanelBody>
97
244
 
98
- <PanelFooter>
99
- <Button fullWidth size="small" color="global" variant="contained" disabled={ ! isDirty }>
245
+ <PanelFooter>
246
+ <Infotip
247
+ placement="right"
248
+ open={ !! serverError }
249
+ content={
250
+ serverError ? (
251
+ <Alert
252
+ severity={ serverError.severity ?? 'error' }
253
+ action={
254
+ serverError.action?.label ? (
255
+ <AlertAction onClick={ serverError.action.callback }>
256
+ { serverError.action.label }
257
+ </AlertAction>
258
+ ) : undefined
259
+ }
260
+ onClose={
261
+ ! serverError.action?.label
262
+ ? () => {
263
+ setServerError( null );
264
+ setIsSaveDisabled( false );
265
+ }
266
+ : undefined
267
+ }
268
+ icon={
269
+ serverError.IconComponent ? (
270
+ <serverError.IconComponent />
271
+ ) : (
272
+ <AlertTriangleFilledIcon />
273
+ )
274
+ }
275
+ >
276
+ <AlertTitle>{ serverError.message }</AlertTitle>
277
+ { serverError.action?.message }
278
+ </Alert>
279
+ ) : null
280
+ }
281
+ arrow={ false }
282
+ slotProps={ {
283
+ popper: {
284
+ modifiers: [
285
+ {
286
+ name: 'offset',
287
+ options: { offset: [ -10, 10 ] },
288
+ },
289
+ ],
290
+ },
291
+ } }
292
+ >
293
+ <Button
294
+ fullWidth
295
+ size="small"
296
+ color="global"
297
+ variant="contained"
298
+ disabled={ isSaveDisabled || ! isDirty || isSaving }
299
+ onClick={ handleSaveClick }
300
+ loading={ isSaving }
301
+ >
100
302
  { __( 'Save changes', 'elementor' ) }
101
303
  </Button>
102
- </PanelFooter>
103
- </Panel>
104
- </ErrorBoundary>
304
+ </Infotip>
305
+ </PanelFooter>
306
+ </Panel>
307
+
308
+ { deleteConfirmation && (
309
+ <DeleteConfirmationDialog
310
+ open
311
+ label={ deleteConfirmation.label }
312
+ onConfirm={ () => handleDeleteVariableWithConfirmation( deleteConfirmation.id ) }
313
+ closeDialog={ () => setDeleteConfirmation( null ) }
314
+ />
315
+ ) }
316
+
317
+ { isSaveChangesDialogOpen && (
318
+ <SaveChangesDialog>
319
+ <SaveChangesDialog.Title onClose={ closeSaveChangesDialog }>
320
+ { __( 'You have unsaved changes', 'elementor' ) }
321
+ </SaveChangesDialog.Title>
322
+ <SaveChangesDialog.Content>
323
+ <SaveChangesDialog.ContentText>
324
+ { __( 'To avoid losing your updates, save your changes before leaving.', 'elementor' ) }
325
+ </SaveChangesDialog.ContentText>
326
+ </SaveChangesDialog.Content>
327
+ <SaveChangesDialog.Actions
328
+ actions={ {
329
+ discard: {
330
+ label: __( 'Discard', 'elementor' ),
331
+ action: () => {
332
+ closeSaveChangesDialog();
333
+ closePanel();
334
+ },
335
+ },
336
+ confirm: {
337
+ label: __( 'Save', 'elementor' ),
338
+ action: async () => {
339
+ const result = await handleSaveClick();
340
+ closeSaveChangesDialog();
341
+ if ( result?.success ) {
342
+ closePanel();
343
+ }
344
+ },
345
+ },
346
+ } }
347
+ />
348
+ </SaveChangesDialog>
349
+ ) }
105
350
  </ThemeProvider>
106
351
  );
107
352
  }
108
353
 
109
- const CloseButton = ( { onClose, ...props }: IconButtonProps & { onClose: () => void } ) => (
110
- <IconButton size="small" color="secondary" onClick={ onClose } aria-label="Close" { ...props }>
111
- <XIcon fontSize="small" />
112
- </IconButton>
113
- );
114
-
115
- const ErrorBoundaryFallback = () => (
116
- <Box role="alert" sx={ { minHeight: '100%', p: 2 } }>
117
- <Alert severity="error" sx={ { mb: 2, maxWidth: 400, textAlign: 'center' } }>
118
- <strong>{ __( 'Something went wrong', 'elementor' ) }</strong>
119
- </Alert>
120
- </Box>
121
- );
122
-
123
354
  const usePreventUnload = ( isDirty: boolean ) => {
124
355
  useEffect( () => {
125
356
  const handleBeforeUnload = ( event: BeforeUnloadEvent ) => {
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { createElement, useState } from 'react';
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 = ( { menuActions, variables, onChange: handleOnChange }: Props ) => {
34
- const [ ids, setIds ] = useState< string[] >( Object.keys( variables ) );
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={ setIds }
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';
@@ -123,7 +173,6 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
123
173
  },
124
174
  } }
125
175
  style={ { ...itemStyle, ...triggerStyle } }
126
- disableDivider={ isDragOverlay || index === rows.length - 1 }
127
176
  >
128
177
  <VariableTableCell noPadding width={ 10 } maxWidth={ 10 }>
129
178
  <IconButton
@@ -148,15 +197,34 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
148
197
  }
149
198
  } }
150
199
  prefixElement={ createElement( row.icon, { fontSize: 'inherit' } ) }
151
- editableElement={ ( { value, onChange } ) => (
200
+ editableElement={ ( {
201
+ value,
202
+ onChange,
203
+ onValidationChange,
204
+ error,
205
+ } ) => (
152
206
  <LabelField
153
207
  id={ 'variable-label-' + row.id }
154
208
  size="tiny"
155
209
  value={ value }
156
210
  onChange={ onChange }
211
+ onErrorChange={ ( errorMsg ) => {
212
+ onValidationChange?.( errorMsg );
213
+ onFieldError?.( !! errorMsg );
214
+ } }
215
+ error={ error }
157
216
  focusOnShow
217
+ selectOnShow={ autoEditVariableId === row.id }
218
+ showWarningInfotip={ true }
219
+ variables={ variables }
158
220
  />
159
221
  ) }
222
+ autoEdit={ autoEditVariableId === row.id }
223
+ onRowRef={ handleRowRef( row.id ) }
224
+ onAutoEditComplete={
225
+ autoEditVariableId === row.id ? onAutoEditComplete : undefined
226
+ }
227
+ fieldType="label"
160
228
  >
161
229
  <EllipsisWithTooltip
162
230
  title={ row.name }
@@ -177,12 +245,34 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
177
245
  } );
178
246
  }
179
247
  } }
180
- editableElement={ row.valueField }
248
+ editableElement={ ( {
249
+ value,
250
+ onChange,
251
+ onValidationChange,
252
+ error,
253
+ } ) =>
254
+ row.valueField( {
255
+ value,
256
+ onChange,
257
+ onValidationChange: ( errorMsg ) => {
258
+ onValidationChange?.( errorMsg );
259
+ onFieldError?.( !! errorMsg );
260
+ },
261
+ error,
262
+ } )
263
+ }
264
+ onRowRef={ handleRowRef( row.id ) }
265
+ gap={ 0.25 }
266
+ fieldType="value"
181
267
  >
182
268
  { row.startIcon && row.startIcon( { value: row.value } ) }
183
269
  <EllipsisWithTooltip
184
270
  title={ row.value }
185
- sx={ { border: '4px solid transparent' } }
271
+ sx={ {
272
+ border: '4px solid transparent',
273
+ lineHeight: '1',
274
+ pt: 0.25,
275
+ } }
186
276
  >
187
277
  { row.value }
188
278
  </EllipsisWithTooltip>
@@ -214,3 +304,10 @@ export const VariablesManagerTable = ( { menuActions, variables, onChange: handl
214
304
  </TableContainer>
215
305
  );
216
306
  };
307
+ function sortVariablesOrder( variables: TVariablesList ): ( a: string, b: string ) => number {
308
+ return ( a, b ) => {
309
+ const orderA = variables[ a ]?.order ?? Number.MAX_SAFE_INTEGER;
310
+ const orderB = variables[ b ]?.order ?? Number.MAX_SAFE_INTEGER;
311
+ return orderA - orderB;
312
+ };
313
+ }