@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-variables",
3
- "version": "3.33.0-98",
3
+ "version": "3.34.2",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,19 +39,21 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "3.33.0-98",
43
- "@elementor/editor-canvas": "3.33.0-98",
44
- "@elementor/editor-controls": "3.33.0-98",
45
- "@elementor/editor-current-user": "3.33.0-98",
46
- "@elementor/editor-editing-panel": "3.33.0-98",
47
- "@elementor/editor-panels": "3.33.0-98",
48
- "@elementor/editor-props": "3.33.0-98",
49
- "@elementor/editor-ui": "3.33.0-98",
50
- "@elementor/editor-v1-adapters": "3.33.0-98",
51
- "@elementor/http-client": "3.33.0-98",
52
- "@elementor/icons": "1.46.0",
53
- "@elementor/schema": "3.33.0-98",
54
- "@elementor/ui": "1.36.12",
42
+ "@elementor/editor": "3.34.2",
43
+ "@elementor/editor-canvas": "3.34.2",
44
+ "@elementor/editor-controls": "3.34.2",
45
+ "@elementor/editor-current-user": "3.34.2",
46
+ "@elementor/editor-editing-panel": "3.34.2",
47
+ "@elementor/editor-mcp": "3.34.2",
48
+ "@elementor/editor-panels": "3.34.2",
49
+ "@elementor/editor-props": "3.34.2",
50
+ "@elementor/editor-ui": "3.34.2",
51
+ "@elementor/editor-v1-adapters": "3.34.2",
52
+ "@elementor/http-client": "3.34.2",
53
+ "@elementor/icons": "^1.61.0",
54
+ "@elementor/mixpanel": "3.34.2",
55
+ "@elementor/schema": "3.34.2",
56
+ "@elementor/ui": "1.36.17",
55
57
  "@wordpress/i18n": "^5.13.0"
56
58
  },
57
59
  "peerDependencies": {
package/src/api.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { httpService } from '@elementor/http-client';
2
2
 
3
+ import { type OperationType } from './batch-operations';
4
+
3
5
  const BASE_PATH = 'elementor/v1/variables';
4
6
 
5
7
  type RestoreVariablePayload = {
@@ -8,6 +10,24 @@ type RestoreVariablePayload = {
8
10
  value?: string;
9
11
  };
10
12
 
13
+ export type BatchOperation = {
14
+ type: OperationType;
15
+ id?: string;
16
+ variable?: {
17
+ id?: string;
18
+ type?: string;
19
+ label?: string;
20
+ value?: string;
21
+ };
22
+ label?: string;
23
+ value?: string;
24
+ };
25
+
26
+ export type BatchPayload = {
27
+ watermark: number;
28
+ operations: BatchOperation[];
29
+ };
30
+
11
31
  export const apiClient = {
12
32
  list: () => {
13
33
  return httpService().get( BASE_PATH + '/list' );
@@ -46,4 +66,8 @@ export const apiClient = {
46
66
 
47
67
  return httpService().post( BASE_PATH + '/restore', payload );
48
68
  },
69
+
70
+ batch: ( payload: BatchPayload ) => {
71
+ return httpService().post( BASE_PATH + '/batch', payload );
72
+ },
49
73
  };
@@ -0,0 +1,86 @@
1
+ import { type BatchOperation } from './api';
2
+ import { type TVariable, type TVariablesList } from './storage';
3
+
4
+ export type OperationType = 'create' | 'update' | 'delete' | 'restore';
5
+
6
+ export type OperationResult = {
7
+ id: string;
8
+ type: OperationType;
9
+ variable?: TVariable & { id: string };
10
+ deleted?: boolean;
11
+ };
12
+
13
+ export const generateTempId = (): string => {
14
+ const timestamp = Date.now().toString( 36 );
15
+ const random = Math.random().toString( 36 ).substring( 2, 8 );
16
+ return `tmp-${ timestamp }-${ random }`;
17
+ };
18
+
19
+ export const isTempId = ( id: string ): boolean => {
20
+ return id.startsWith( 'tmp-' );
21
+ };
22
+
23
+ export const buildOperationsArray = (
24
+ originalVariables: TVariablesList,
25
+ currentVariables: TVariablesList
26
+ ): BatchOperation[] => {
27
+ const operations: BatchOperation[] = [];
28
+
29
+ Object.entries( currentVariables ).forEach( ( [ id, variable ] ) => {
30
+ if ( isTempId( id ) ) {
31
+ operations.push( {
32
+ type: 'create',
33
+ variable: {
34
+ ...variable,
35
+ id,
36
+ },
37
+ } );
38
+ } else if ( originalVariables[ id ] ) {
39
+ const original = originalVariables[ id ];
40
+
41
+ if ( original.deleted && ! variable.deleted ) {
42
+ operations.push( {
43
+ type: 'restore',
44
+ id,
45
+ ...( original.label !== variable.label && { label: variable.label } ),
46
+ ...( original.value !== variable.value && { value: variable.value } ),
47
+ } );
48
+ } else if (
49
+ ! variable.deleted &&
50
+ ( original.label !== variable.label ||
51
+ original.value !== variable.value ||
52
+ original.order !== variable.order )
53
+ ) {
54
+ operations.push( {
55
+ type: 'update',
56
+ id,
57
+ variable: {
58
+ ...( original.label !== variable.label && { label: variable.label } ),
59
+ ...( original.value !== variable.value && { value: variable.value } ),
60
+ ...( original.order !== variable.order && { order: variable.order } ),
61
+ },
62
+ } );
63
+ }
64
+ }
65
+ } );
66
+
67
+ Object.entries( originalVariables ).forEach( ( [ id, variable ] ) => {
68
+ if ( isTempId( id ) || variable.deleted ) {
69
+ return;
70
+ }
71
+
72
+ const currentVariable = currentVariables[ id ];
73
+
74
+ if ( ! currentVariable || currentVariable.deleted ) {
75
+ operations.push( {
76
+ type: 'delete',
77
+ id,
78
+ } );
79
+ }
80
+ } );
81
+
82
+ return operations.filter( ( op ) => {
83
+ const id = op.id || op.variable?.id;
84
+ return id && ! ( isTempId( id ) && currentVariables[ id ]?.deleted );
85
+ } );
86
+ };
@@ -30,6 +30,7 @@ export const ColorField = ( { value, onChange, onValidationChange }: ColorFieldP
30
30
 
31
31
  return (
32
32
  <UnstableColorField
33
+ id="color-variable-field"
33
34
  size="tiny"
34
35
  fullWidth
35
36
  value={ color }
@@ -68,12 +68,13 @@ export const FontField = ( { value, onChange, onValidationChange }: FontFieldPro
68
68
  { ...bindPopover( fontPopoverState ) }
69
69
  >
70
70
  <ItemSelector
71
+ id="font-family-variables-selector"
71
72
  itemsList={ mapFontSubs }
72
73
  selectedItem={ fontFamily }
73
74
  onItemChange={ handleFontFamilyChange }
74
75
  onClose={ fontPopoverState.close }
75
76
  sectionWidth={ sectionWidth }
76
- title={ __( 'Font Family', 'elementor' ) }
77
+ title={ __( 'Font family', 'elementor' ) }
77
78
  itemStyle={ ( item ) => ( { fontFamily: item.value } ) }
78
79
  onDebounce={ enqueueFont }
79
80
  icon={ TextIcon as React.ElementType< { fontSize: string } > }
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
- import { useState } from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { WarningInfotip } from '@elementor/editor-ui';
3
4
  import { TextField, type TextFieldProps } from '@elementor/ui';
4
5
 
5
- import { validateLabel, VARIABLE_LABEL_MAX_LENGTH } from '../../utils/validations';
6
+ import { type TVariablesList } from '../../storage';
7
+ import { labelHint, validateLabel, VARIABLE_LABEL_MAX_LENGTH } from '../../utils/validations';
6
8
  function isLabelEqual( a: string, b: string ) {
7
9
  return a.trim().toLowerCase() === b.trim().toLowerCase();
8
10
  }
@@ -21,7 +23,7 @@ export const useLabelError = ( initialError?: LabelErrorProps ) => {
21
23
  };
22
24
  };
23
25
 
24
- type LabelFieldProps = {
26
+ export type LabelFieldProps = {
25
27
  value: string;
26
28
  error?: LabelErrorProps;
27
29
  onChange: ( value: string ) => void;
@@ -29,6 +31,9 @@ type LabelFieldProps = {
29
31
  onErrorChange?: ( errorMsg: string ) => void;
30
32
  size?: TextFieldProps[ 'size' ];
31
33
  focusOnShow?: boolean;
34
+ selectOnShow?: boolean;
35
+ showWarningInfotip?: boolean;
36
+ variables?: TVariablesList;
32
37
  };
33
38
 
34
39
  export const LabelField = ( {
@@ -39,14 +44,19 @@ export const LabelField = ( {
39
44
  onErrorChange,
40
45
  size = 'tiny',
41
46
  focusOnShow = false,
47
+ selectOnShow = false,
48
+ showWarningInfotip = false,
49
+ variables,
42
50
  }: LabelFieldProps ) => {
43
51
  const [ label, setLabel ] = useState( value );
44
52
  const [ errorMessage, setErrorMessage ] = useState( '' );
45
53
 
54
+ const fieldRef = useRef< HTMLElement >( null );
55
+
46
56
  const handleChange = ( newValue: string ) => {
47
57
  setLabel( newValue );
48
58
 
49
- const errorMsg = validateLabel( newValue );
59
+ const errorMsg = validateLabel( newValue, variables );
50
60
 
51
61
  setErrorMessage( errorMsg );
52
62
  onErrorChange?.( errorMsg );
@@ -59,17 +69,43 @@ export const LabelField = ( {
59
69
  errorMsg = error.message;
60
70
  }
61
71
 
62
- return (
72
+ const hintMsg = ! errorMsg ? labelHint( label ) : '';
73
+
74
+ const textField = (
63
75
  <TextField
76
+ ref={ fieldRef }
64
77
  id={ id }
65
78
  size={ size }
66
79
  fullWidth
67
80
  value={ label }
68
81
  error={ !! errorMsg }
69
82
  onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => handleChange( e.target.value ) }
70
- inputProps={ { maxLength: VARIABLE_LABEL_MAX_LENGTH } }
83
+ inputProps={ {
84
+ maxLength: VARIABLE_LABEL_MAX_LENGTH,
85
+ ...( selectOnShow && { onFocus: ( e: React.FocusEvent< HTMLInputElement > ) => e.target.select() } ),
86
+ 'aria-label': 'Name',
87
+ } }
71
88
  // eslint-disable-next-line jsx-a11y/no-autofocus
72
89
  autoFocus={ focusOnShow }
73
90
  />
74
91
  );
92
+
93
+ if ( showWarningInfotip ) {
94
+ const tooltipWidth = Math.max( 240, fieldRef.current?.getBoundingClientRect().width ?? 240 );
95
+
96
+ return (
97
+ <WarningInfotip
98
+ open={ Boolean( errorMsg || hintMsg ) }
99
+ text={ errorMsg || hintMsg }
100
+ placement="bottom-start"
101
+ width={ tooltipWidth }
102
+ offset={ [ 0, -15 ] }
103
+ { ...( hintMsg && { hasError: false } ) }
104
+ >
105
+ { textField }
106
+ </WarningInfotip>
107
+ );
108
+ }
109
+
110
+ return textField;
75
111
  };
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useSectionWidth } from '@elementor/editor-editing-panel';
3
- import { Alert, AlertAction, AlertTitle, ClickAwayListener } from '@elementor/ui';
2
+ import { Alert, AlertAction, AlertTitle, ClickAwayListener, Typography } from '@elementor/ui';
4
3
  import { __ } from '@wordpress/i18n';
5
4
 
6
5
  type DeletedVariableAlertProps = {
@@ -11,8 +10,6 @@ type DeletedVariableAlertProps = {
11
10
  };
12
11
 
13
12
  export const DeletedVariableAlert = ( { onClose, onUnlink, onRestore, label }: DeletedVariableAlertProps ) => {
14
- const sectionWidth = useSectionWidth();
15
-
16
13
  return (
17
14
  <ClickAwayListener onClickAway={ onClose }>
18
15
  <Alert
@@ -33,14 +30,21 @@ export const DeletedVariableAlert = ( { onClose, onUnlink, onRestore, label }: D
33
30
  ) }
34
31
  </>
35
32
  }
36
- sx={ { width: sectionWidth } }
33
+ sx={ { maxWidth: 300 } }
37
34
  >
38
35
  <AlertTitle>{ __( 'Deleted variable', 'elementor' ) }</AlertTitle>
39
- { __( 'The variable', 'elementor' ) } &apos;{ label }&apos;{ ' ' }
40
- { __(
41
- 'has been deleted, but it is still referenced in this location. You may restore the variable or unlink it to assign a different value.',
42
- 'elementor'
43
- ) }
36
+ <Typography variant="body2" color="textPrimary">
37
+ { __( 'The variable', 'elementor' ) }
38
+ &nbsp;&apos;
39
+ <Typography variant="body2" component="span" sx={ { lineBreak: 'anywhere' } }>
40
+ { label }
41
+ </Typography>
42
+ &apos;&nbsp;
43
+ { __(
44
+ 'has been deleted, but it is still referenced in this location. You may restore the variable or unlink it to assign a different value.',
45
+ 'elementor'
46
+ ) }
47
+ </Typography>
44
48
  </Alert>
45
49
  </ClickAwayListener>
46
50
  );
@@ -6,33 +6,28 @@ import { usePermissions } from '../../hooks/use-permissions';
6
6
 
7
7
  type Props = {
8
8
  icon?: React.ReactNode;
9
- title?: string;
9
+ title: string;
10
+ message: string;
10
11
  onAdd?: () => void;
11
12
  };
12
13
 
13
- export const NoVariables = ( { icon, title, onAdd }: Props ) => {
14
+ export const EmptyState = ( { icon, title, message, onAdd }: Props ) => {
14
15
  const canAdd = usePermissions().canAdd();
15
16
 
16
17
  return (
17
18
  <Stack
18
19
  gap={ 1 }
19
20
  alignItems="center"
20
- justifyContent="center"
21
+ justifyContent="flex-start"
21
22
  height="100%"
22
23
  color="text.secondary"
23
- sx={ { p: 2.5, pb: 5.5 } }
24
+ sx={ { p: 2.5, pt: 8, pb: 5.5 } }
24
25
  >
25
26
  { icon }
26
27
 
27
28
  { canAdd ? (
28
29
  <>
29
- <NoVariablesContent
30
- title={ title || __( 'Create your first variable', 'elementor' ) }
31
- message={ __(
32
- 'Variables are saved attributes that you can apply anywhere on your site.',
33
- 'elementor'
34
- ) }
35
- />
30
+ <Content title={ title } message={ message } />
36
31
  { onAdd && (
37
32
  <Button variant="outlined" color="secondary" size="small" onClick={ onAdd }>
38
33
  { __( 'Create a variable', 'elementor' ) }
@@ -40,7 +35,7 @@ export const NoVariables = ( { icon, title, onAdd }: Props ) => {
40
35
  ) }
41
36
  </>
42
37
  ) : (
43
- <NoVariablesContent
38
+ <Content
44
39
  title={ __( 'There are no variables', 'elementor' ) }
45
40
  message={ __( 'With your current role, you can only connect and detach variables.', 'elementor' ) }
46
41
  />
@@ -54,7 +49,7 @@ type NoVariablesContentProps = {
54
49
  message: string;
55
50
  };
56
51
 
57
- function NoVariablesContent( { title, message }: NoVariablesContentProps ) {
52
+ function Content( { title, message }: NoVariablesContentProps ) {
58
53
  return (
59
54
  <>
60
55
  <Typography align="center" variant="subtitle2">
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { EllipsisWithTooltip, type VirtualizedItem } from '@elementor/editor-ui';
3
3
  import { EditIcon } from '@elementor/icons';
4
- import { Box, IconButton, ListItemIcon, Typography } from '@elementor/ui';
4
+ import { Box, IconButton, ListItemIcon, Tooltip, Typography } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  const SIZE = 'tiny';
8
+ const EDIT_LABEL = __( 'Edit variable', 'elementor' );
8
9
 
9
10
  export const MenuItemContent = < T, V extends string >( { item }: { item: VirtualizedItem< T, V > } ) => {
10
11
  const onEdit = item.onEdit as ( ( value: V ) => void ) | undefined;
@@ -41,16 +42,18 @@ export const MenuItemContent = < T, V extends string >( { item }: { item: Virtua
41
42
  ) }
42
43
  </Box>
43
44
  { !! onEdit && (
44
- <IconButton
45
- sx={ { mx: 1, opacity: '0' } }
46
- onClick={ ( e: React.MouseEvent< HTMLButtonElement > ) => {
47
- e.stopPropagation();
48
- onEdit( item.value );
49
- } }
50
- aria-label={ __( 'Edit', 'elementor' ) }
51
- >
52
- <EditIcon color="action" fontSize={ SIZE } />
53
- </IconButton>
45
+ <Tooltip placement="top" title={ EDIT_LABEL }>
46
+ <IconButton
47
+ sx={ { mx: 1, opacity: '0' } }
48
+ onClick={ ( e: React.MouseEvent< HTMLButtonElement > ) => {
49
+ e.stopPropagation();
50
+ onEdit( item.value );
51
+ } }
52
+ aria-label={ EDIT_LABEL }
53
+ >
54
+ <EditIcon color="action" fontSize={ SIZE } />
55
+ </IconButton>
56
+ </Tooltip>
54
57
  ) }
55
58
  </>
56
59
  );
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useSectionWidth } from '@elementor/editor-editing-panel';
3
- import { Alert, AlertAction, AlertTitle, ClickAwayListener } from '@elementor/ui';
2
+ import { Alert, AlertAction, AlertTitle, ClickAwayListener, Typography } from '@elementor/ui';
4
3
  import { __ } from '@wordpress/i18n';
5
4
 
6
5
  type AlertProps = {
@@ -22,8 +21,6 @@ const i18n = {
22
21
  };
23
22
 
24
23
  export const MismatchVariableAlert = ( { onClose, onClear, triggerSelect }: AlertProps ) => {
25
- const sectionWidth = useSectionWidth();
26
-
27
24
  return (
28
25
  <ClickAwayListener onClickAway={ onClose }>
29
26
  <Alert
@@ -44,13 +41,12 @@ export const MismatchVariableAlert = ( { onClose, onClear, triggerSelect }: Aler
44
41
  ) }
45
42
  </>
46
43
  }
47
- sx={ {
48
- width: sectionWidth,
49
- minWidth: 300,
50
- } }
44
+ sx={ { maxWidth: 300 } }
51
45
  >
52
46
  <AlertTitle>{ i18n.title }</AlertTitle>
53
- { i18n.message }
47
+ <Typography variant="body2" color="textPrimary">
48
+ { i18n.message }
49
+ </Typography>
54
50
  </Alert>
55
51
  </ClickAwayListener>
56
52
  );
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useSectionWidth } from '@elementor/editor-editing-panel';
3
- import { Alert, AlertAction, AlertTitle, ClickAwayListener } from '@elementor/ui';
2
+ import { Alert, AlertAction, AlertTitle, ClickAwayListener, Typography } from '@elementor/ui';
4
3
  import { __ } from '@wordpress/i18n';
5
4
 
6
5
  type MissingVariableAlertProps = {
@@ -9,8 +8,6 @@ type MissingVariableAlertProps = {
9
8
  };
10
9
 
11
10
  export const MissingVariableAlert = ( { onClose, onClear }: MissingVariableAlertProps ) => {
12
- const sectionWidth = useSectionWidth();
13
-
14
11
  return (
15
12
  <ClickAwayListener onClickAway={ onClose }>
16
13
  <Alert
@@ -26,13 +23,15 @@ export const MissingVariableAlert = ( { onClose, onClear }: MissingVariableAlert
26
23
  ) }
27
24
  </>
28
25
  }
29
- sx={ { width: sectionWidth } }
26
+ sx={ { maxWidth: 300 } }
30
27
  >
31
28
  <AlertTitle>{ __( 'This variable is missing', 'elementor' ) }</AlertTitle>
32
- { __(
33
- 'It may have been deleted. Try clearing this field and select a different value or variable.',
34
- 'elementor'
35
- ) }
29
+ <Typography variant="body2" color="textPrimary">
30
+ { __(
31
+ 'It may have been deleted. Try clearing this field and select a different value or variable.',
32
+ 'elementor'
33
+ ) }
34
+ </Typography>
36
35
  </Alert>
37
36
  </ClickAwayListener>
38
37
  );
@@ -14,10 +14,9 @@ export const NoSearchResults = ( { searchValue, onClear, icon }: Props ) => {
14
14
  gap={ 1 }
15
15
  alignItems="center"
16
16
  justifyContent="center"
17
- height="100%"
18
17
  p={ 2.5 }
19
18
  color="text.secondary"
20
- sx={ { pb: 3.5 } }
19
+ sx={ { pb: 3.5, pt: 8 } }
21
20
  >
22
21
  { icon }
23
22
  <Typography align="center" variant="subtitle2">
@@ -4,6 +4,7 @@ import { Box, IconButton, Stack, Tooltip, Typography, UnstableTag as Tag, type U
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
6
  export const SIZE = 'tiny';
7
+ const UNLINK_LABEL = __( 'Unlink variable', 'elementor' );
7
8
 
8
9
  interface VariableTagProps extends UnstableTagProps {
9
10
  onUnlink?: () => void;
@@ -14,9 +15,11 @@ export const AssignedTag = ( { startIcon, label, onUnlink, ...props }: VariableT
14
15
 
15
16
  if ( onUnlink ) {
16
17
  actions.push(
17
- <IconButton key="unlink" size={ SIZE } onClick={ onUnlink } aria-label={ __( 'Unlink', 'elementor' ) }>
18
- <DetachIcon fontSize={ SIZE } />
19
- </IconButton>
18
+ <Tooltip key="unlink" title={ UNLINK_LABEL } placement="bottom">
19
+ <IconButton size={ SIZE } onClick={ onUnlink } aria-label={ UNLINK_LABEL }>
20
+ <DetachIcon fontSize={ SIZE } />
21
+ </IconButton>
22
+ </Tooltip>
20
23
  );
21
24
  }
22
25
 
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { AlertTriangleFilledIcon } from '@elementor/icons';
3
+ import { Box, Chip, type ChipProps, type Theme, Tooltip, Typography } from '@elementor/ui';
4
+
5
+ interface WarningVariableTagProps extends ChipProps {
6
+ label: string;
7
+ suffix?: string;
8
+ }
9
+
10
+ export const WarningVariableTag = React.forwardRef< HTMLDivElement, WarningVariableTagProps >(
11
+ ( { label, suffix, onClick, icon, ...props }, ref ) => {
12
+ const displayText = suffix ? `${ label } (${ suffix })` : label;
13
+
14
+ return (
15
+ <Chip
16
+ ref={ ref }
17
+ size="tiny"
18
+ color="warning"
19
+ shape="rounded"
20
+ variant="standard"
21
+ onClick={ onClick }
22
+ icon={ <AlertTriangleFilledIcon /> }
23
+ label={
24
+ <Tooltip title={ displayText } placement="top">
25
+ <Box sx={ { display: 'inline-grid', minWidth: 0 } }>
26
+ <Typography variant="caption" noWrap sx={ { lineHeight: 1.34 } }>
27
+ { displayText }
28
+ </Typography>
29
+ </Box>
30
+ </Tooltip>
31
+ }
32
+ sx={ {
33
+ height: ( theme: Theme ) => theme.spacing( 3.5 ),
34
+ borderRadius: ( theme: Theme ) => theme.spacing( 1 ),
35
+ justifyContent: 'flex-start',
36
+ width: '100%',
37
+ } }
38
+ { ...props }
39
+ />
40
+ );
41
+ }
42
+ );
43
+
44
+ WarningVariableTag.displayName = 'WarningVariableTag';
@@ -3,16 +3,18 @@ import { useId, useRef, useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
4
  import { type PropTypeKey } from '@elementor/editor-props';
5
5
  import { Backdrop, bindPopover, Box, Infotip, Popover, usePopupState } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
6
7
 
7
8
  import { VariableTypeProvider } from '../../../context/variable-type-context';
8
9
  import { usePermissions } from '../../../hooks/use-permissions';
9
10
  import { restoreVariable } from '../../../hooks/use-prop-variables';
11
+ import { resolveBoundPropAndSetValue } from '../../../hooks/use-variable-bound-prop';
10
12
  import { type Variable } from '../../../types';
11
13
  import { createUnlinkHandler } from '../../../utils/unlink-variable';
12
14
  import { getVariableType } from '../../../variables-registry/variable-type-registry';
13
15
  import { VariableRestore } from '../../variable-restore';
14
16
  import { DeletedVariableAlert } from '../deleted-variable-alert';
15
- import { DeletedTag } from '../tags/deleted-tag';
17
+ import { WarningVariableTag } from '../tags/warning-variable-tag';
16
18
 
17
19
  type Props = {
18
20
  variable: Variable;
@@ -27,7 +29,7 @@ type Handlers = {
27
29
  export const DeletedVariable = ( { variable, propTypeKey }: Props ) => {
28
30
  const { propTypeUtil } = getVariableType( propTypeKey );
29
31
 
30
- const { setValue } = useBoundProp();
32
+ const boundProp = useBoundProp();
31
33
 
32
34
  const userPermissions = usePermissions();
33
35
 
@@ -46,7 +48,7 @@ export const DeletedVariable = ( { variable, propTypeKey }: Props ) => {
46
48
  const handlers: Handlers = {};
47
49
 
48
50
  if ( userPermissions.canUnlink() ) {
49
- handlers.onUnlink = createUnlinkHandler( variable, propTypeKey, setValue );
51
+ handlers.onUnlink = createUnlinkHandler( variable, propTypeKey, boundProp.setValue );
50
52
  }
51
53
 
52
54
  if ( userPermissions.canRestore() ) {
@@ -56,8 +58,9 @@ export const DeletedVariable = ( { variable, propTypeKey }: Props ) => {
56
58
  }
57
59
 
58
60
  restoreVariable( variable.key )
59
- .then( ( key ) => {
60
- setValue( propTypeUtil.create( key ) );
61
+ .then( ( id ) => {
62
+ resolveBoundPropAndSetValue( propTypeUtil.create( id ), boundProp );
63
+
61
64
  closeInfotip();
62
65
  } )
63
66
  .catch( () => {
@@ -101,7 +104,11 @@ export const DeletedVariable = ( { variable, propTypeKey }: Props ) => {
101
104
  },
102
105
  } }
103
106
  >
104
- <DeletedTag label={ variable.label } onClick={ toggleInfotip } />
107
+ <WarningVariableTag
108
+ label={ variable.label }
109
+ onClick={ toggleInfotip }
110
+ suffix={ __( 'deleted', 'elementor' ) }
111
+ />
105
112
  </Infotip>
106
113
 
107
114
  <Popover