@elementor/editor-variables 3.33.0-99 → 3.35.0-325

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 (60) hide show
  1. package/dist/index.d.mts +17 -4
  2. package/dist/index.d.ts +17 -4
  3. package/dist/index.js +1907 -810
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1854 -748
  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 +13 -4
  25. package/src/components/variable-edit.tsx +12 -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 +118 -0
  32. package/src/components/variables-manager/variables-manager-panel.tsx +290 -59
  33. package/src/components/variables-manager/variables-manager-table.tsx +137 -15
  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 +28 -9
  37. package/src/hooks/use-variable-bound-prop.ts +42 -0
  38. package/src/index.ts +1 -0
  39. package/src/init.ts +9 -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 +4 -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/unlink-variable.ts +1 -1
  53. package/src/utils/validations.ts +72 -3
  54. package/src/variables-registry/create-variable-type-registry.ts +20 -8
  55. package/src/variables-registry/variable-type-registry.ts +2 -1
  56. package/src/components/ui/tags/deleted-tag.tsx +0 -37
  57. package/src/components/ui/tags/mismatch-tag.tsx +0 -37
  58. package/src/components/ui/tags/missing-tag.tsx +0 -25
  59. /package/src/components/variables-manager/{variable-edit-menu.tsx → ui/variable-edit-menu.tsx} +0 -0
  60. /package/src/components/variables-manager/{variable-table-cell.tsx → ui/variable-table-cell.tsx} +0 -0
@@ -2,18 +2,19 @@ import * as React from 'react';
2
2
  import { useId, useRef, useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
4
  import { Backdrop, bindPopover, Box, Infotip, Popover, usePopupState } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { type Variable } from '../../../types';
7
8
  import { VariableSelectionPopover } from '../../variable-selection-popover';
8
9
  import { MismatchVariableAlert } from '../mismatch-variable-alert';
9
- import { MismatchTag } from '../tags/mismatch-tag';
10
+ import { WarningVariableTag } from '../tags/warning-variable-tag';
10
11
 
11
12
  type Props = {
12
13
  variable: Variable;
13
14
  };
14
15
 
15
16
  export const MismatchVariable = ( { variable }: Props ) => {
16
- const { setValue } = useBoundProp();
17
+ const { setValue, value } = useBoundProp();
17
18
 
18
19
  const anchorRef = useRef< HTMLDivElement >( null );
19
20
 
@@ -40,6 +41,8 @@ export const MismatchVariable = ( { variable }: Props ) => {
40
41
  setValue( null );
41
42
  };
42
43
 
44
+ const showClearButton = !! value;
45
+
43
46
  return (
44
47
  <Box ref={ anchorRef }>
45
48
  { infotipVisible && <Backdrop open onClick={ closeInfotip } invisible /> }
@@ -52,7 +55,7 @@ export const MismatchVariable = ( { variable }: Props ) => {
52
55
  content={
53
56
  <MismatchVariableAlert
54
57
  onClose={ closeInfotip }
55
- onClear={ clearValue }
58
+ onClear={ showClearButton ? clearValue : undefined }
56
59
  triggerSelect={ triggerSelect }
57
60
  />
58
61
  }
@@ -67,7 +70,11 @@ export const MismatchVariable = ( { variable }: Props ) => {
67
70
  },
68
71
  } }
69
72
  >
70
- <MismatchTag label={ variable.label } onClick={ toggleInfotip } />
73
+ <WarningVariableTag
74
+ label={ variable.label }
75
+ onClick={ toggleInfotip }
76
+ suffix={ __( 'changed', 'elementor' ) }
77
+ />
71
78
  </Infotip>
72
79
 
73
80
  <Popover
@@ -5,7 +5,7 @@ import { Backdrop, Infotip } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { MissingVariableAlert } from '../missing-variable-alert';
8
- import { MissingTag } from '../tags/missing-tag';
8
+ import { WarningVariableTag } from '../tags/warning-variable-tag';
9
9
 
10
10
  export const MissingVariable = () => {
11
11
  const { setValue } = useBoundProp();
@@ -37,7 +37,7 @@ export const MissingVariable = () => {
37
37
  },
38
38
  } }
39
39
  >
40
- <MissingTag label={ __( 'Missing variable', 'elementor' ) } onClick={ toggleInfotip } />
40
+ <WarningVariableTag label={ __( 'Missing variable', 'elementor' ) } onClick={ toggleInfotip } />
41
41
  </Infotip>
42
42
  </>
43
43
  );
@@ -10,6 +10,7 @@ import { __ } from '@wordpress/i18n';
10
10
  import { useVariableType } from '../context/variable-type-context';
11
11
  import { useInitialValue } from '../hooks/use-initial-value';
12
12
  import { createVariable } from '../hooks/use-prop-variables';
13
+ import { useVariableBoundProp } from '../hooks/use-variable-bound-prop';
13
14
  import { trackVariableEvent } from '../utils/tracking';
14
15
  import { ERROR_MESSAGES, labelHint, mapServerError } from '../utils/validations';
15
16
  import { LabelField, useLabelError } from './fields/label-field';
@@ -25,7 +26,7 @@ type Props = {
25
26
  export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
26
27
  const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
27
28
 
28
- const { setValue: setVariable, path } = useBoundProp( propTypeUtil );
29
+ const { setVariableValue: setVariable, path } = useVariableBoundProp();
29
30
  const { propType } = useBoundProp();
30
31
 
31
32
  const initialValue = useInitialValue();
@@ -34,6 +35,7 @@ export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
34
35
  const [ label, setLabel ] = useState( '' );
35
36
  const [ errorMessage, setErrorMessage ] = useState( '' );
36
37
  const [ valueFieldError, setValueFieldError ] = useState( '' );
38
+ const [ propTypeKey, setPropTypeKey ] = useState( propTypeUtil.key );
37
39
 
38
40
  const { labelFieldError, setLabelFieldError } = useLabelError();
39
41
 
@@ -53,7 +55,7 @@ export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
53
55
  createVariable( {
54
56
  value,
55
57
  label,
56
- type: propTypeUtil.key,
58
+ type: propTypeKey,
57
59
  } )
58
60
  .then( ( key ) => {
59
61
  setVariable( key );
@@ -141,9 +143,10 @@ export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
141
143
  />
142
144
  </FormField>
143
145
  <FormField errorMsg={ valueFieldError } label={ __( 'Value', 'elementor' ) }>
144
- <Typography variant="h5">
146
+ <Typography variant="h5" id="variable-value-wrapper">
145
147
  <ValueField
146
148
  value={ value }
149
+ onPropTypeKeyChange={ ( key: string ) => setPropTypeKey( key ) }
147
150
  onChange={ ( newValue ) => {
148
151
  setValue( newValue );
149
152
  setErrorMessage( '' );
@@ -159,7 +162,13 @@ export const VariableCreation = ( { onGoBack, onClose }: Props ) => {
159
162
  </PopoverContent>
160
163
 
161
164
  <CardActions sx={ { pt: 0.5, pb: 1 } }>
162
- <Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleCreateAndTrack }>
165
+ <Button
166
+ id="create-variable-button"
167
+ size="small"
168
+ variant="contained"
169
+ disabled={ isSubmitDisabled }
170
+ onClick={ handleCreateAndTrack }
171
+ >
163
172
  { __( 'Create', 'elementor' ) }
164
173
  </Button>
165
174
  </CardActions>
@@ -5,12 +5,13 @@ import { useSuppressedMessage } from '@elementor/editor-current-user';
5
5
  import { PopoverBody } from '@elementor/editor-editing-panel';
6
6
  import { PopoverHeader } from '@elementor/editor-ui';
7
7
  import { ArrowLeftIcon, TrashIcon } from '@elementor/icons';
8
- import { Button, CardActions, Divider, FormHelperText, IconButton, Typography } from '@elementor/ui';
8
+ import { Button, CardActions, Divider, FormHelperText, IconButton, Tooltip, Typography } from '@elementor/ui';
9
9
  import { __ } from '@wordpress/i18n';
10
10
 
11
11
  import { useVariableType } from '../context/variable-type-context';
12
12
  import { usePermissions } from '../hooks/use-permissions';
13
13
  import { deleteVariable, updateVariable, useVariable } from '../hooks/use-prop-variables';
14
+ import { useVariableBoundProp } from '../hooks/use-variable-bound-prop';
14
15
  import { styleVariablesRepository } from '../style-variables-repository';
15
16
  import { ERROR_MESSAGES, labelHint, mapServerError } from '../utils/validations';
16
17
  import { LabelField, useLabelError } from './fields/label-field';
@@ -19,6 +20,7 @@ import { EDIT_CONFIRMATION_DIALOG_ID, EditConfirmationDialog } from './ui/edit-c
19
20
  import { FormField } from './ui/form-field';
20
21
 
21
22
  const SIZE = 'tiny';
23
+ const DELETE_LABEL = __( 'Delete variable', 'elementor' );
22
24
 
23
25
  type Props = {
24
26
  editId: string;
@@ -28,9 +30,9 @@ type Props = {
28
30
  };
29
31
 
30
32
  export const VariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) => {
31
- const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
33
+ const { icon: VariableIcon, valueField: ValueField, variableType } = useVariableType();
32
34
 
33
- const { setValue: notifyBoundPropChange, value: assignedValue } = useBoundProp( propTypeUtil );
35
+ const { setVariableValue: notifyBoundPropChange, variableId } = useVariableBoundProp();
34
36
  const { propType } = useBoundProp();
35
37
  const [ isMessageSuppressed, suppressMessage ] = useSuppressedMessage( EDIT_CONFIRMATION_DIALOG_ID );
36
38
  const [ deleteConfirmation, setDeleteConfirmation ] = useState( false );
@@ -105,7 +107,7 @@ export const VariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) =
105
107
  };
106
108
 
107
109
  const maybeTriggerBoundPropChange = () => {
108
- if ( editId === assignedValue ) {
110
+ if ( editId === variableId ) {
109
111
  notifyBoundPropChange( editId );
110
112
  }
111
113
  };
@@ -126,14 +128,11 @@ export const VariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) =
126
128
 
127
129
  if ( userPermissions.canDelete() ) {
128
130
  actions.push(
129
- <IconButton
130
- key="delete"
131
- size={ SIZE }
132
- aria-label={ __( 'Delete', 'elementor' ) }
133
- onClick={ handleDeleteConfirmation }
134
- >
135
- <TrashIcon fontSize={ SIZE } />
136
- </IconButton>
131
+ <Tooltip key="delete" placement="top" title={ DELETE_LABEL }>
132
+ <IconButton size={ SIZE } onClick={ handleDeleteConfirmation } aria-label={ DELETE_LABEL }>
133
+ <TrashIcon fontSize={ SIZE } />
134
+ </IconButton>
135
+ </Tooltip>
137
136
  );
138
137
  }
139
138
 
@@ -210,6 +209,7 @@ export const VariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) =
210
209
  <FormField errorMsg={ valueFieldError } label={ __( 'Value', 'elementor' ) }>
211
210
  <Typography variant="h5">
212
211
  <ValueField
212
+ propTypeKey={ variable.type }
213
213
  value={ value }
214
214
  onChange={ ( newValue ) => {
215
215
  setValue( newValue );
@@ -9,6 +9,7 @@ import { __ } from '@wordpress/i18n';
9
9
  import { PopoverContentRefContextProvider } from '../context/variable-selection-popover.context';
10
10
  import { useVariableType } from '../context/variable-type-context';
11
11
  import { restoreVariable, useVariable } from '../hooks/use-prop-variables';
12
+ import { useVariableBoundProp } from '../hooks/use-variable-bound-prop';
12
13
  import { ERROR_MESSAGES, labelHint, mapServerError } from '../utils/validations';
13
14
  import { LabelField, useLabelError } from './fields/label-field';
14
15
  import { FormField } from './ui/form-field';
@@ -22,9 +23,9 @@ type Props = {
22
23
  };
23
24
 
24
25
  export const VariableRestore = ( { variableId, onClose, onSubmit }: Props ) => {
25
- const { icon: VariableIcon, valueField: ValueField, variableType, propTypeUtil } = useVariableType();
26
+ const { icon: VariableIcon, valueField: ValueField, variableType } = useVariableType();
26
27
 
27
- const { setValue: notifyBoundPropChange } = useBoundProp( propTypeUtil );
28
+ const { setVariableValue: notifyBoundPropChange } = useVariableBoundProp();
28
29
  const { propType } = useBoundProp();
29
30
 
30
31
  const variable = useVariable( variableId );
@@ -0,0 +1,21 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ export const useAutoEdit = () => {
4
+ const [ autoEditVariableId, setAutoEditVariableId ] = useState< string | undefined >( undefined );
5
+
6
+ const startAutoEdit = useCallback( ( variableId: string ) => {
7
+ setAutoEditVariableId( variableId );
8
+ }, [] );
9
+
10
+ const handleAutoEditComplete = useCallback( () => {
11
+ setTimeout( () => {
12
+ setAutoEditVariableId( undefined );
13
+ }, 100 );
14
+ }, [] );
15
+
16
+ return {
17
+ autoEditVariableId,
18
+ startAutoEdit,
19
+ handleAutoEditComplete,
20
+ };
21
+ };
@@ -0,0 +1,49 @@
1
+ import { useCallback, useRef } from 'react';
2
+
3
+ export interface UseErrorNavigationReturn {
4
+ createNavigationCallback: (
5
+ ids: string[],
6
+ onNavigate: ( id: string ) => void,
7
+ onComplete: () => void
8
+ ) => () => void;
9
+ resetNavigation: () => void;
10
+ }
11
+
12
+ export const useErrorNavigation = (): UseErrorNavigationReturn => {
13
+ const currentIndexRef = useRef( 0 );
14
+
15
+ const createNavigationCallback = useCallback(
16
+ ( ids: string[], onNavigate: ( id: string ) => void, onComplete: () => void ) => {
17
+ return () => {
18
+ if ( ! ids?.length ) {
19
+ return;
20
+ }
21
+
22
+ const currentIndex = currentIndexRef.current;
23
+ const currentId = ids[ currentIndex ];
24
+
25
+ if ( currentId ) {
26
+ onNavigate( currentId );
27
+
28
+ const nextIndex = currentIndex + 1;
29
+ if ( nextIndex >= ids.length ) {
30
+ onComplete();
31
+ currentIndexRef.current = 0;
32
+ } else {
33
+ currentIndexRef.current = nextIndex;
34
+ }
35
+ }
36
+ };
37
+ },
38
+ []
39
+ );
40
+
41
+ const resetNavigation = useCallback( () => {
42
+ currentIndexRef.current = 0;
43
+ }, [] );
44
+
45
+ return {
46
+ createNavigationCallback,
47
+ resetNavigation,
48
+ };
49
+ };
@@ -0,0 +1,89 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ import { generateTempId } from '../../../batch-operations';
4
+ import { getVariables } from '../../../hooks/use-prop-variables';
5
+ import { service } from '../../../service';
6
+ import { type TVariablesList } from '../../../storage';
7
+ import { filterBySearch } from '../../../utils/filter-by-search';
8
+
9
+ export const useVariablesManagerState = () => {
10
+ const [ variables, setVariables ] = useState( () => getVariables( false ) );
11
+ const [ deletedVariables, setDeletedVariables ] = useState< string[] >( [] );
12
+ const [ isSaveDisabled, setIsSaveDisabled ] = useState( false );
13
+ const [ isDirty, setIsDirty ] = useState( false );
14
+ const [ isSaving, setIsSaving ] = useState( false );
15
+ const [ searchValue, setSearchValue ] = useState( '' );
16
+
17
+ const handleOnChange = useCallback(
18
+ ( newVariables: TVariablesList ) => {
19
+ setVariables( { ...variables, ...newVariables } );
20
+ setIsDirty( true );
21
+ },
22
+ [ variables ]
23
+ );
24
+
25
+ const createVariable = useCallback( ( type: string, defaultName: string, defaultValue: string ) => {
26
+ const newId = generateTempId();
27
+ const newVariable = {
28
+ id: newId,
29
+ label: defaultName.trim(),
30
+ value: defaultValue.trim(),
31
+ type,
32
+ };
33
+
34
+ setVariables( ( prev ) => ( { ...prev, [ newId ]: newVariable } ) );
35
+ setIsDirty( true );
36
+
37
+ return newId;
38
+ }, [] );
39
+
40
+ const handleDeleteVariable = useCallback( ( itemId: string ) => {
41
+ setDeletedVariables( ( prev ) => [ ...prev, itemId ] );
42
+ setVariables( ( prev ) => ( { ...prev, [ itemId ]: { ...prev[ itemId ], deleted: true } } ) );
43
+ setIsDirty( true );
44
+ }, [] );
45
+
46
+ const handleSearch = ( searchTerm: string ) => {
47
+ setSearchValue( searchTerm );
48
+ };
49
+
50
+ const handleSave = useCallback( async (): Promise< { success: boolean } > => {
51
+ const originalVariables = getVariables( false );
52
+ setIsSaving( true );
53
+ const result = await service.batchSave( originalVariables, variables );
54
+
55
+ if ( result.success ) {
56
+ await service.load();
57
+ const updatedVariables = service.variables();
58
+
59
+ setVariables( updatedVariables );
60
+ setDeletedVariables( [] );
61
+ setIsDirty( false );
62
+ }
63
+
64
+ return { success: result.success };
65
+ }, [ variables ] );
66
+
67
+ const filteredVariables = () => {
68
+ const list = Object.entries( variables ).map( ( [ id, value ] ) => ( { ...value, id } ) );
69
+ const filtered = filterBySearch( list, searchValue );
70
+
71
+ return Object.fromEntries( filtered.map( ( { id, ...rest } ) => [ id, rest ] ) );
72
+ };
73
+
74
+ return {
75
+ variables: filteredVariables(),
76
+ deletedVariables,
77
+ isDirty,
78
+ isSaveDisabled,
79
+ handleOnChange,
80
+ createVariable,
81
+ handleDeleteVariable,
82
+ handleSave,
83
+ isSaving,
84
+ handleSearch,
85
+ searchValue,
86
+ setIsSaving,
87
+ setIsSaveDisabled,
88
+ };
89
+ };
@@ -1,85 +1,149 @@
1
1
  import * as React from 'react';
2
- import { useState } from 'react';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { ClickAwayListener, Stack } from '@elementor/ui';
4
4
 
5
5
  import { type ValueFieldProps } from '../../variables-registry/create-variable-type-registry';
6
+ import { useLabelError } from '../fields/label-field';
6
7
 
7
- export const VariableEditableCell = ( {
8
- initialValue,
9
- children,
10
- editableElement,
11
- onChange,
12
- prefixElement,
13
- }: {
8
+ type VariableEditableCellProps = {
14
9
  initialValue: string;
15
10
  children: React.ReactNode;
16
- editableElement: ( { value, onChange, onValidationChange }: ValueFieldProps ) => JSX.Element;
11
+ editableElement: ( { value, onChange, onValidationChange, error }: ValueFieldProps ) => JSX.Element;
17
12
  onChange: ( newValue: string ) => void;
18
13
  prefixElement?: React.ReactNode;
19
- } ) => {
20
- const [ value, setValue ] = useState( initialValue );
21
- const [ isEditing, setIsEditing ] = useState( false );
22
-
23
- const handleDoubleClick = () => {
24
- setIsEditing( true );
25
- };
26
-
27
- const handleSave = () => {
28
- onChange( value );
29
- setIsEditing( false );
30
- };
31
-
32
- const handleKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
33
- if ( event.key === 'Enter' ) {
34
- handleSave();
35
- } else if ( event.key === 'Escape' ) {
14
+ autoEdit?: boolean;
15
+ onRowRef?: ( ref: HTMLTableRowElement | null ) => void;
16
+ onAutoEditComplete?: () => void;
17
+ gap?: number;
18
+ fieldType?: 'label' | 'value';
19
+ };
20
+
21
+ export const VariableEditableCell = React.memo(
22
+ ( {
23
+ initialValue,
24
+ children,
25
+ editableElement,
26
+ onChange,
27
+ prefixElement,
28
+ autoEdit = false,
29
+ onRowRef,
30
+ onAutoEditComplete,
31
+ gap = 1,
32
+ fieldType,
33
+ }: VariableEditableCellProps ) => {
34
+ const [ value, setValue ] = useState( initialValue );
35
+ const [ isEditing, setIsEditing ] = useState( false );
36
+
37
+ const { labelFieldError, setLabelFieldError } = useLabelError();
38
+ const [ valueFieldError, setValueFieldError ] = useState( '' );
39
+
40
+ const rowRef = useRef< HTMLTableRowElement >( null );
41
+
42
+ const handleSave = useCallback( () => {
43
+ const hasError =
44
+ ( fieldType === 'label' && labelFieldError?.message ) || ( fieldType === 'value' && valueFieldError );
45
+
46
+ if ( ! hasError ) {
47
+ onChange( value );
48
+ }
36
49
  setIsEditing( false );
37
- }
38
- if ( event.key === ' ' && ! isEditing ) {
39
- event.preventDefault();
50
+ }, [ value, onChange, fieldType, labelFieldError, valueFieldError ] );
51
+
52
+ useEffect( () => {
53
+ onRowRef?.( rowRef?.current );
54
+ }, [ onRowRef ] );
55
+
56
+ useEffect( () => {
57
+ if ( autoEdit && ! isEditing ) {
58
+ setIsEditing( true );
59
+ onAutoEditComplete?.();
60
+ }
61
+ }, [ autoEdit, isEditing, onAutoEditComplete ] );
62
+
63
+ const handleDoubleClick = () => {
40
64
  setIsEditing( true );
65
+ };
66
+
67
+ const handleKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
68
+ if ( event.key === 'Enter' ) {
69
+ handleSave();
70
+ } else if ( event.key === 'Escape' ) {
71
+ setIsEditing( false );
72
+ }
73
+ if ( event.key === ' ' && ! isEditing ) {
74
+ event.preventDefault();
75
+ setIsEditing( true );
76
+ }
77
+ };
78
+
79
+ const handleChange = useCallback( ( newValue: string ) => {
80
+ setValue( newValue );
81
+ }, [] );
82
+
83
+ const handleValidationChange = useCallback(
84
+ ( errorMsg: string ) => {
85
+ if ( fieldType === 'label' ) {
86
+ setLabelFieldError( {
87
+ value,
88
+ message: errorMsg,
89
+ } );
90
+ } else {
91
+ setValueFieldError( errorMsg );
92
+ }
93
+ },
94
+ [ fieldType, value, setLabelFieldError, setValueFieldError ]
95
+ );
96
+
97
+ let currentError;
98
+ if ( fieldType === 'label' ) {
99
+ currentError = labelFieldError;
100
+ } else if ( fieldType === 'value' ) {
101
+ currentError = { value, message: valueFieldError };
41
102
  }
42
- };
43
103
 
44
- const handleChange = ( newValue: string ) => {
45
- setValue( newValue );
46
- };
104
+ const editableContent = editableElement( {
105
+ value,
106
+ onChange: handleChange,
107
+ onValidationChange: handleValidationChange,
108
+ error: currentError,
109
+ } );
47
110
 
48
- const editableContent = editableElement( { value, onChange: handleChange } );
111
+ if ( isEditing ) {
112
+ return (
113
+ <ClickAwayListener onClickAway={ handleSave }>
114
+ <Stack
115
+ ref={ rowRef }
116
+ direction="row"
117
+ alignItems="center"
118
+ gap={ gap }
119
+ onDoubleClick={ handleDoubleClick }
120
+ onKeyDown={ handleKeyDown }
121
+ tabIndex={ 0 }
122
+ role="button"
123
+ aria-label="Double click or press Space to edit"
124
+ >
125
+ { prefixElement }
126
+ { editableContent }
127
+ </Stack>
128
+ </ClickAwayListener>
129
+ );
130
+ }
49
131
 
50
- if ( isEditing ) {
51
132
  return (
52
- <ClickAwayListener onClickAway={ handleSave }>
53
- <Stack
54
- direction="row"
55
- alignItems="center"
56
- gap={ 1 }
57
- onDoubleClick={ handleDoubleClick }
58
- onKeyDown={ handleKeyDown }
59
- tabIndex={ 0 }
60
- role="button"
61
- aria-label="Double click or press Space to edit"
62
- >
63
- { prefixElement }
64
- { editableContent }
65
- </Stack>
66
- </ClickAwayListener>
133
+ <Stack
134
+ ref={ rowRef }
135
+ direction="row"
136
+ alignItems="center"
137
+ gap={ gap }
138
+ onDoubleClick={ handleDoubleClick }
139
+ onKeyDown={ handleKeyDown }
140
+ tabIndex={ 0 }
141
+ role="button"
142
+ aria-label="Double click or press Space to edit"
143
+ >
144
+ { prefixElement }
145
+ { children }
146
+ </Stack>
67
147
  );
68
148
  }
69
-
70
- return (
71
- <Stack
72
- direction="row"
73
- alignItems="center"
74
- gap={ 1 }
75
- onDoubleClick={ handleDoubleClick }
76
- onKeyDown={ handleKeyDown }
77
- tabIndex={ 0 }
78
- role="button"
79
- aria-label="Double click or press Space to edit"
80
- >
81
- { prefixElement }
82
- { children }
83
- </Stack>
84
- );
85
- };
149
+ );