@elementor/editor-controls 0.12.0 → 0.13.0
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/CHANGELOG.md +12 -0
- package/dist/index.js +343 -217
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +286 -153
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/repeater.tsx +83 -28
- package/src/components/sortable.tsx +108 -0
- package/src/controls/text-area-control.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-controls",
|
|
3
3
|
"description": "This package contains the controls model and utils for the Elementor editor",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
} from '@elementor/ui';
|
|
17
17
|
import { __ } from '@wordpress/i18n';
|
|
18
18
|
|
|
19
|
+
import { useSyncExternalState } from '../hooks/use-sync-external-state';
|
|
19
20
|
import { SectionContent } from './section-content';
|
|
21
|
+
import { SortableItem, SortableProvider } from './sortable';
|
|
20
22
|
|
|
21
23
|
const SIZE = 'tiny';
|
|
22
24
|
|
|
@@ -50,32 +52,61 @@ export const Repeater = < T, >( {
|
|
|
50
52
|
values: repeaterValues = [],
|
|
51
53
|
setValues: setRepeaterValues,
|
|
52
54
|
}: RepeaterProps< Item< T > > ) => {
|
|
55
|
+
const [ items, setItems ] = useSyncExternalState( {
|
|
56
|
+
external: repeaterValues,
|
|
57
|
+
// @ts-expect-error - as long as persistWhen => true, value will never be null
|
|
58
|
+
setExternal: setRepeaterValues,
|
|
59
|
+
persistWhen: () => true,
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
const [ uniqueKeys, setUniqueKeys ] = useState( items.map( ( _, index ) => index ) );
|
|
63
|
+
|
|
64
|
+
const generateNextKey = ( source: number[] ) => {
|
|
65
|
+
return 1 + Math.max( 0, ...source );
|
|
66
|
+
};
|
|
67
|
+
|
|
53
68
|
const addRepeaterItem = () => {
|
|
54
69
|
const newItem = structuredClone( itemSettings.initialValues );
|
|
70
|
+
const newKey = generateNextKey( uniqueKeys );
|
|
55
71
|
|
|
56
72
|
if ( addToBottom ) {
|
|
57
|
-
|
|
73
|
+
setItems( [ ...items, newItem ] );
|
|
74
|
+
setUniqueKeys( [ ...uniqueKeys, newKey ] );
|
|
75
|
+
} else {
|
|
76
|
+
setItems( [ newItem, ...items ] );
|
|
77
|
+
setUniqueKeys( [ newKey, ...uniqueKeys ] );
|
|
58
78
|
}
|
|
59
|
-
|
|
60
|
-
setRepeaterValues( [ newItem, ...repeaterValues ] );
|
|
61
79
|
};
|
|
62
80
|
|
|
63
81
|
const duplicateRepeaterItem = ( index: number ) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
const newItem = structuredClone( items[ index ] );
|
|
83
|
+
const newKey = generateNextKey( uniqueKeys );
|
|
84
|
+
|
|
85
|
+
// Insert the new (cloned item) at the next spot (after the current index)
|
|
86
|
+
const atPosition = 1 + index;
|
|
87
|
+
|
|
88
|
+
setItems( [ ...items.slice( 0, atPosition ), newItem, ...items.slice( atPosition ) ] );
|
|
89
|
+
setUniqueKeys( [ ...uniqueKeys.slice( 0, atPosition ), newKey, ...uniqueKeys.slice( atPosition ) ] );
|
|
69
90
|
};
|
|
70
91
|
|
|
71
92
|
const removeRepeaterItem = ( index: number ) => {
|
|
72
|
-
|
|
93
|
+
setUniqueKeys(
|
|
94
|
+
uniqueKeys.filter( ( _, pos ) => {
|
|
95
|
+
return pos !== index;
|
|
96
|
+
} )
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
setItems(
|
|
100
|
+
items.filter( ( _, pos ) => {
|
|
101
|
+
return pos !== index;
|
|
102
|
+
} )
|
|
103
|
+
);
|
|
73
104
|
};
|
|
74
105
|
|
|
75
106
|
const toggleDisableRepeaterItem = ( index: number ) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
107
|
+
setItems(
|
|
108
|
+
items.map( ( value, pos ) => {
|
|
109
|
+
if ( pos === index ) {
|
|
79
110
|
const { disabled, ...rest } = value;
|
|
80
111
|
|
|
81
112
|
// If the items should not be disabled, remove the disabled property.
|
|
@@ -87,6 +118,16 @@ export const Repeater = < T, >( {
|
|
|
87
118
|
);
|
|
88
119
|
};
|
|
89
120
|
|
|
121
|
+
const onChangeOrder = ( reorderedKeys: number[] ) => {
|
|
122
|
+
setUniqueKeys( reorderedKeys );
|
|
123
|
+
setItems( ( prevItems ) => {
|
|
124
|
+
return reorderedKeys.map( ( keyValue ) => {
|
|
125
|
+
const index = uniqueKeys.indexOf( keyValue );
|
|
126
|
+
return prevItems[ index ];
|
|
127
|
+
} );
|
|
128
|
+
} );
|
|
129
|
+
};
|
|
130
|
+
|
|
90
131
|
return (
|
|
91
132
|
<SectionContent>
|
|
92
133
|
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
|
@@ -97,22 +138,35 @@ export const Repeater = < T, >( {
|
|
|
97
138
|
<PlusIcon fontSize={ SIZE } />
|
|
98
139
|
</IconButton>
|
|
99
140
|
</Stack>
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
{ 0 < uniqueKeys.length && (
|
|
142
|
+
<SortableProvider value={ uniqueKeys } onChange={ onChangeOrder }>
|
|
143
|
+
{ uniqueKeys.map( ( key, index ) => {
|
|
144
|
+
const value = items[ index ];
|
|
145
|
+
|
|
146
|
+
if ( ! value ) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<SortableItem id={ key } key={ `sortable-${ key }` }>
|
|
152
|
+
<RepeaterItem
|
|
153
|
+
bind={ String( index ) }
|
|
154
|
+
disabled={ value?.disabled }
|
|
155
|
+
label={ <itemSettings.Label value={ value } /> }
|
|
156
|
+
startIcon={ <itemSettings.Icon value={ value } /> }
|
|
157
|
+
removeItem={ () => removeRepeaterItem( index ) }
|
|
158
|
+
duplicateItem={ () => duplicateRepeaterItem( index ) }
|
|
159
|
+
toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
|
|
160
|
+
>
|
|
161
|
+
{ ( props ) => (
|
|
162
|
+
<itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
|
|
163
|
+
) }
|
|
164
|
+
</RepeaterItem>
|
|
165
|
+
</SortableItem>
|
|
166
|
+
);
|
|
167
|
+
} ) }
|
|
168
|
+
</SortableProvider>
|
|
169
|
+
) }
|
|
116
170
|
</SectionContent>
|
|
117
171
|
);
|
|
118
172
|
};
|
|
@@ -151,6 +205,7 @@ const RepeaterItem = ( {
|
|
|
151
205
|
<UnstableTag
|
|
152
206
|
label={ label }
|
|
153
207
|
showActionsOnHover
|
|
208
|
+
fullWidth
|
|
154
209
|
ref={ controlRef }
|
|
155
210
|
variant="outlined"
|
|
156
211
|
aria-label={ __( 'Open item', 'elementor' ) }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { GripVerticalIcon } from '@elementor/icons';
|
|
3
|
+
import {
|
|
4
|
+
Divider,
|
|
5
|
+
List,
|
|
6
|
+
ListItem,
|
|
7
|
+
styled,
|
|
8
|
+
UnstableSortableItem,
|
|
9
|
+
type UnstableSortableItemProps,
|
|
10
|
+
type UnstableSortableItemRenderProps,
|
|
11
|
+
UnstableSortableProvider,
|
|
12
|
+
type UnstableSortableProviderProps,
|
|
13
|
+
} from '@elementor/ui';
|
|
14
|
+
|
|
15
|
+
export const SortableProvider = < T extends number >( props: UnstableSortableProviderProps< T > ) => {
|
|
16
|
+
return (
|
|
17
|
+
<List sx={ { p: 0, m: 0 } }>
|
|
18
|
+
<UnstableSortableProvider
|
|
19
|
+
restrictAxis={ true }
|
|
20
|
+
disableDragOverlay={ false }
|
|
21
|
+
variant={ 'static' }
|
|
22
|
+
{ ...props }
|
|
23
|
+
/>
|
|
24
|
+
</List>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type SortableItemProps = {
|
|
29
|
+
id: UnstableSortableItemProps[ 'id' ];
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const SortableItem = ( { id, children }: SortableItemProps ): React.ReactNode => {
|
|
34
|
+
return (
|
|
35
|
+
<UnstableSortableItem
|
|
36
|
+
id={ id }
|
|
37
|
+
render={ ( {
|
|
38
|
+
itemProps,
|
|
39
|
+
triggerProps,
|
|
40
|
+
itemStyle,
|
|
41
|
+
triggerStyle,
|
|
42
|
+
isDragOverlay,
|
|
43
|
+
showDropIndication,
|
|
44
|
+
dropIndicationStyle,
|
|
45
|
+
}: UnstableSortableItemRenderProps ) => {
|
|
46
|
+
return (
|
|
47
|
+
<StyledListItem
|
|
48
|
+
{ ...itemProps }
|
|
49
|
+
style={ itemStyle }
|
|
50
|
+
sx={ { backgroundColor: isDragOverlay ? 'background.paper' : undefined } }
|
|
51
|
+
>
|
|
52
|
+
<SortableTrigger { ...triggerProps } style={ triggerStyle } />
|
|
53
|
+
{ children }
|
|
54
|
+
{ showDropIndication && <StyledDivider style={ dropIndicationStyle } /> }
|
|
55
|
+
</StyledListItem>
|
|
56
|
+
);
|
|
57
|
+
} }
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const StyledListItem = styled( ListItem )`
|
|
63
|
+
position: relative;
|
|
64
|
+
margin-inline: 0px;
|
|
65
|
+
padding-inline: 0px;
|
|
66
|
+
padding-block: ${ ( { theme } ) => theme.spacing( 0.5 ) };
|
|
67
|
+
|
|
68
|
+
& .class-item-sortable-trigger {
|
|
69
|
+
color: ${ ( { theme } ) => theme.palette.action.active };
|
|
70
|
+
height: 100%;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
visibility: hidden;
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: 50%;
|
|
76
|
+
padding-inline-end: ${ ( { theme } ) => theme.spacing( 0.5 ) };
|
|
77
|
+
transform: translate( -75%, -50% );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:hover {
|
|
81
|
+
& .class-item-sortable-trigger {
|
|
82
|
+
visibility: visible;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
const SortableTrigger = ( props: React.HTMLAttributes< HTMLDivElement > ) => (
|
|
88
|
+
<div { ...props } role="button" className="class-item-sortable-trigger">
|
|
89
|
+
<GripVerticalIcon fontSize="tiny" />
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const StyledDivider = styled( Divider )`
|
|
94
|
+
height: 0px;
|
|
95
|
+
border: none;
|
|
96
|
+
overflow: visible;
|
|
97
|
+
|
|
98
|
+
&:after {
|
|
99
|
+
--height: 2px;
|
|
100
|
+
content: '';
|
|
101
|
+
display: block;
|
|
102
|
+
width: 100%;
|
|
103
|
+
height: var( --height );
|
|
104
|
+
margin-block: calc( -1 * var( --height ) / 2 );
|
|
105
|
+
border-radius: ${ ( { theme } ) => theme.spacing( 0.5 ) };
|
|
106
|
+
background-color: ${ ( { theme } ) => theme.palette.text.primary };
|
|
107
|
+
}
|
|
108
|
+
`;
|