@elementor/editor-controls 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.
- package/dist/index.d.mts +264 -74
- package/dist/index.d.ts +264 -74
- package/dist/index.js +2541 -1861
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2344 -1660
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -17
- package/src/bound-prop-context/prop-context.tsx +8 -1
- package/src/bound-prop-context/use-bound-prop.ts +19 -5
- package/src/components/autocomplete.tsx +34 -3
- package/src/components/conditional-control-infotip.tsx +64 -0
- package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
- package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
- package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
- package/src/components/control-repeater/context/item-context.tsx +8 -0
- package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
- package/src/components/control-repeater/control-repeater.tsx +29 -0
- package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
- package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
- package/src/components/control-repeater/items/item.tsx +75 -0
- package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
- package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
- package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
- package/src/components/control-toggle-button-group.tsx +79 -69
- package/src/components/enable-unfiltered-modal.tsx +1 -26
- package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
- package/src/components/inline-editor-toolbar.tsx +137 -0
- package/src/components/inline-editor.tsx +111 -0
- package/src/components/item-selector.tsx +10 -4
- package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
- package/src/components/repeater/repeater-popover.tsx +19 -0
- package/src/components/repeater/repeater-tag.tsx +16 -0
- package/src/components/repeater/repeater.tsx +405 -0
- package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
- package/src/components/size-control/size-input.tsx +20 -14
- package/src/components/size-control/text-field-inner-selection.tsx +15 -2
- package/src/control-adornments/control-adornments-context.tsx +5 -4
- package/src/control-replacements.tsx +3 -43
- package/src/controls/background-control/background-control.tsx +43 -12
- package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
- package/src/controls/box-shadow-repeater-control.tsx +38 -21
- package/src/controls/color-control.tsx +3 -1
- package/src/controls/date-time-control.tsx +108 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
- package/src/controls/filter-control/filter-content.tsx +1 -1
- package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
- package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
- package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
- package/src/controls/font-family-control/font-family-control.tsx +66 -55
- package/src/controls/html-tag-control.tsx +90 -0
- package/src/controls/image-media-control.tsx +2 -2
- package/src/controls/inline-editing-control.tsx +18 -0
- package/src/controls/key-value-control.tsx +8 -2
- package/src/controls/link-control.tsx +23 -123
- package/src/controls/linked-dimensions-control.tsx +71 -33
- package/src/controls/query-control.tsx +168 -0
- package/src/controls/repeatable-control.tsx +62 -27
- package/src/controls/select-control-wrapper.tsx +57 -0
- package/src/controls/select-control.tsx +9 -5
- package/src/controls/selection-size-control.tsx +13 -2
- package/src/controls/size-control.tsx +32 -59
- package/src/controls/svg-media-control.tsx +33 -10
- package/src/controls/text-area-control.tsx +5 -1
- package/src/controls/text-control.tsx +5 -0
- package/src/controls/toggle-control.tsx +11 -2
- package/src/controls/transform-control/functions/axis-row.tsx +1 -0
- package/src/controls/transform-control/transform-icon.tsx +2 -2
- package/src/controls/transform-control/transform-label.tsx +15 -32
- package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
- package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
- package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
- package/src/controls/transition-control/data.ts +16 -1
- package/src/controls/transition-control/trainsition-events.ts +2 -2
- package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
- package/src/controls/transition-control/transition-selector.tsx +37 -14
- package/src/controls/url-control.tsx +21 -16
- package/src/hooks/use-filtered-items-list.ts +3 -2
- package/src/hooks/use-repeatable-control-context.ts +3 -0
- package/src/hooks/use-sync-external-state.tsx +0 -1
- package/src/index.ts +21 -5
- package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
- package/src/utils/escape-html-attr.ts +11 -0
- package/src/components/css-code-editor/css-editor.styles.ts +0 -52
- package/src/components/css-code-editor/css-editor.tsx +0 -142
- package/src/components/css-code-editor/css-validation.ts +0 -75
- package/src/components/css-code-editor/resize-handle.tsx +0 -55
- package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
- package/src/components/repeater.tsx +0 -343
- package/src/components/unstable-repeater/items/item.tsx +0 -77
- package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
- /package/src/components/{unstable-repeater → control-repeater}/actions/tooltip-add-item-action.tsx +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { numberPropTypeUtil, stringPropTypeUtil, urlPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
5
|
+
import { SearchIcon } from '@elementor/icons';
|
|
6
|
+
import { debounce } from '@elementor/utils';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
|
|
9
|
+
import { useBoundProp } from '../bound-prop-context';
|
|
10
|
+
import {
|
|
11
|
+
Autocomplete,
|
|
12
|
+
type CategorizedOption,
|
|
13
|
+
findMatchingOption,
|
|
14
|
+
type FlatOption,
|
|
15
|
+
isCategorizedOptionPool,
|
|
16
|
+
} from '../components/autocomplete';
|
|
17
|
+
import ControlActions from '../control-actions/control-actions';
|
|
18
|
+
import { createControl } from '../create-control';
|
|
19
|
+
import { type DestinationProp } from './link-control';
|
|
20
|
+
|
|
21
|
+
type Props = {
|
|
22
|
+
queryOptions: {
|
|
23
|
+
params: Record< string, unknown >;
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
allowCustomValues?: boolean;
|
|
27
|
+
minInputLength?: number;
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
onSetValue?: ( value: DestinationProp | null ) => void;
|
|
30
|
+
ariaLabel?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type Response = HttpResponse< { value: FlatOption[] | CategorizedOption[] } >;
|
|
34
|
+
|
|
35
|
+
type FetchOptionsParams = Record< string, unknown > & { term: string };
|
|
36
|
+
|
|
37
|
+
export const QueryControl = createControl( ( props: Props ) => {
|
|
38
|
+
const { value, setValue } = useBoundProp< DestinationProp >();
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
allowCustomValues = true,
|
|
42
|
+
queryOptions: { url, params = {} },
|
|
43
|
+
placeholder,
|
|
44
|
+
minInputLength = 2,
|
|
45
|
+
onSetValue,
|
|
46
|
+
ariaLabel,
|
|
47
|
+
} = props || {};
|
|
48
|
+
|
|
49
|
+
const normalizedPlaceholder = placeholder || __( 'Search', 'elementor' );
|
|
50
|
+
|
|
51
|
+
const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
|
|
52
|
+
generateFirstLoadedOption( value?.value )
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const onOptionChange = ( newValue: number | null ) => {
|
|
56
|
+
if ( newValue === null ) {
|
|
57
|
+
setValue( null );
|
|
58
|
+
onSetValue?.( null );
|
|
59
|
+
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const valueToSave = {
|
|
64
|
+
$$type: 'query',
|
|
65
|
+
value: {
|
|
66
|
+
id: numberPropTypeUtil.create( newValue ),
|
|
67
|
+
label: stringPropTypeUtil.create( findMatchingOption( options, newValue )?.label || null ),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
setValue( valueToSave );
|
|
72
|
+
onSetValue?.( valueToSave );
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const onTextChange = ( newValue: string | null ) => {
|
|
76
|
+
if ( ! newValue ) {
|
|
77
|
+
setValue( null );
|
|
78
|
+
onSetValue?.( null );
|
|
79
|
+
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const newLinkValue = newValue?.trim() || '';
|
|
84
|
+
const valueToSave = newLinkValue ? urlPropTypeUtil.create( newLinkValue ) : null;
|
|
85
|
+
|
|
86
|
+
setValue( valueToSave );
|
|
87
|
+
onSetValue?.( valueToSave );
|
|
88
|
+
updateOptions( newValue );
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const updateOptions = ( newValue: string | null ) => {
|
|
92
|
+
setOptions( [] );
|
|
93
|
+
|
|
94
|
+
if ( ! newValue || ! url || newValue.length < minInputLength ) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
debounceFetch( { ...params, term: newValue } );
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const debounceFetch = useMemo(
|
|
102
|
+
() =>
|
|
103
|
+
debounce(
|
|
104
|
+
( queryParams: FetchOptionsParams ) =>
|
|
105
|
+
fetchOptions( url, queryParams ).then( ( newOptions ) => {
|
|
106
|
+
setOptions( formatOptions( newOptions ) );
|
|
107
|
+
} ),
|
|
108
|
+
400
|
|
109
|
+
),
|
|
110
|
+
[ url ]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<ControlActions>
|
|
115
|
+
<Autocomplete
|
|
116
|
+
options={ options }
|
|
117
|
+
allowCustomValues={ allowCustomValues }
|
|
118
|
+
placeholder={ normalizedPlaceholder }
|
|
119
|
+
startAdornment={ <SearchIcon fontSize="tiny" /> }
|
|
120
|
+
value={ value?.value?.id?.value || value?.value }
|
|
121
|
+
onOptionChange={ onOptionChange }
|
|
122
|
+
onTextChange={ onTextChange }
|
|
123
|
+
minInputLength={ minInputLength }
|
|
124
|
+
disablePortal={ false }
|
|
125
|
+
inputProps={ {
|
|
126
|
+
...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
|
|
127
|
+
} }
|
|
128
|
+
/>
|
|
129
|
+
</ControlActions>
|
|
130
|
+
);
|
|
131
|
+
} );
|
|
132
|
+
|
|
133
|
+
async function fetchOptions( ajaxUrl: string, params: FetchOptionsParams ) {
|
|
134
|
+
if ( ! params || ! ajaxUrl ) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const { data: response } = await httpService().get< Response >( ajaxUrl, { params } );
|
|
140
|
+
|
|
141
|
+
return response.data.value;
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatOptions( options: FlatOption[] | CategorizedOption[] ): FlatOption[] | CategorizedOption[] {
|
|
148
|
+
const compareKey = isCategorizedOptionPool( options ) ? 'groupLabel' : 'label';
|
|
149
|
+
|
|
150
|
+
return options.sort( ( a, b ) =>
|
|
151
|
+
a[ compareKey ] && b[ compareKey ] ? a[ compareKey ].localeCompare( b[ compareKey ] ) : 0
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function generateFirstLoadedOption( unionValue: DestinationProp | null ): FlatOption[] {
|
|
156
|
+
const value = unionValue?.id?.value;
|
|
157
|
+
const label = unionValue?.label?.value;
|
|
158
|
+
const type = unionValue?.id?.$$type || 'url';
|
|
159
|
+
|
|
160
|
+
return value && label && type === 'number'
|
|
161
|
+
? [
|
|
162
|
+
{
|
|
163
|
+
id: value.toString(),
|
|
164
|
+
label,
|
|
165
|
+
},
|
|
166
|
+
]
|
|
167
|
+
: [];
|
|
168
|
+
}
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import { createArrayPropUtils } from '@elementor/editor-props';
|
|
3
|
+
import { createArrayPropUtils, type SizePropValue } from '@elementor/editor-props';
|
|
4
4
|
import { Box } from '@elementor/ui';
|
|
5
5
|
|
|
6
6
|
import { PropProvider, useBoundProp } from '../bound-prop-context';
|
|
7
|
+
import { ControlRepeater, Item, TooltipAddItemAction } from '../components/control-repeater';
|
|
8
|
+
import { DisableItemAction } from '../components/control-repeater/actions/disable-item-action';
|
|
9
|
+
import { DuplicateItemAction } from '../components/control-repeater/actions/duplicate-item-action';
|
|
10
|
+
import { RemoveItemAction } from '../components/control-repeater/actions/remove-item-action';
|
|
11
|
+
import { type TooltipAddItemActionProps } from '../components/control-repeater/actions/tooltip-add-item-action';
|
|
12
|
+
import { EditItemPopover } from '../components/control-repeater/items/edit-item-popover';
|
|
13
|
+
import { ItemsContainer } from '../components/control-repeater/items/items-container';
|
|
14
|
+
import { type CollectionPropUtil, type RepeatablePropValue } from '../components/control-repeater/types';
|
|
7
15
|
import { PopoverContent } from '../components/popover-content';
|
|
8
16
|
import { PopoverGridContainer } from '../components/popover-grid-container';
|
|
9
|
-
import {
|
|
10
|
-
import { Header, Item, TooltipAddItemAction, UnstableRepeater } from '../components/unstable-repeater';
|
|
11
|
-
import { DisableItemAction } from '../components/unstable-repeater/actions/disable-item-action';
|
|
12
|
-
import { DuplicateItemAction } from '../components/unstable-repeater/actions/duplicate-item-action';
|
|
13
|
-
import { RemoveItemAction } from '../components/unstable-repeater/actions/remove-item-action';
|
|
14
|
-
import { type TooltipAddItemActionProps } from '../components/unstable-repeater/actions/tooltip-add-item-action';
|
|
15
|
-
import { EditItemPopover } from '../components/unstable-repeater/items/edit-item-popover';
|
|
16
|
-
import { ItemsContainer } from '../components/unstable-repeater/items/items-container';
|
|
17
|
-
import { type RepeatablePropValue } from '../components/unstable-repeater/types';
|
|
17
|
+
import { RepeaterHeader } from '../components/repeater/repeater-header';
|
|
18
18
|
import { createControl } from '../create-control';
|
|
19
19
|
import {
|
|
20
20
|
type ChildControlConfig,
|
|
21
21
|
RepeatableControlContext,
|
|
22
22
|
useRepeatableControlContext,
|
|
23
23
|
} from '../hooks/use-repeatable-control-context';
|
|
24
|
+
import { CUSTOM_SIZE_LABEL } from './size-control';
|
|
24
25
|
|
|
25
26
|
type RepeatableControlProps = {
|
|
26
27
|
label: string;
|
|
@@ -49,7 +50,7 @@ export const RepeatableControl = createControl(
|
|
|
49
50
|
propKey,
|
|
50
51
|
addItemTooltipProps,
|
|
51
52
|
}: RepeatableControlProps ) => {
|
|
52
|
-
const { propTypeUtil: childPropTypeUtil } = childControlConfig;
|
|
53
|
+
const { propTypeUtil: childPropTypeUtil, isItemDisabled } = childControlConfig;
|
|
53
54
|
|
|
54
55
|
if ( ! childPropTypeUtil ) {
|
|
55
56
|
return null;
|
|
@@ -74,29 +75,35 @@ export const RepeatableControl = createControl(
|
|
|
74
75
|
return (
|
|
75
76
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
76
77
|
<RepeatableControlContext.Provider value={ contextValue }>
|
|
77
|
-
<
|
|
78
|
+
<ControlRepeater
|
|
78
79
|
initial={ childPropTypeUtil.create( initialValues || null ) }
|
|
79
80
|
propTypeUtil={ childArrayPropTypeUtil as CollectionPropUtil< RepeatablePropValue > }
|
|
81
|
+
isItemDisabled={ isItemDisabled }
|
|
80
82
|
>
|
|
81
|
-
<
|
|
83
|
+
<RepeaterHeader label={ repeaterLabel }>
|
|
82
84
|
<TooltipAddItemAction
|
|
83
85
|
{ ...addItemTooltipProps }
|
|
84
86
|
newItemIndex={ 0 }
|
|
85
87
|
ariaLabel={ repeaterLabel }
|
|
86
88
|
/>
|
|
87
|
-
</
|
|
88
|
-
<ItemsContainer
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
</RepeaterHeader>
|
|
90
|
+
<ItemsContainer isSortable={ false }>
|
|
91
|
+
<Item
|
|
92
|
+
Icon={ ItemIcon }
|
|
93
|
+
Label={ ItemLabel }
|
|
94
|
+
actions={
|
|
95
|
+
<>
|
|
96
|
+
{ showDuplicate && <DuplicateItemAction /> }
|
|
97
|
+
{ showToggle && <DisableItemAction /> }
|
|
98
|
+
<RemoveItemAction />
|
|
99
|
+
</>
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
95
102
|
</ItemsContainer>
|
|
96
103
|
<EditItemPopover>
|
|
97
104
|
<Content />
|
|
98
105
|
</EditItemPopover>
|
|
99
|
-
</
|
|
106
|
+
</ControlRepeater>
|
|
100
107
|
</RepeatableControlContext.Provider>
|
|
101
108
|
</PropProvider>
|
|
102
109
|
);
|
|
@@ -126,7 +133,7 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
|
|
|
126
133
|
const value = getNestedValue( data, path );
|
|
127
134
|
|
|
128
135
|
if ( typeof value === 'object' && value !== null && ! Array.isArray( value ) ) {
|
|
129
|
-
if ( value.name ) {
|
|
136
|
+
if ( 'name' in value && value.name ) {
|
|
130
137
|
return value.name as string;
|
|
131
138
|
}
|
|
132
139
|
|
|
@@ -142,12 +149,32 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
|
|
|
142
149
|
};
|
|
143
150
|
|
|
144
151
|
const getNestedValue = ( obj: Record< string, unknown >, path: string ) => {
|
|
145
|
-
|
|
152
|
+
let parentObj: Record< string, unknown > = {};
|
|
153
|
+
const pathKeys = path.split( '.' );
|
|
154
|
+
const key = pathKeys.slice( -1 )[ 0 ];
|
|
155
|
+
|
|
156
|
+
let value: unknown = pathKeys.reduce( ( current: Record< string, unknown >, currentKey, currentIndex ) => {
|
|
157
|
+
if ( currentIndex === pathKeys.length - 2 ) {
|
|
158
|
+
parentObj = current;
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
if ( current && typeof current === 'object' ) {
|
|
147
|
-
return current[
|
|
162
|
+
return current[ currentKey ] as Record< string, unknown >;
|
|
148
163
|
}
|
|
164
|
+
|
|
149
165
|
return {};
|
|
150
166
|
}, obj );
|
|
167
|
+
|
|
168
|
+
value = !! value ? value : '';
|
|
169
|
+
const propType = parentObj?.$$type;
|
|
170
|
+
const propValue = parentObj?.value as SizePropValue[ 'value' ];
|
|
171
|
+
const doesValueRepresentCustomSize = key === 'unit' && propType === 'size' && propValue?.unit === 'custom';
|
|
172
|
+
|
|
173
|
+
if ( ! doesValueRepresentCustomSize ) {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return propValue?.size ? '' : CUSTOM_SIZE_LABEL;
|
|
151
178
|
};
|
|
152
179
|
|
|
153
180
|
const isEmptyValue = ( val: unknown ) => {
|
|
@@ -190,11 +217,19 @@ const shouldShowPlaceholder = ( pattern: string, data: Record< string, unknown >
|
|
|
190
217
|
return false;
|
|
191
218
|
};
|
|
192
219
|
|
|
220
|
+
const getTextColor = ( isReadOnly: boolean, showPlaceholder: boolean ): string => {
|
|
221
|
+
if ( isReadOnly ) {
|
|
222
|
+
return 'text.disabled';
|
|
223
|
+
}
|
|
224
|
+
return showPlaceholder ? 'text.tertiary' : 'text.primary';
|
|
225
|
+
};
|
|
226
|
+
|
|
193
227
|
const ItemLabel = ( { value }: { value: Record< string, unknown > } ) => {
|
|
194
|
-
const { placeholder, patternLabel } = useRepeatableControlContext();
|
|
228
|
+
const { placeholder, patternLabel, props: childProps } = useRepeatableControlContext();
|
|
195
229
|
const showPlaceholder = shouldShowPlaceholder( patternLabel, value );
|
|
196
230
|
const label = showPlaceholder ? placeholder : interpolate( patternLabel, value );
|
|
197
|
-
const
|
|
231
|
+
const isReadOnly = !! childProps?.readOnly;
|
|
232
|
+
const color = getTextColor( isReadOnly, showPlaceholder );
|
|
198
233
|
|
|
199
234
|
return (
|
|
200
235
|
<Box component="span" color={ color }>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { createControl } from '../create-control';
|
|
5
|
+
import { SelectControl, type SelectOption } from './select-control';
|
|
6
|
+
|
|
7
|
+
type ExtendedWindow = Window & {
|
|
8
|
+
elementor: { $previewContents: [ HTMLIFrameElement ]; config: { document: { id: string } } };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const getOffCanvasElements = () => {
|
|
12
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
13
|
+
const documentId = extendedWindow.elementor.config.document.id;
|
|
14
|
+
const offCanvasElements = extendedWindow.elementor.$previewContents[ 0 ].querySelectorAll(
|
|
15
|
+
`[data-elementor-id="${ documentId }"] .elementor-widget-off-canvas.elementor-element-edit-mode`
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return Array.from( offCanvasElements as unknown as HTMLElement[] ).map( ( offCanvasElement ) => {
|
|
19
|
+
return {
|
|
20
|
+
label: offCanvasElement.querySelector( '.e-off-canvas' )?.getAttribute( 'aria-label' ) ?? '',
|
|
21
|
+
value: offCanvasElement.dataset.id,
|
|
22
|
+
} as SelectOption;
|
|
23
|
+
} );
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const collectionMethods = {
|
|
27
|
+
'off-canvas': getOffCanvasElements,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
type SelectControlWrapperProps = Parameters< typeof SelectControl >[ 0 ] & {
|
|
31
|
+
collectionId?: keyof typeof collectionMethods;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const useDynamicOptions = (
|
|
35
|
+
collectionId?: keyof typeof collectionMethods,
|
|
36
|
+
initialOptions?: SelectControlWrapperProps[ 'options' ]
|
|
37
|
+
) => {
|
|
38
|
+
const [ options, setOptions ] = useState< SelectControlWrapperProps[ 'options' ] >( initialOptions ?? [] );
|
|
39
|
+
|
|
40
|
+
useEffect( () => {
|
|
41
|
+
if ( ! collectionId || ! collectionMethods[ collectionId ] ) {
|
|
42
|
+
setOptions( initialOptions ?? [] );
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setOptions( collectionMethods[ collectionId ]() );
|
|
46
|
+
}, [ collectionId, initialOptions ] );
|
|
47
|
+
|
|
48
|
+
return options;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const SelectControlWrapper = createControl(
|
|
52
|
+
( { collectionId, options, ...props }: SelectControlWrapperProps ) => {
|
|
53
|
+
const actualOptions = useDynamicOptions( collectionId, options );
|
|
54
|
+
|
|
55
|
+
return <SelectControl options={ actualOptions } { ...props } />;
|
|
56
|
+
}
|
|
57
|
+
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
|
|
3
3
|
import { MenuListItem } from '@elementor/editor-ui';
|
|
4
|
-
import { Select, type SelectChangeEvent, Typography } from '@elementor/ui';
|
|
4
|
+
import { Select, type SelectChangeEvent, type SelectProps, Typography } from '@elementor/ui';
|
|
5
5
|
|
|
6
6
|
import { useBoundProp } from '../bound-prop-context';
|
|
7
7
|
import ControlActions from '../control-actions/control-actions';
|
|
@@ -13,12 +13,13 @@ export type SelectOption = {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
type
|
|
16
|
+
type SelectControlProps = {
|
|
17
17
|
options: SelectOption[];
|
|
18
18
|
onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
|
|
19
|
+
MenuProps?: SelectProps[ 'MenuProps' ];
|
|
20
|
+
ariaLabel?: string;
|
|
19
21
|
};
|
|
20
|
-
|
|
21
|
-
export const SelectControl = createControl( ( { options, onChange }: Props ) => {
|
|
22
|
+
export const SelectControl = createControl( ( { options, onChange, MenuProps, ariaLabel }: SelectControlProps ) => {
|
|
22
23
|
const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
|
|
23
24
|
const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
|
|
24
25
|
const newValue = event.target.value || null;
|
|
@@ -26,6 +27,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
26
27
|
onChange?.( newValue, value );
|
|
27
28
|
setValue( newValue );
|
|
28
29
|
};
|
|
30
|
+
const isDisabled = disabled || options.length === 0;
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<ControlActions>
|
|
@@ -33,6 +35,8 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
33
35
|
sx={ { overflow: 'hidden' } }
|
|
34
36
|
displayEmpty
|
|
35
37
|
size="tiny"
|
|
38
|
+
MenuProps={ MenuProps }
|
|
39
|
+
aria-label={ ariaLabel || placeholder }
|
|
36
40
|
renderValue={ ( selectedValue: string | null ) => {
|
|
37
41
|
const findOptionByValue = ( searchValue: string | null ) =>
|
|
38
42
|
options.find( ( opt ) => opt.value === searchValue );
|
|
@@ -55,7 +59,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
55
59
|
} }
|
|
56
60
|
value={ value ?? '' }
|
|
57
61
|
onChange={ handleChange }
|
|
58
|
-
disabled={
|
|
62
|
+
disabled={ isDisabled }
|
|
59
63
|
fullWidth
|
|
60
64
|
>
|
|
61
65
|
{ options.map( ( { label, ...props } ) => (
|
|
@@ -20,13 +20,22 @@ type SelectionSizeControlProps = {
|
|
|
20
20
|
sizeLabel: string;
|
|
21
21
|
selectionConfig: SelectionComponentConfig;
|
|
22
22
|
sizeConfigMap: Record< string, SizeControlConfig >;
|
|
23
|
+
isRepeaterControl?: boolean;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
export const SelectionSizeControl = createControl(
|
|
26
|
-
( {
|
|
27
|
+
( {
|
|
28
|
+
selectionLabel,
|
|
29
|
+
sizeLabel,
|
|
30
|
+
selectionConfig,
|
|
31
|
+
sizeConfigMap,
|
|
32
|
+
isRepeaterControl = false,
|
|
33
|
+
}: SelectionSizeControlProps ) => {
|
|
27
34
|
const { value, setValue, propType } = useBoundProp( selectionSizePropTypeUtil );
|
|
28
35
|
const rowRef = useRef< HTMLDivElement >( null );
|
|
29
36
|
|
|
37
|
+
const sizeFieldId = sizeLabel.replace( /\s+/g, '-' ).toLowerCase();
|
|
38
|
+
|
|
30
39
|
const currentSizeConfig = useMemo( () => {
|
|
31
40
|
switch ( value.selection.$$type ) {
|
|
32
41
|
case 'key-value':
|
|
@@ -53,7 +62,7 @@ export const SelectionSizeControl = createControl(
|
|
|
53
62
|
{ currentSizeConfig && (
|
|
54
63
|
<>
|
|
55
64
|
<Grid item xs={ 6 } sx={ { display: 'flex', alignItems: 'center' } }>
|
|
56
|
-
<ControlFormLabel>{ sizeLabel }</ControlFormLabel>
|
|
65
|
+
<ControlFormLabel htmlFor={ sizeFieldId }>{ sizeLabel }</ControlFormLabel>
|
|
57
66
|
</Grid>
|
|
58
67
|
<Grid item xs={ 6 }>
|
|
59
68
|
<PropKeyProvider bind="size">
|
|
@@ -62,6 +71,8 @@ export const SelectionSizeControl = createControl(
|
|
|
62
71
|
variant={ currentSizeConfig.variant }
|
|
63
72
|
units={ currentSizeConfig.units }
|
|
64
73
|
defaultUnit={ currentSizeConfig.defaultUnit }
|
|
74
|
+
id={ sizeFieldId }
|
|
75
|
+
isRepeaterControl={ isRepeaterControl }
|
|
65
76
|
/>
|
|
66
77
|
</PropKeyProvider>
|
|
67
78
|
</Grid>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type RefObject, useEffect,
|
|
2
|
+
import { type RefObject, useEffect, useMemo } from 'react';
|
|
3
3
|
import { type PropType, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
|
|
4
4
|
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
5
5
|
import { usePopupState } from '@elementor/ui';
|
|
@@ -42,6 +42,8 @@ type BaseSizeControlProps = {
|
|
|
42
42
|
min?: number;
|
|
43
43
|
enablePropTypeUnits?: boolean;
|
|
44
44
|
id?: string;
|
|
45
|
+
ariaLabel?: string;
|
|
46
|
+
isRepeaterControl?: boolean;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
type LengthSizeControlProps = BaseSizeControlProps &
|
|
@@ -79,6 +81,8 @@ const defaultUnits: Record< SizeControlProps[ 'variant' ], Unit[] > = {
|
|
|
79
81
|
time: [ ...timeUnits ] as TimeUnit[],
|
|
80
82
|
} as const;
|
|
81
83
|
|
|
84
|
+
export const CUSTOM_SIZE_LABEL = 'fx';
|
|
85
|
+
|
|
82
86
|
export const SizeControl = createControl(
|
|
83
87
|
( {
|
|
84
88
|
variant = 'length' as SizeControlProps[ 'variant' ],
|
|
@@ -92,6 +96,7 @@ export const SizeControl = createControl(
|
|
|
92
96
|
min = 0,
|
|
93
97
|
enablePropTypeUnits = false,
|
|
94
98
|
id,
|
|
99
|
+
ariaLabel,
|
|
95
100
|
}: Omit< SizeControlProps, 'variant' > & { variant?: SizeVariant } ) => {
|
|
96
101
|
const {
|
|
97
102
|
value: sizeValue,
|
|
@@ -101,29 +106,24 @@ export const SizeControl = createControl(
|
|
|
101
106
|
placeholder: externalPlaceholder,
|
|
102
107
|
propType,
|
|
103
108
|
} = useBoundProp( sizePropTypeUtil );
|
|
109
|
+
|
|
104
110
|
const actualDefaultUnit = defaultUnit ?? externalPlaceholder?.unit ?? defaultSelectedUnit[ variant ];
|
|
105
|
-
const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, actualDefaultUnit ) );
|
|
106
111
|
const activeBreakpoint = useActiveBreakpoint();
|
|
107
112
|
const actualUnits = resolveUnits( propType, enablePropTypeUnits, variant, units );
|
|
108
113
|
|
|
109
114
|
const actualExtendedOptions = useSizeExtendedOptions( extendedOptions || [], disableCustom ?? false );
|
|
110
115
|
const popupState = usePopupState( { variant: 'popover' } );
|
|
111
116
|
|
|
117
|
+
const memorizedExternalState = useMemo(
|
|
118
|
+
() => createStateFromSizeProp( sizeValue, actualDefaultUnit ),
|
|
119
|
+
[ sizeValue, actualDefaultUnit ]
|
|
120
|
+
);
|
|
121
|
+
|
|
112
122
|
const [ state, setState ] = useSyncExternalState( {
|
|
113
|
-
external:
|
|
123
|
+
external: memorizedExternalState,
|
|
114
124
|
setExternal: ( newState: State | null, options, meta ) =>
|
|
115
125
|
setSizeValue( extractValueFromState( newState ), options, meta ),
|
|
116
|
-
persistWhen: ( newState ) =>
|
|
117
|
-
if ( ! newState?.unit ) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if ( isUnitExtendedOption( newState.unit ) ) {
|
|
122
|
-
return newState.unit === 'auto' ? true : !! newState.custom;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return !! newState?.numeric || newState?.numeric === 0;
|
|
126
|
-
},
|
|
126
|
+
persistWhen: ( newState ) => !! extractValueFromState( newState ),
|
|
127
127
|
fallback: ( newState ) => ( {
|
|
128
128
|
unit: newState?.unit ?? actualDefaultUnit,
|
|
129
129
|
numeric: newState?.numeric ?? DEFAULT_SIZE,
|
|
@@ -132,7 +132,7 @@ export const SizeControl = createControl(
|
|
|
132
132
|
} );
|
|
133
133
|
|
|
134
134
|
const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
|
|
135
|
-
extractValueFromState( state ) || {};
|
|
135
|
+
extractValueFromState( state, true ) || {};
|
|
136
136
|
|
|
137
137
|
const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
|
|
138
138
|
if ( newUnit === 'custom' ) {
|
|
@@ -169,40 +169,14 @@ export const SizeControl = createControl(
|
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
state.unit === 'custom' ? state.unit : actualDefaultUnit,
|
|
176
|
-
'',
|
|
177
|
-
state.custom
|
|
178
|
-
);
|
|
179
|
-
const currentUnitType = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
|
|
180
|
-
const mergedStates = {
|
|
181
|
-
...state,
|
|
182
|
-
unit: newState.unit ?? state.unit,
|
|
183
|
-
[ currentUnitType ]: newState[ currentUnitType ],
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
if ( mergedStates.unit !== 'auto' && areStatesEqual( state, mergedStates ) ) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if ( state.unit === newState.unit ) {
|
|
191
|
-
setInternalState( mergedStates );
|
|
192
|
-
|
|
193
|
-
return;
|
|
172
|
+
const maybeClosePopup = React.useCallback( () => {
|
|
173
|
+
if ( popupState && popupState.isOpen ) {
|
|
174
|
+
popupState.close();
|
|
194
175
|
}
|
|
195
|
-
|
|
196
|
-
setState( newState );
|
|
197
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
|
-
}, [ sizeValue ] );
|
|
176
|
+
}, [ popupState ] );
|
|
199
177
|
|
|
200
178
|
useEffect( () => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if ( activeBreakpoint && ! areStatesEqual( newState, state ) ) {
|
|
204
|
-
setState( newState );
|
|
205
|
-
}
|
|
179
|
+
maybeClosePopup();
|
|
206
180
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
207
181
|
}, [ activeBreakpoint ] );
|
|
208
182
|
|
|
@@ -222,6 +196,7 @@ export const SizeControl = createControl(
|
|
|
222
196
|
popupState={ popupState }
|
|
223
197
|
min={ min }
|
|
224
198
|
id={ id }
|
|
199
|
+
ariaLabel={ ariaLabel }
|
|
225
200
|
/>
|
|
226
201
|
{ anchorRef?.current && popupState.isOpen && (
|
|
227
202
|
<TextFieldPopover
|
|
@@ -279,7 +254,7 @@ function createStateFromSizeProp(
|
|
|
279
254
|
};
|
|
280
255
|
}
|
|
281
256
|
|
|
282
|
-
function extractValueFromState( state: State | null ): SizeValue | null {
|
|
257
|
+
function extractValueFromState( state: State | null, allowEmpty: boolean = false ): SizeValue | null {
|
|
283
258
|
if ( ! state ) {
|
|
284
259
|
return null;
|
|
285
260
|
}
|
|
@@ -294,20 +269,18 @@ function extractValueFromState( state: State | null ): SizeValue | null {
|
|
|
294
269
|
return { size: '', unit };
|
|
295
270
|
}
|
|
296
271
|
|
|
297
|
-
|
|
298
|
-
size: state
|
|
299
|
-
unit,
|
|
300
|
-
} as SizeValue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function areStatesEqual( state1: State, state2: State ): boolean {
|
|
304
|
-
if ( state1.unit !== state2.unit || state1.custom !== state2.custom ) {
|
|
305
|
-
return false;
|
|
272
|
+
if ( unit === 'custom' ) {
|
|
273
|
+
return { size: state.custom ?? '', unit: 'custom' };
|
|
306
274
|
}
|
|
307
275
|
|
|
308
|
-
|
|
309
|
-
|
|
276
|
+
const numeric = state.numeric;
|
|
277
|
+
|
|
278
|
+
if ( ! allowEmpty && ( numeric === undefined || numeric === null || Number.isNaN( numeric ) ) ) {
|
|
279
|
+
return null;
|
|
310
280
|
}
|
|
311
281
|
|
|
312
|
-
return
|
|
282
|
+
return {
|
|
283
|
+
size: numeric,
|
|
284
|
+
unit,
|
|
285
|
+
};
|
|
313
286
|
}
|