@elementor/editor-variables 3.33.0-141 → 3.33.0-143
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 +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +496 -368
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +461 -333
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -13
- package/src/components/fields/label-field.tsx +29 -3
- package/src/components/variables-manager/hooks/use-auto-edit.ts +21 -0
- package/src/components/variables-manager/hooks/use-variables-manager-state.ts +79 -0
- package/src/components/variables-manager/variable-editable-cell.tsx +108 -82
- package/src/components/variables-manager/variables-manager-panel.tsx +39 -62
- package/src/components/variables-manager/variables-manager-table.tsx +15 -1
- package/src/variables-registry/create-variable-type-registry.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-variables",
|
|
3
|
-
"version": "3.33.0-
|
|
3
|
+
"version": "3.33.0-143",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,19 +39,19 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "3.33.0-
|
|
43
|
-
"@elementor/editor-canvas": "3.33.0-
|
|
44
|
-
"@elementor/editor-controls": "3.33.0-
|
|
45
|
-
"@elementor/editor-current-user": "3.33.0-
|
|
46
|
-
"@elementor/editor-editing-panel": "3.33.0-
|
|
47
|
-
"@elementor/editor-panels": "3.33.0-
|
|
48
|
-
"@elementor/editor-props": "3.33.0-
|
|
49
|
-
"@elementor/editor-ui": "3.33.0-
|
|
50
|
-
"@elementor/editor-v1-adapters": "3.33.0-
|
|
51
|
-
"@elementor/http-client": "3.33.0-
|
|
42
|
+
"@elementor/editor": "3.33.0-143",
|
|
43
|
+
"@elementor/editor-canvas": "3.33.0-143",
|
|
44
|
+
"@elementor/editor-controls": "3.33.0-143",
|
|
45
|
+
"@elementor/editor-current-user": "3.33.0-143",
|
|
46
|
+
"@elementor/editor-editing-panel": "3.33.0-143",
|
|
47
|
+
"@elementor/editor-panels": "3.33.0-143",
|
|
48
|
+
"@elementor/editor-props": "3.33.0-143",
|
|
49
|
+
"@elementor/editor-ui": "3.33.0-143",
|
|
50
|
+
"@elementor/editor-v1-adapters": "3.33.0-143",
|
|
51
|
+
"@elementor/http-client": "3.33.0-143",
|
|
52
52
|
"@elementor/icons": "1.46.0",
|
|
53
|
-
"@elementor/mixpanel": "3.33.0-
|
|
54
|
-
"@elementor/schema": "3.33.0-
|
|
53
|
+
"@elementor/mixpanel": "3.33.0-143",
|
|
54
|
+
"@elementor/schema": "3.33.0-143",
|
|
55
55
|
"@elementor/ui": "1.36.12",
|
|
56
56
|
"@wordpress/i18n": "^5.13.0"
|
|
57
57
|
},
|
|
@@ -1,8 +1,9 @@
|
|
|
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 { labelHint, validateLabel, VARIABLE_LABEL_MAX_LENGTH } from '../../utils/validations';
|
|
6
7
|
function isLabelEqual( a: string, b: string ) {
|
|
7
8
|
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
8
9
|
}
|
|
@@ -30,6 +31,7 @@ type LabelFieldProps = {
|
|
|
30
31
|
size?: TextFieldProps[ 'size' ];
|
|
31
32
|
focusOnShow?: boolean;
|
|
32
33
|
selectOnShow?: boolean;
|
|
34
|
+
showWarningInfotip?: boolean;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
export const LabelField = ( {
|
|
@@ -41,9 +43,11 @@ export const LabelField = ( {
|
|
|
41
43
|
size = 'tiny',
|
|
42
44
|
focusOnShow = false,
|
|
43
45
|
selectOnShow = false,
|
|
46
|
+
showWarningInfotip = false,
|
|
44
47
|
}: LabelFieldProps ) => {
|
|
45
48
|
const [ label, setLabel ] = useState( value );
|
|
46
49
|
const [ errorMessage, setErrorMessage ] = useState( '' );
|
|
50
|
+
const fieldRef = useRef< HTMLElement >( null );
|
|
47
51
|
|
|
48
52
|
const handleChange = ( newValue: string ) => {
|
|
49
53
|
setLabel( newValue );
|
|
@@ -61,8 +65,11 @@ export const LabelField = ( {
|
|
|
61
65
|
errorMsg = error.message;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
const hintMsg = ! errorMsg ? labelHint( label ) : '';
|
|
69
|
+
|
|
70
|
+
const textField = (
|
|
65
71
|
<TextField
|
|
72
|
+
ref={ fieldRef }
|
|
66
73
|
id={ id }
|
|
67
74
|
size={ size }
|
|
68
75
|
fullWidth
|
|
@@ -77,4 +84,23 @@ export const LabelField = ( {
|
|
|
77
84
|
autoFocus={ focusOnShow }
|
|
78
85
|
/>
|
|
79
86
|
);
|
|
87
|
+
|
|
88
|
+
if ( showWarningInfotip ) {
|
|
89
|
+
const tooltipWidth = Math.max( 240, fieldRef.current?.getBoundingClientRect().width ?? 240 );
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<WarningInfotip
|
|
93
|
+
open={ Boolean( errorMsg || hintMsg ) }
|
|
94
|
+
text={ errorMsg || hintMsg }
|
|
95
|
+
placement="bottom-start"
|
|
96
|
+
width={ tooltipWidth }
|
|
97
|
+
offset={ [ 0, -15 ] }
|
|
98
|
+
{ ...( hintMsg && { hasError: false } ) }
|
|
99
|
+
>
|
|
100
|
+
{ textField }
|
|
101
|
+
</WarningInfotip>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return textField;
|
|
80
106
|
};
|
|
@@ -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,79 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { __ } from '@wordpress/i18n';
|
|
3
|
+
|
|
4
|
+
import { generateTempId } from '../../../batch-operations';
|
|
5
|
+
import { getVariables } from '../../../hooks/use-prop-variables';
|
|
6
|
+
import { service } from '../../../service';
|
|
7
|
+
import { type TVariablesList } from '../../../storage';
|
|
8
|
+
import { ERROR_MESSAGES } from '../../../utils/validations';
|
|
9
|
+
|
|
10
|
+
export const useVariablesManagerState = () => {
|
|
11
|
+
const [ variables, setVariables ] = useState( () => getVariables( false ) );
|
|
12
|
+
const [ deletedVariables, setDeletedVariables ] = useState< string[] >( [] );
|
|
13
|
+
const [ ids, setIds ] = useState< string[] >( () => Object.keys( getVariables( false ) ) );
|
|
14
|
+
const [ isDirty, setIsDirty ] = useState( false );
|
|
15
|
+
const [ hasValidationErrors, setHasValidationErrors ] = useState( false );
|
|
16
|
+
|
|
17
|
+
const handleOnChange = useCallback( ( newVariables: TVariablesList ) => {
|
|
18
|
+
setVariables( newVariables );
|
|
19
|
+
setIsDirty( true );
|
|
20
|
+
}, [] );
|
|
21
|
+
|
|
22
|
+
const createVariable = useCallback( ( type: string, defaultName: string, defaultValue: string ) => {
|
|
23
|
+
const newId = generateTempId();
|
|
24
|
+
const newVariable = {
|
|
25
|
+
id: newId,
|
|
26
|
+
label: defaultName.trim(),
|
|
27
|
+
value: defaultValue.trim(),
|
|
28
|
+
type,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
setVariables( ( prev ) => ( { ...prev, [ newId ]: newVariable } ) );
|
|
32
|
+
setIds( ( prev ) => [ ...prev, newId ] );
|
|
33
|
+
setIsDirty( true );
|
|
34
|
+
|
|
35
|
+
return newId;
|
|
36
|
+
}, [] );
|
|
37
|
+
|
|
38
|
+
const handleDeleteVariable = useCallback( ( itemId: string ) => {
|
|
39
|
+
setDeletedVariables( ( prev ) => [ ...prev, itemId ] );
|
|
40
|
+
setVariables( ( prev ) => ( { ...prev, [ itemId ]: { ...prev[ itemId ], deleted: true } } ) );
|
|
41
|
+
setIsDirty( true );
|
|
42
|
+
}, [] );
|
|
43
|
+
|
|
44
|
+
const handleSave = useCallback( async (): Promise< { success: boolean; error?: string } > => {
|
|
45
|
+
try {
|
|
46
|
+
const originalVariables = getVariables( false );
|
|
47
|
+
const result = await service.batchSave( originalVariables, variables );
|
|
48
|
+
|
|
49
|
+
if ( result.success ) {
|
|
50
|
+
await service.load();
|
|
51
|
+
const updatedVariables = service.variables();
|
|
52
|
+
|
|
53
|
+
setVariables( updatedVariables );
|
|
54
|
+
setIds( Object.keys( updatedVariables ) );
|
|
55
|
+
setDeletedVariables( [] );
|
|
56
|
+
setIsDirty( false );
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
throw new Error( __( 'Failed to save variables. Please try again.', 'elementor' ) );
|
|
60
|
+
} catch ( error ) {
|
|
61
|
+
const errorMessage = error instanceof Error ? error.message : ERROR_MESSAGES.UNEXPECTED_ERROR;
|
|
62
|
+
return { success: false, error: errorMessage };
|
|
63
|
+
}
|
|
64
|
+
}, [ variables ] );
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
variables,
|
|
68
|
+
deletedVariables,
|
|
69
|
+
ids,
|
|
70
|
+
isDirty,
|
|
71
|
+
hasValidationErrors,
|
|
72
|
+
setIds,
|
|
73
|
+
handleOnChange,
|
|
74
|
+
createVariable,
|
|
75
|
+
handleDeleteVariable,
|
|
76
|
+
handleSave,
|
|
77
|
+
setHasValidationErrors,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
@@ -1,108 +1,134 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useEffect, useRef, 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
8
|
type VariableEditableCellProps = {
|
|
8
9
|
initialValue: string;
|
|
9
10
|
children: React.ReactNode;
|
|
10
|
-
editableElement: ( { value, onChange, onValidationChange }: ValueFieldProps ) => JSX.Element;
|
|
11
|
+
editableElement: ( { value, onChange, onValidationChange, error }: ValueFieldProps ) => JSX.Element;
|
|
11
12
|
onChange: ( newValue: string ) => void;
|
|
12
13
|
prefixElement?: React.ReactNode;
|
|
13
14
|
autoEdit?: boolean;
|
|
14
15
|
onRowRef?: ( ref: HTMLTableRowElement | null ) => void;
|
|
15
16
|
onAutoEditComplete?: () => void;
|
|
17
|
+
fieldType?: 'label' | 'value';
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
export const VariableEditableCell = (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
useEffect( () => {
|
|
34
|
-
onRowRef?.( rowRef?.current );
|
|
35
|
-
}, [ onRowRef ] );
|
|
36
|
-
|
|
37
|
-
useEffect( () => {
|
|
38
|
-
if ( autoEdit && ! isEditing ) {
|
|
39
|
-
setIsEditing( true );
|
|
40
|
-
onAutoEditComplete?.();
|
|
41
|
-
}
|
|
42
|
-
}, [ autoEdit, isEditing, onAutoEditComplete ] );
|
|
20
|
+
export const VariableEditableCell = React.memo(
|
|
21
|
+
( {
|
|
22
|
+
initialValue,
|
|
23
|
+
children,
|
|
24
|
+
editableElement,
|
|
25
|
+
onChange,
|
|
26
|
+
prefixElement,
|
|
27
|
+
autoEdit = false,
|
|
28
|
+
onRowRef,
|
|
29
|
+
onAutoEditComplete,
|
|
30
|
+
fieldType,
|
|
31
|
+
}: VariableEditableCellProps ) => {
|
|
32
|
+
const [ value, setValue ] = useState( initialValue );
|
|
33
|
+
const [ isEditing, setIsEditing ] = useState( false );
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
setIsEditing( true );
|
|
46
|
-
};
|
|
35
|
+
const { labelFieldError, setLabelFieldError } = useLabelError();
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
onChange( value );
|
|
50
|
-
setIsEditing( false );
|
|
51
|
-
};
|
|
37
|
+
const rowRef = useRef< HTMLTableRowElement >( null );
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
handleSave();
|
|
56
|
-
} else if ( event.key === 'Escape' ) {
|
|
39
|
+
const handleSave = useCallback( () => {
|
|
40
|
+
onChange( value );
|
|
57
41
|
setIsEditing( false );
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
42
|
+
}, [ value, onChange ] );
|
|
43
|
+
|
|
44
|
+
useEffect( () => {
|
|
45
|
+
onRowRef?.( rowRef?.current );
|
|
46
|
+
}, [ onRowRef ] );
|
|
47
|
+
|
|
48
|
+
useEffect( () => {
|
|
49
|
+
if ( autoEdit && ! isEditing ) {
|
|
50
|
+
setIsEditing( true );
|
|
51
|
+
onAutoEditComplete?.();
|
|
52
|
+
}
|
|
53
|
+
}, [ autoEdit, isEditing, onAutoEditComplete ] );
|
|
54
|
+
|
|
55
|
+
const handleDoubleClick = () => {
|
|
61
56
|
setIsEditing( true );
|
|
62
|
-
}
|
|
63
|
-
};
|
|
57
|
+
};
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
const handleKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
|
|
60
|
+
if ( event.key === 'Enter' ) {
|
|
61
|
+
handleSave();
|
|
62
|
+
} else if ( event.key === 'Escape' ) {
|
|
63
|
+
setIsEditing( false );
|
|
64
|
+
}
|
|
65
|
+
if ( event.key === ' ' && ! isEditing ) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
setIsEditing( true );
|
|
68
|
+
}
|
|
69
|
+
};
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
const handleChange = useCallback( ( newValue: string ) => {
|
|
72
|
+
setValue( newValue );
|
|
73
|
+
}, [] );
|
|
74
|
+
|
|
75
|
+
const handleValidationChange = useCallback(
|
|
76
|
+
( errorMsg: string ) => {
|
|
77
|
+
if ( fieldType === 'label' ) {
|
|
78
|
+
setLabelFieldError( {
|
|
79
|
+
value,
|
|
80
|
+
message: errorMsg,
|
|
81
|
+
} );
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[ fieldType, value, setLabelFieldError ]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const currentError = fieldType === 'label' ? labelFieldError : undefined;
|
|
88
|
+
|
|
89
|
+
const editableContent = editableElement( {
|
|
90
|
+
value,
|
|
91
|
+
onChange: handleChange,
|
|
92
|
+
onValidationChange: handleValidationChange,
|
|
93
|
+
error: currentError,
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
if ( isEditing ) {
|
|
97
|
+
return (
|
|
98
|
+
<ClickAwayListener onClickAway={ handleSave }>
|
|
99
|
+
<Stack
|
|
100
|
+
ref={ rowRef }
|
|
101
|
+
direction="row"
|
|
102
|
+
alignItems="center"
|
|
103
|
+
gap={ 1 }
|
|
104
|
+
onDoubleClick={ handleDoubleClick }
|
|
105
|
+
onKeyDown={ handleKeyDown }
|
|
106
|
+
tabIndex={ 0 }
|
|
107
|
+
role="button"
|
|
108
|
+
aria-label="Double click or press Space to edit"
|
|
109
|
+
>
|
|
110
|
+
{ prefixElement }
|
|
111
|
+
{ editableContent }
|
|
112
|
+
</Stack>
|
|
113
|
+
</ClickAwayListener>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
70
116
|
|
|
71
|
-
if ( isEditing ) {
|
|
72
117
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</Stack>
|
|
88
|
-
</ClickAwayListener>
|
|
118
|
+
<Stack
|
|
119
|
+
ref={ rowRef }
|
|
120
|
+
direction="row"
|
|
121
|
+
alignItems="center"
|
|
122
|
+
gap={ 1 }
|
|
123
|
+
onDoubleClick={ handleDoubleClick }
|
|
124
|
+
onKeyDown={ handleKeyDown }
|
|
125
|
+
tabIndex={ 0 }
|
|
126
|
+
role="button"
|
|
127
|
+
aria-label="Double click or press Space to edit"
|
|
128
|
+
>
|
|
129
|
+
{ prefixElement }
|
|
130
|
+
{ children }
|
|
131
|
+
</Stack>
|
|
89
132
|
);
|
|
90
133
|
}
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<Stack
|
|
94
|
-
ref={ rowRef }
|
|
95
|
-
direction="row"
|
|
96
|
-
alignItems="center"
|
|
97
|
-
gap={ 1 }
|
|
98
|
-
onDoubleClick={ handleDoubleClick }
|
|
99
|
-
onKeyDown={ handleKeyDown }
|
|
100
|
-
tabIndex={ 0 }
|
|
101
|
-
role="button"
|
|
102
|
-
aria-label="Double click or press Space to edit"
|
|
103
|
-
>
|
|
104
|
-
{ prefixElement }
|
|
105
|
-
{ children }
|
|
106
|
-
</Stack>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
134
|
+
);
|
|
@@ -14,11 +14,9 @@ import { ColorFilterIcon, TrashIcon } from '@elementor/icons';
|
|
|
14
14
|
import { Alert, Box, Button, CloseButton, Divider, ErrorBoundary, Stack } from '@elementor/ui';
|
|
15
15
|
import { __ } from '@wordpress/i18n';
|
|
16
16
|
|
|
17
|
-
import { generateTempId } from '../../batch-operations';
|
|
18
|
-
import { getVariables } from '../../hooks/use-prop-variables';
|
|
19
|
-
import { service } from '../../service';
|
|
20
|
-
import { type TVariablesList } from '../../storage';
|
|
21
17
|
import { DeleteConfirmationDialog } from '../ui/delete-confirmation-dialog';
|
|
18
|
+
import { useAutoEdit } from './hooks/use-auto-edit';
|
|
19
|
+
import { useVariablesManagerState } from './hooks/use-variables-manager-state';
|
|
22
20
|
import { SIZE, VariableManagerCreateMenu } from './variables-manager-create-menu';
|
|
23
21
|
import { VariablesManagerTable } from './variables-manager-table';
|
|
24
22
|
|
|
@@ -40,13 +38,22 @@ export function VariablesManagerPanel() {
|
|
|
40
38
|
const { close: closePanel } = usePanelActions();
|
|
41
39
|
const { open: openSaveChangesDialog, close: closeSaveChangesDialog, isOpen: isSaveChangesDialogOpen } = useDialog();
|
|
42
40
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
const {
|
|
42
|
+
variables,
|
|
43
|
+
ids,
|
|
44
|
+
isDirty,
|
|
45
|
+
hasValidationErrors,
|
|
46
|
+
setIds,
|
|
47
|
+
handleOnChange,
|
|
48
|
+
createVariable,
|
|
49
|
+
handleDeleteVariable,
|
|
50
|
+
handleSave,
|
|
51
|
+
setHasValidationErrors,
|
|
52
|
+
} = useVariablesManagerState();
|
|
53
|
+
|
|
54
|
+
const { autoEditVariableId, startAutoEdit, handleAutoEditComplete } = useAutoEdit();
|
|
46
55
|
|
|
47
56
|
const [ deleteConfirmation, setDeleteConfirmation ] = useState< { id: string; label: string } | null >( null );
|
|
48
|
-
const [ autoEditVariableId, setAutoEditVariableId ] = useState< string | undefined >( undefined );
|
|
49
|
-
const [ isDirty, setIsDirty ] = useState( false );
|
|
50
57
|
|
|
51
58
|
usePreventUnload( isDirty );
|
|
52
59
|
|
|
@@ -59,57 +66,23 @@ export function VariablesManagerPanel() {
|
|
|
59
66
|
closePanel();
|
|
60
67
|
};
|
|
61
68
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setVariables( updatedVariables );
|
|
73
|
-
setIds( Object.keys( updatedVariables ) );
|
|
74
|
-
setDeletedVariables( [] );
|
|
75
|
-
} else {
|
|
76
|
-
setIsDirty( true );
|
|
77
|
-
}
|
|
78
|
-
}, [ variables ] );
|
|
79
|
-
|
|
80
|
-
const handleOnChange = ( newVariables: TVariablesList ) => {
|
|
81
|
-
setVariables( newVariables );
|
|
82
|
-
setIsDirty( true );
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const createVariable = useCallback( ( type: string, defaultName: string, defaultValue: string ) => {
|
|
86
|
-
const newId = generateTempId();
|
|
87
|
-
const newVariable = {
|
|
88
|
-
id: newId,
|
|
89
|
-
label: defaultName,
|
|
90
|
-
value: defaultValue,
|
|
91
|
-
type,
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
setVariables( ( prev ) => ( { ...prev, [ newId ]: newVariable } ) );
|
|
95
|
-
setIds( ( prev ) => [ ...prev, newId ] );
|
|
96
|
-
setIsDirty( true );
|
|
97
|
-
|
|
98
|
-
setAutoEditVariableId( newId );
|
|
99
|
-
}, [] );
|
|
100
|
-
|
|
101
|
-
const handleDeleteVariable = ( itemId: string ) => {
|
|
102
|
-
setDeletedVariables( [ ...deletedVariables, itemId ] );
|
|
103
|
-
setVariables( { ...variables, [ itemId ]: { ...variables[ itemId ], deleted: true } } );
|
|
104
|
-
setIsDirty( true );
|
|
105
|
-
setDeleteConfirmation( null );
|
|
106
|
-
};
|
|
69
|
+
const handleCreateVariable = useCallback(
|
|
70
|
+
( type: string, defaultName: string, defaultValue: string ) => {
|
|
71
|
+
const newId = createVariable( type, defaultName, defaultValue );
|
|
72
|
+
if ( newId ) {
|
|
73
|
+
startAutoEdit( newId );
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[ createVariable, startAutoEdit ]
|
|
77
|
+
);
|
|
107
78
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
79
|
+
const handleDeleteVariableWithConfirmation = useCallback(
|
|
80
|
+
( itemId: string ) => {
|
|
81
|
+
handleDeleteVariable( itemId );
|
|
82
|
+
setDeleteConfirmation( null );
|
|
83
|
+
},
|
|
84
|
+
[ handleDeleteVariable ]
|
|
85
|
+
);
|
|
113
86
|
|
|
114
87
|
const menuActions = [
|
|
115
88
|
{
|
|
@@ -138,7 +111,10 @@ export function VariablesManagerPanel() {
|
|
|
138
111
|
</PanelHeaderTitle>
|
|
139
112
|
</Stack>
|
|
140
113
|
<Stack direction="row" gap={ 0.5 } alignItems="center">
|
|
141
|
-
<VariableManagerCreateMenu
|
|
114
|
+
<VariableManagerCreateMenu
|
|
115
|
+
onCreate={ handleCreateVariable }
|
|
116
|
+
variables={ variables }
|
|
117
|
+
/>
|
|
142
118
|
<CloseButton
|
|
143
119
|
aria-label="Close"
|
|
144
120
|
slotProps={ { icon: { fontSize: SIZE } } }
|
|
@@ -166,6 +142,7 @@ export function VariablesManagerPanel() {
|
|
|
166
142
|
onIdsChange={ setIds }
|
|
167
143
|
autoEditVariableId={ autoEditVariableId }
|
|
168
144
|
onAutoEditComplete={ handleAutoEditComplete }
|
|
145
|
+
onFieldError={ setHasValidationErrors }
|
|
169
146
|
/>
|
|
170
147
|
</PanelBody>
|
|
171
148
|
|
|
@@ -175,7 +152,7 @@ export function VariablesManagerPanel() {
|
|
|
175
152
|
size="small"
|
|
176
153
|
color="global"
|
|
177
154
|
variant="contained"
|
|
178
|
-
disabled={ ! isDirty }
|
|
155
|
+
disabled={ ! isDirty || hasValidationErrors }
|
|
179
156
|
onClick={ handleSave }
|
|
180
157
|
>
|
|
181
158
|
{ __( 'Save changes', 'elementor' ) }
|
|
@@ -187,7 +164,7 @@ export function VariablesManagerPanel() {
|
|
|
187
164
|
<DeleteConfirmationDialog
|
|
188
165
|
open
|
|
189
166
|
label={ deleteConfirmation.label }
|
|
190
|
-
onConfirm={ () =>
|
|
167
|
+
onConfirm={ () => handleDeleteVariableWithConfirmation( deleteConfirmation.id ) }
|
|
191
168
|
closeDialog={ () => setDeleteConfirmation( null ) }
|
|
192
169
|
/>
|
|
193
170
|
) }
|
|
@@ -32,6 +32,7 @@ type Props = {
|
|
|
32
32
|
onIdsChange: ( ids: string[] ) => void;
|
|
33
33
|
autoEditVariableId?: string;
|
|
34
34
|
onAutoEditComplete?: () => void;
|
|
35
|
+
onFieldError?: ( hasError: boolean ) => void;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
export const VariablesManagerTable = ( {
|
|
@@ -42,6 +43,7 @@ export const VariablesManagerTable = ( {
|
|
|
42
43
|
onIdsChange: setIds,
|
|
43
44
|
autoEditVariableId,
|
|
44
45
|
onAutoEditComplete,
|
|
46
|
+
onFieldError,
|
|
45
47
|
}: Props ) => {
|
|
46
48
|
const tableContainerRef = useRef< HTMLDivElement >( null );
|
|
47
49
|
const variableRowRefs = useRef< Map< string, HTMLTableRowElement > >( new Map() );
|
|
@@ -203,14 +205,25 @@ export const VariablesManagerTable = ( {
|
|
|
203
205
|
}
|
|
204
206
|
} }
|
|
205
207
|
prefixElement={ createElement( row.icon, { fontSize: 'inherit' } ) }
|
|
206
|
-
editableElement={ ( {
|
|
208
|
+
editableElement={ ( {
|
|
209
|
+
value,
|
|
210
|
+
onChange,
|
|
211
|
+
onValidationChange,
|
|
212
|
+
error,
|
|
213
|
+
} ) => (
|
|
207
214
|
<LabelField
|
|
208
215
|
id={ 'variable-label-' + row.id }
|
|
209
216
|
size="tiny"
|
|
210
217
|
value={ value }
|
|
211
218
|
onChange={ onChange }
|
|
219
|
+
onErrorChange={ ( errorMsg ) => {
|
|
220
|
+
onValidationChange?.( errorMsg );
|
|
221
|
+
onFieldError?.( !! errorMsg );
|
|
222
|
+
} }
|
|
223
|
+
error={ error }
|
|
212
224
|
focusOnShow
|
|
213
225
|
selectOnShow={ autoEditVariableId === row.id }
|
|
226
|
+
showWarningInfotip={ true }
|
|
214
227
|
/>
|
|
215
228
|
) }
|
|
216
229
|
autoEdit={ autoEditVariableId === row.id }
|
|
@@ -218,6 +231,7 @@ export const VariablesManagerTable = ( {
|
|
|
218
231
|
onAutoEditComplete={
|
|
219
232
|
autoEditVariableId === row.id ? onAutoEditComplete : undefined
|
|
220
233
|
}
|
|
234
|
+
fieldType="label"
|
|
221
235
|
>
|
|
222
236
|
<EllipsisWithTooltip
|
|
223
237
|
title={ row.name }
|
|
@@ -19,6 +19,7 @@ export type ValueFieldProps = {
|
|
|
19
19
|
onChange: ( value: string ) => void;
|
|
20
20
|
onValidationChange?: ( value: string ) => void;
|
|
21
21
|
propType?: PropType;
|
|
22
|
+
error?: { value: string; message: string };
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
type FallbackPropTypeUtil = ReturnType< typeof createPropUtils >;
|
|
@@ -26,7 +27,7 @@ type FallbackPropTypeUtil = ReturnType< typeof createPropUtils >;
|
|
|
26
27
|
type VariableTypeOptions = {
|
|
27
28
|
icon: ForwardRefExoticComponent< Omit< SvgIconProps, 'ref' > & RefAttributes< SVGSVGElement > >;
|
|
28
29
|
startIcon?: ( { value }: { value: string } ) => JSX.Element;
|
|
29
|
-
valueField: ( { value, onChange, onValidationChange, propType }: ValueFieldProps ) => JSX.Element;
|
|
30
|
+
valueField: ( { value, onChange, onValidationChange, propType, error }: ValueFieldProps ) => JSX.Element;
|
|
30
31
|
variableType: string;
|
|
31
32
|
defaultValue?: string;
|
|
32
33
|
fallbackPropTypeUtil: FallbackPropTypeUtil;
|