@elementor/editor-controls 4.2.0-856 → 4.2.0-857
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 +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +1001 -872
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +775 -643
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -15
- package/src/controls/query-filter-repeater-control.tsx +243 -0
- package/src/index.ts +1 -0
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": "4.2.0-
|
|
4
|
+
"version": "4.2.0-857",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,22 +40,22 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-current-user": "4.2.0-
|
|
44
|
-
"@elementor/editor-elements": "4.2.0-
|
|
45
|
-
"@elementor/editor-props": "4.2.0-
|
|
46
|
-
"@elementor/editor-responsive": "4.2.0-
|
|
47
|
-
"@elementor/editor-ui": "4.2.0-
|
|
48
|
-
"@elementor/editor-v1-adapters": "4.2.0-
|
|
49
|
-
"@elementor/env": "4.2.0-
|
|
50
|
-
"@elementor/events": "4.2.0-
|
|
51
|
-
"@elementor/http-client": "4.2.0-
|
|
43
|
+
"@elementor/editor-current-user": "4.2.0-857",
|
|
44
|
+
"@elementor/editor-elements": "4.2.0-857",
|
|
45
|
+
"@elementor/editor-props": "4.2.0-857",
|
|
46
|
+
"@elementor/editor-responsive": "4.2.0-857",
|
|
47
|
+
"@elementor/editor-ui": "4.2.0-857",
|
|
48
|
+
"@elementor/editor-v1-adapters": "4.2.0-857",
|
|
49
|
+
"@elementor/env": "4.2.0-857",
|
|
50
|
+
"@elementor/events": "4.2.0-857",
|
|
51
|
+
"@elementor/http-client": "4.2.0-857",
|
|
52
52
|
"@elementor/icons": "~1.75.1",
|
|
53
|
-
"@elementor/locations": "4.2.0-
|
|
54
|
-
"@elementor/query": "4.2.0-
|
|
55
|
-
"@elementor/session": "4.2.0-
|
|
53
|
+
"@elementor/locations": "4.2.0-857",
|
|
54
|
+
"@elementor/query": "4.2.0-857",
|
|
55
|
+
"@elementor/session": "4.2.0-857",
|
|
56
56
|
"@elementor/ui": "1.37.5",
|
|
57
|
-
"@elementor/utils": "4.2.0-
|
|
58
|
-
"@elementor/wp-media": "4.2.0-
|
|
57
|
+
"@elementor/utils": "4.2.0-857",
|
|
58
|
+
"@elementor/wp-media": "4.2.0-857",
|
|
59
59
|
"@monaco-editor/react": "^4.7.0",
|
|
60
60
|
"@tiptap/extension-bold": "^3.11.1",
|
|
61
61
|
"@tiptap/extension-document": "^3.11.1",
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo, useRef } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
type CreateOptions,
|
|
5
|
+
type PropKey,
|
|
6
|
+
queryFilterArrayPropTypeUtil,
|
|
7
|
+
type QueryFilterKeyConfig,
|
|
8
|
+
queryFilterPropTypeUtil,
|
|
9
|
+
type QueryFilterPropValue,
|
|
10
|
+
type QueryPropValue,
|
|
11
|
+
stringPropTypeUtil,
|
|
12
|
+
} from '@elementor/editor-props';
|
|
13
|
+
import { PlusIcon } from '@elementor/icons';
|
|
14
|
+
import { Box, Grid, IconButton } from '@elementor/ui';
|
|
15
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
16
|
+
|
|
17
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
18
|
+
import { ControlFormLabel } from '../components/control-form-label';
|
|
19
|
+
import { ControlRepeater, Item, ItemsContainer } from '../components/control-repeater';
|
|
20
|
+
import { RemoveItemAction } from '../components/control-repeater/actions/remove-item-action';
|
|
21
|
+
import { useRepeaterContext } from '../components/control-repeater/context/repeater-context';
|
|
22
|
+
import { EditItemPopover } from '../components/control-repeater/items/edit-item-popover';
|
|
23
|
+
import { PopoverContent } from '../components/popover-content';
|
|
24
|
+
import { PopoverGridContainer } from '../components/popover-grid-container';
|
|
25
|
+
import { RepeaterHeader } from '../components/repeater/repeater-header';
|
|
26
|
+
import { createControl } from '../create-control';
|
|
27
|
+
import { QueryChipsControl } from './query-chips-control';
|
|
28
|
+
import { SelectControl, type SelectOption } from './select-control';
|
|
29
|
+
|
|
30
|
+
type QueryFilterRepeaterControlProps = {
|
|
31
|
+
allowedKeys: string[];
|
|
32
|
+
keyConfig: Record< string, QueryFilterKeyConfig >;
|
|
33
|
+
label?: string;
|
|
34
|
+
chipsPlaceholder?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const QueryFilterRepeaterControl = createControl(
|
|
38
|
+
( {
|
|
39
|
+
allowedKeys,
|
|
40
|
+
keyConfig,
|
|
41
|
+
label = __( 'Filter', 'elementor' ),
|
|
42
|
+
chipsPlaceholder,
|
|
43
|
+
}: QueryFilterRepeaterControlProps ) => {
|
|
44
|
+
const { propType, value, setValue } = useBoundProp( queryFilterArrayPropTypeUtil );
|
|
45
|
+
|
|
46
|
+
const usedKeys = useMemo( () => getUsedKeys( value ?? [] ), [ value ] );
|
|
47
|
+
const nextAvailableKey = useMemo(
|
|
48
|
+
() => allowedKeys.find( ( key ) => ! usedKeys.has( key ) ) ?? null,
|
|
49
|
+
[ allowedKeys, usedKeys ]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const getKeySelectOptions = useMemo(
|
|
53
|
+
() => ( currentKey: string | null ) =>
|
|
54
|
+
allowedKeys.map( ( itemKey ) => ( {
|
|
55
|
+
value: itemKey,
|
|
56
|
+
label: keyConfig[ itemKey ]?.label ?? itemKey,
|
|
57
|
+
disabled: itemKey !== currentKey && usedKeys.has( itemKey ),
|
|
58
|
+
} ) ),
|
|
59
|
+
[ allowedKeys, keyConfig, usedKeys ]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const initialFallback = useMemo( () => createItemForKey( allowedKeys[ 0 ] ?? '' ), [ allowedKeys ] );
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
66
|
+
<ControlRepeater initial={ initialFallback } propTypeUtil={ queryFilterArrayPropTypeUtil }>
|
|
67
|
+
<RepeaterHeader label={ label }>
|
|
68
|
+
<AddFilterItemAction nextAvailableKey={ nextAvailableKey } ariaLabel={ label } />
|
|
69
|
+
</RepeaterHeader>
|
|
70
|
+
<ItemsContainer isSortable={ false }>
|
|
71
|
+
<Item
|
|
72
|
+
Icon={ EmptyIcon }
|
|
73
|
+
actions={ <RemoveItemAction /> }
|
|
74
|
+
Label={ ( { value: itemValue }: { value: QueryFilterPropValue } ) => (
|
|
75
|
+
<ItemLabel value={ itemValue } keyConfig={ keyConfig } />
|
|
76
|
+
) }
|
|
77
|
+
/>
|
|
78
|
+
</ItemsContainer>
|
|
79
|
+
<EditItemPopover>
|
|
80
|
+
<ItemContent
|
|
81
|
+
keyConfig={ keyConfig }
|
|
82
|
+
getKeySelectOptions={ getKeySelectOptions }
|
|
83
|
+
chipsPlaceholder={ chipsPlaceholder }
|
|
84
|
+
/>
|
|
85
|
+
</EditItemPopover>
|
|
86
|
+
</ControlRepeater>
|
|
87
|
+
</PropProvider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const AddFilterItemAction = ( {
|
|
93
|
+
nextAvailableKey,
|
|
94
|
+
ariaLabel,
|
|
95
|
+
}: {
|
|
96
|
+
nextAvailableKey: string | null;
|
|
97
|
+
ariaLabel: string;
|
|
98
|
+
} ) => {
|
|
99
|
+
const { addItem } = useRepeaterContext();
|
|
100
|
+
const disabled = nextAvailableKey === null;
|
|
101
|
+
|
|
102
|
+
const onClick = ( ev: React.MouseEvent ) => {
|
|
103
|
+
if ( ! nextAvailableKey ) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
addItem( ev, { item: createItemForKey( nextAvailableKey ), index: 0 } );
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Box component="span" sx={ { cursor: disabled ? 'not-allowed' : 'pointer' } }>
|
|
112
|
+
<IconButton
|
|
113
|
+
size="tiny"
|
|
114
|
+
disabled={ disabled }
|
|
115
|
+
onClick={ onClick }
|
|
116
|
+
/* Translators: %s: Aria label. */
|
|
117
|
+
aria-label={ sprintf( __( 'Add %s item', 'elementor' ), ariaLabel.toLowerCase() ) }
|
|
118
|
+
>
|
|
119
|
+
<PlusIcon fontSize="tiny" />
|
|
120
|
+
</IconButton>
|
|
121
|
+
</Box>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const EmptyIcon = () => null;
|
|
126
|
+
|
|
127
|
+
type ItemLabelProps = {
|
|
128
|
+
value: QueryFilterPropValue;
|
|
129
|
+
keyConfig: Record< string, QueryFilterKeyConfig >;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const ItemLabel = ( { value, keyConfig }: ItemLabelProps ) => {
|
|
133
|
+
const itemKey = stringPropTypeUtil.extract( value?.value?.key );
|
|
134
|
+
const label = ( itemKey && keyConfig[ itemKey ]?.label ) || __( 'Item', 'elementor' );
|
|
135
|
+
const chipLabels = extractChipLabels( value?.value?.values );
|
|
136
|
+
const suffix = chipLabels.length > 0 ? `: ${ chipLabels.join( ', ' ) }` : '';
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Box component="span">
|
|
140
|
+
{ label }
|
|
141
|
+
{ suffix }
|
|
142
|
+
</Box>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type QueryArrayPropValue = { value?: QueryPropValue[] | null };
|
|
147
|
+
|
|
148
|
+
function extractChipLabels< T extends QueryArrayPropValue | undefined >( chipsProp: T ): string[] {
|
|
149
|
+
const chips = chipsProp?.value ?? [];
|
|
150
|
+
|
|
151
|
+
return chips
|
|
152
|
+
.map( ( chip ) => stringPropTypeUtil.extract( chip?.value?.label ) )
|
|
153
|
+
.filter( ( label ): label is string => !! label );
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type FilterItemValue = NonNullable< QueryFilterPropValue[ 'value' ] >;
|
|
157
|
+
|
|
158
|
+
const ItemContent = ( {
|
|
159
|
+
keyConfig,
|
|
160
|
+
getKeySelectOptions,
|
|
161
|
+
chipsPlaceholder,
|
|
162
|
+
}: {
|
|
163
|
+
keyConfig: Record< string, QueryFilterKeyConfig >;
|
|
164
|
+
getKeySelectOptions: ( currentKey: string | null ) => SelectOption[];
|
|
165
|
+
chipsPlaceholder?: string;
|
|
166
|
+
} ) => {
|
|
167
|
+
const propContext = useBoundProp( queryFilterPropTypeUtil );
|
|
168
|
+
|
|
169
|
+
const valuesByKeyRef = useRef< Record< string, FilterItemValue[ 'values' ] | null > >( {} );
|
|
170
|
+
|
|
171
|
+
const handleValueChange = ( nextValue: FilterItemValue, options?: CreateOptions, meta?: { bind?: PropKey } ) => {
|
|
172
|
+
if ( meta?.bind !== 'key' ) {
|
|
173
|
+
propContext.setValue( nextValue, options, meta );
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const previousKey = stringPropTypeUtil.extract( propContext.value?.key );
|
|
178
|
+
const newKey = stringPropTypeUtil.extract( nextValue?.key );
|
|
179
|
+
|
|
180
|
+
if ( previousKey ) {
|
|
181
|
+
valuesByKeyRef.current[ previousKey ] = propContext.value?.values ?? null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const restoredValues = newKey ? valuesByKeyRef.current[ newKey ] ?? null : null;
|
|
185
|
+
|
|
186
|
+
propContext.setValue( { ...nextValue, values: restoredValues }, options, meta );
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const currentKey = stringPropTypeUtil.extract( propContext.value?.key );
|
|
190
|
+
const currentKeyConfig = currentKey ? keyConfig[ currentKey ] : undefined;
|
|
191
|
+
const hasValuesField = !! currentKeyConfig?.queryEndpoint;
|
|
192
|
+
const keySelectOptions = useMemo( () => getKeySelectOptions( currentKey ), [ getKeySelectOptions, currentKey ] );
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<PopoverContent p={ 1.5 }>
|
|
196
|
+
<PropProvider { ...propContext } setValue={ handleValueChange }>
|
|
197
|
+
<PopoverGridContainer flexWrap="wrap">
|
|
198
|
+
<Grid item xs={ 12 }>
|
|
199
|
+
<ControlFormLabel>{ __( 'Type', 'elementor' ) }</ControlFormLabel>
|
|
200
|
+
</Grid>
|
|
201
|
+
<Grid item xs={ 12 }>
|
|
202
|
+
<PropKeyProvider bind="key">
|
|
203
|
+
<SelectControl options={ keySelectOptions } />
|
|
204
|
+
</PropKeyProvider>
|
|
205
|
+
</Grid>
|
|
206
|
+
</PopoverGridContainer>
|
|
207
|
+
{ hasValuesField && currentKeyConfig?.queryEndpoint && (
|
|
208
|
+
<PopoverGridContainer flexWrap="wrap">
|
|
209
|
+
<Grid item xs={ 12 }>
|
|
210
|
+
<ControlFormLabel>{ currentKeyConfig.label }</ControlFormLabel>
|
|
211
|
+
</Grid>
|
|
212
|
+
<Grid item xs={ 12 }>
|
|
213
|
+
<PropKeyProvider bind="values">
|
|
214
|
+
<QueryChipsControl
|
|
215
|
+
queryOptions={ {
|
|
216
|
+
url: currentKeyConfig.queryEndpoint.url,
|
|
217
|
+
params: currentKeyConfig.queryEndpoint.params ?? {},
|
|
218
|
+
} }
|
|
219
|
+
placeholder={ currentKeyConfig.chipsPlaceholder ?? chipsPlaceholder }
|
|
220
|
+
/>
|
|
221
|
+
</PropKeyProvider>
|
|
222
|
+
</Grid>
|
|
223
|
+
</PopoverGridContainer>
|
|
224
|
+
) }
|
|
225
|
+
</PropProvider>
|
|
226
|
+
</PopoverContent>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
function createItemForKey( key: string ): QueryFilterPropValue {
|
|
231
|
+
return queryFilterPropTypeUtil.create( {
|
|
232
|
+
key: stringPropTypeUtil.create( key ),
|
|
233
|
+
values: null,
|
|
234
|
+
} );
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getUsedKeys( items: QueryFilterPropValue[] ): Set< string > {
|
|
238
|
+
const keys = items
|
|
239
|
+
.map( ( item ) => stringPropTypeUtil.extract( item?.value?.key ) )
|
|
240
|
+
.filter( ( key ): key is string => !! key );
|
|
241
|
+
|
|
242
|
+
return new Set( keys );
|
|
243
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { LinkControl } from './controls/link-control';
|
|
|
22
22
|
export { HtmlTagControl } from './controls/html-tag-control';
|
|
23
23
|
export { QueryChipsControl } from './controls/query-chips-control';
|
|
24
24
|
export { QueryControl } from './controls/query-control';
|
|
25
|
+
export { QueryFilterRepeaterControl } from './controls/query-filter-repeater-control';
|
|
25
26
|
export { GapControl } from './controls/gap-control';
|
|
26
27
|
export { AspectRatioControl } from './controls/aspect-ratio-control';
|
|
27
28
|
export { SvgMediaControl } from './controls/svg-media-control';
|