@elementor/editor-controls 3.33.0-99 → 3.35.0-324
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 +276 -85
- package/dist/index.d.ts +276 -85
- package/dist/index.js +2491 -1783
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2304 -1592
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -17
- package/src/bound-prop-context/prop-context.tsx +7 -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 +12 -47
- 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/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 +43 -25
- 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/create-control.tsx +3 -2
- 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
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": "3.
|
|
4
|
+
"version": "3.35.0-324",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,23 +40,37 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-current-user": "3.
|
|
44
|
-
"@elementor/editor-elements": "3.
|
|
45
|
-
"@elementor/editor-props": "3.
|
|
46
|
-
"@elementor/editor-responsive": "3.
|
|
47
|
-
"@elementor/editor-ui": "3.
|
|
48
|
-
"@elementor/editor-v1-adapters": "3.
|
|
49
|
-
"@elementor/env": "3.
|
|
50
|
-
"@elementor/http-client": "3.
|
|
51
|
-
"@elementor/icons": "^1.
|
|
52
|
-
"@elementor/locations": "3.
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@elementor/
|
|
55
|
-
"@elementor/
|
|
56
|
-
"@elementor/
|
|
57
|
-
"@elementor/
|
|
43
|
+
"@elementor/editor-current-user": "3.35.0-324",
|
|
44
|
+
"@elementor/editor-elements": "3.35.0-324",
|
|
45
|
+
"@elementor/editor-props": "3.35.0-324",
|
|
46
|
+
"@elementor/editor-responsive": "3.35.0-324",
|
|
47
|
+
"@elementor/editor-ui": "3.35.0-324",
|
|
48
|
+
"@elementor/editor-v1-adapters": "3.35.0-324",
|
|
49
|
+
"@elementor/env": "3.35.0-324",
|
|
50
|
+
"@elementor/http-client": "3.35.0-324",
|
|
51
|
+
"@elementor/icons": "^1.61.1",
|
|
52
|
+
"@elementor/locations": "3.35.0-324",
|
|
53
|
+
"@elementor/mixpanel": "3.35.0-324",
|
|
54
|
+
"@elementor/query": "3.35.0-324",
|
|
55
|
+
"@elementor/session": "3.35.0-324",
|
|
56
|
+
"@elementor/ui": "1.36.17",
|
|
57
|
+
"@elementor/utils": "3.35.0-324",
|
|
58
|
+
"@elementor/wp-media": "3.35.0-324",
|
|
58
59
|
"@wordpress/i18n": "^5.13.0",
|
|
59
|
-
"@monaco-editor/react": "^4.7.0"
|
|
60
|
+
"@monaco-editor/react": "^4.7.0",
|
|
61
|
+
"dayjs": "^1.11.18",
|
|
62
|
+
"@tiptap/extension-bold": "^3.11.1",
|
|
63
|
+
"@tiptap/extension-document": "^3.11.1",
|
|
64
|
+
"@tiptap/extension-hard-break": "^3.11.1",
|
|
65
|
+
"@tiptap/extension-italic": "^3.11.1",
|
|
66
|
+
"@tiptap/extension-strike": "^3.11.1",
|
|
67
|
+
"@tiptap/extension-subscript": "^3.11.1",
|
|
68
|
+
"@tiptap/extension-superscript": "^3.11.1",
|
|
69
|
+
"@tiptap/extension-text": "^3.11.1",
|
|
70
|
+
"@tiptap/extension-underline": "^3.11.1",
|
|
71
|
+
"@tiptap/pm": "^3.11.1",
|
|
72
|
+
"@tiptap/react": "^3.11.1",
|
|
73
|
+
"@tiptap/starter-kit": "^3.11.1"
|
|
60
74
|
},
|
|
61
75
|
"devDependencies": {
|
|
62
76
|
"monaco-types": "^0.1.0",
|
|
@@ -4,9 +4,15 @@ import { type CreateOptions, type PropKey, type PropType, type PropValue } from
|
|
|
4
4
|
|
|
5
5
|
import { HookOutsideProviderError } from './errors';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type Action = {
|
|
8
|
+
type: string;
|
|
9
|
+
payload?: object;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SetValueMeta< TAction = Action > = {
|
|
8
13
|
bind?: PropKey;
|
|
9
14
|
validation?: ( value: PropValue ) => boolean;
|
|
15
|
+
action?: TAction;
|
|
10
16
|
};
|
|
11
17
|
|
|
12
18
|
export type SetValue< T > = ( value: T, options?: CreateOptions, meta?: SetValueMeta ) => void;
|
|
@@ -19,14 +19,19 @@ type UseBoundProp< TValue extends PropValue > = {
|
|
|
19
19
|
placeholder?: TValue;
|
|
20
20
|
path: PropKey[];
|
|
21
21
|
restoreValue: () => void;
|
|
22
|
+
resetValue: () => void;
|
|
22
23
|
isDisabled?: ( propType: PropType ) => boolean | undefined;
|
|
23
24
|
disabled?: boolean;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
type EnhancedPropKeyContextValue< T, P > = PropKeyContextValue< T, P > & {
|
|
28
|
+
resetValue: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function useBoundProp<
|
|
32
|
+
T extends PropValue = PropValue,
|
|
33
|
+
P extends PropType = PropType,
|
|
34
|
+
>(): EnhancedPropKeyContextValue< T, P >;
|
|
30
35
|
|
|
31
36
|
export function useBoundProp< TKey extends string, TValue extends PropValue >(
|
|
32
37
|
propTypeUtil: PropTypeUtil< TKey, TValue >
|
|
@@ -41,9 +46,17 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
|
|
|
41
46
|
|
|
42
47
|
const disabled = propKeyContext.isDisabled?.( propKeyContext.propType );
|
|
43
48
|
|
|
49
|
+
const resetValue = () => {
|
|
50
|
+
propKeyContext.setValue( propKeyContext.propType.initial_value ?? null );
|
|
51
|
+
};
|
|
52
|
+
|
|
44
53
|
// allow using the hook without a propTypeUtil, with no modifications or validations.
|
|
45
54
|
if ( ! propTypeUtil ) {
|
|
46
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
...propKeyContext,
|
|
57
|
+
disabled,
|
|
58
|
+
resetValue,
|
|
59
|
+
} as EnhancedPropKeyContextValue< PropValue, PropType >;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
function setValue( value: TValue | null, options: CreateOptions, meta?: SetValueMeta ) {
|
|
@@ -71,6 +84,7 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
|
|
|
71
84
|
restoreValue,
|
|
72
85
|
placeholder,
|
|
73
86
|
disabled,
|
|
87
|
+
resetValue,
|
|
74
88
|
};
|
|
75
89
|
}
|
|
76
90
|
|
|
@@ -30,6 +30,9 @@ export type Props = {
|
|
|
30
30
|
allowCustomValues?: boolean;
|
|
31
31
|
placeholder?: string;
|
|
32
32
|
minInputLength?: number;
|
|
33
|
+
startAdornment?: React.ReactNode;
|
|
34
|
+
inputProps?: Record< string, unknown >;
|
|
35
|
+
disablePortal?: boolean;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
export const Autocomplete = forwardRef( ( props: Props, ref ) => {
|
|
@@ -41,10 +44,12 @@ export const Autocomplete = forwardRef( ( props: Props, ref ) => {
|
|
|
41
44
|
placeholder = '',
|
|
42
45
|
minInputLength = 2,
|
|
43
46
|
value = '',
|
|
47
|
+
startAdornment,
|
|
48
|
+
disablePortal = true,
|
|
44
49
|
...restProps
|
|
45
50
|
} = props;
|
|
46
51
|
|
|
47
|
-
const optionKeys =
|
|
52
|
+
const optionKeys = factoryFilter( value, options, minInputLength ).map( ( { id } ) => id );
|
|
48
53
|
const allowClear = !! value;
|
|
49
54
|
|
|
50
55
|
// Prevents MUI warning when freeSolo/allowCustomValues is false
|
|
@@ -54,13 +59,20 @@ export const Autocomplete = forwardRef( ( props: Props, ref ) => {
|
|
|
54
59
|
|
|
55
60
|
const isValueFromOptions = typeof value === 'number' && !! findMatchingOption( options, value );
|
|
56
61
|
|
|
62
|
+
const valueLength = value?.toString()?.length ?? 0;
|
|
63
|
+
const meetsMinLength = valueLength >= minInputLength;
|
|
64
|
+
const shouldOpen = meetsMinLength && ( allowCustomValues ? optionKeys.length > 0 : true );
|
|
65
|
+
|
|
57
66
|
return (
|
|
58
67
|
<AutocompleteBase
|
|
59
68
|
{ ...restProps }
|
|
60
69
|
ref={ ref }
|
|
61
70
|
forcePopupIcon={ false }
|
|
71
|
+
disablePortal={ disablePortal }
|
|
62
72
|
disableClearable={ true } // Disabled component's auto clear icon to use our custom one instead
|
|
63
73
|
freeSolo={ allowCustomValues }
|
|
74
|
+
openOnFocus={ false }
|
|
75
|
+
open={ shouldOpen }
|
|
64
76
|
value={ value?.toString() || '' }
|
|
65
77
|
size={ 'tiny' }
|
|
66
78
|
onChange={ ( _, newValue ) => onOptionChange( Number( newValue ) ) }
|
|
@@ -87,6 +99,8 @@ export const Autocomplete = forwardRef( ( props: Props, ref ) => {
|
|
|
87
99
|
allowClear={ allowClear }
|
|
88
100
|
placeholder={ placeholder }
|
|
89
101
|
hasSelectedValue={ isValueFromOptions }
|
|
102
|
+
startAdornment={ startAdornment }
|
|
103
|
+
extraInputProps={ restProps.inputProps }
|
|
90
104
|
/>
|
|
91
105
|
) }
|
|
92
106
|
/>
|
|
@@ -99,12 +113,16 @@ const TextInput = ( {
|
|
|
99
113
|
placeholder,
|
|
100
114
|
handleChange,
|
|
101
115
|
hasSelectedValue,
|
|
116
|
+
startAdornment,
|
|
117
|
+
extraInputProps,
|
|
102
118
|
}: {
|
|
103
119
|
params: AutocompleteRenderInputParams;
|
|
104
120
|
allowClear: boolean;
|
|
105
121
|
handleChange: ( newValue: string | null ) => void;
|
|
106
122
|
placeholder: string;
|
|
107
123
|
hasSelectedValue: boolean;
|
|
124
|
+
startAdornment?: React.ReactNode;
|
|
125
|
+
extraInputProps?: Record< string, unknown >;
|
|
108
126
|
} ) => {
|
|
109
127
|
const onChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
110
128
|
handleChange( event.target.value );
|
|
@@ -115,6 +133,7 @@ const TextInput = ( {
|
|
|
115
133
|
{ ...params }
|
|
116
134
|
placeholder={ placeholder }
|
|
117
135
|
onChange={ onChange }
|
|
136
|
+
inputProps={ { ...( params.inputProps ?? {} ), ...( extraInputProps ?? {} ) } }
|
|
118
137
|
sx={ {
|
|
119
138
|
'& .MuiInputBase-input': {
|
|
120
139
|
cursor: hasSelectedValue ? 'default' : undefined,
|
|
@@ -122,6 +141,11 @@ const TextInput = ( {
|
|
|
122
141
|
} }
|
|
123
142
|
InputProps={ {
|
|
124
143
|
...params.InputProps,
|
|
144
|
+
startAdornment: startAdornment ? (
|
|
145
|
+
<InputAdornment position="start">{ startAdornment }</InputAdornment>
|
|
146
|
+
) : (
|
|
147
|
+
params.InputProps.startAdornment
|
|
148
|
+
),
|
|
125
149
|
endAdornment: <ClearButton params={ params } allowClear={ allowClear } handleChange={ handleChange } />,
|
|
126
150
|
} }
|
|
127
151
|
/>
|
|
@@ -156,9 +180,16 @@ export function findMatchingOption(
|
|
|
156
180
|
}
|
|
157
181
|
|
|
158
182
|
export function isCategorizedOptionPool( options: FlatOption[] | CategorizedOption[] ): options is CategorizedOption[] {
|
|
159
|
-
|
|
183
|
+
if ( options.length <= 1 ) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const uniqueGroupLabels = new Set( options.map( ( option ) => option.groupLabel ) );
|
|
188
|
+
|
|
189
|
+
return uniqueGroupLabels.size > 1; // should not categorize options if there is only one group
|
|
160
190
|
}
|
|
161
|
-
|
|
191
|
+
|
|
192
|
+
function factoryFilter< T extends FlatOption[] | CategorizedOption[] >(
|
|
162
193
|
newValue: string | number | null,
|
|
163
194
|
options: T,
|
|
164
195
|
minInputLength: number
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { InfoAlert } from '@elementor/editor-ui';
|
|
3
|
+
import { type AlertProps, AlertTitle, Box, Infotip, type InfotipProps, useTheme } from '@elementor/ui';
|
|
4
|
+
import { DirectionProvider } from '@elementor/ui';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
infotipProps?: Partial< InfotipProps >;
|
|
8
|
+
alertProps?: Partial< AlertProps >;
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: React.ReactNode | string;
|
|
11
|
+
isEnabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const DEFAULT_COLOR = 'secondary';
|
|
15
|
+
|
|
16
|
+
export const ConditionalControlInfotip = React.forwardRef(
|
|
17
|
+
( { children, title, description, alertProps, infotipProps, ...props }: React.PropsWithChildren< Props >, ref ) => {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const isUiRtl = 'rtl' === theme.direction;
|
|
20
|
+
const isEnabled = props.isEnabled && ( title || description );
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Box ref={ ref }>
|
|
24
|
+
{ isEnabled ? (
|
|
25
|
+
<DirectionProvider rtl={ isUiRtl }>
|
|
26
|
+
<Infotip
|
|
27
|
+
placement={ 'right' }
|
|
28
|
+
color={ DEFAULT_COLOR }
|
|
29
|
+
slotProps={ {
|
|
30
|
+
popper: {
|
|
31
|
+
modifiers: [
|
|
32
|
+
{
|
|
33
|
+
name: 'offset',
|
|
34
|
+
options: {
|
|
35
|
+
offset: [ 0, 10 ],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
} }
|
|
41
|
+
{ ...infotipProps }
|
|
42
|
+
content={
|
|
43
|
+
<InfoAlert
|
|
44
|
+
color={ DEFAULT_COLOR }
|
|
45
|
+
sx={ { width: 300, px: 1.5, py: 2 } }
|
|
46
|
+
{ ...alertProps }
|
|
47
|
+
>
|
|
48
|
+
<Box sx={ { flexDirection: 'column', display: 'flex', gap: 0.5 } }>
|
|
49
|
+
<AlertTitle>{ title }</AlertTitle>
|
|
50
|
+
<Box>{ description }</Box>
|
|
51
|
+
</Box>
|
|
52
|
+
</InfoAlert>
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
{ children }
|
|
56
|
+
</Infotip>
|
|
57
|
+
</DirectionProvider>
|
|
58
|
+
) : (
|
|
59
|
+
children
|
|
60
|
+
) }
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
);
|
package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx
RENAMED
|
@@ -6,8 +6,8 @@ import { __ } from '@wordpress/i18n';
|
|
|
6
6
|
import { useRepeaterContext } from '../context/repeater-context';
|
|
7
7
|
const SIZE = 'tiny';
|
|
8
8
|
|
|
9
|
-
export const DisableItemAction = (
|
|
10
|
-
const { items, updateItem } = useRepeaterContext();
|
|
9
|
+
export const DisableItemAction = () => {
|
|
10
|
+
const { items, updateItem, index = -1 } = useRepeaterContext();
|
|
11
11
|
|
|
12
12
|
if ( index === -1 ) {
|
|
13
13
|
return null;
|
package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx
RENAMED
|
@@ -7,24 +7,30 @@ import { useRepeaterContext } from '../context/repeater-context';
|
|
|
7
7
|
|
|
8
8
|
const SIZE = 'tiny';
|
|
9
9
|
|
|
10
|
-
export const DuplicateItemAction = (
|
|
11
|
-
const { items, addItem } = useRepeaterContext();
|
|
10
|
+
export const DuplicateItemAction = () => {
|
|
11
|
+
const { items, addItem, index = -1, isItemDisabled } = useRepeaterContext();
|
|
12
12
|
|
|
13
13
|
if ( index === -1 ) {
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const duplicateLabel = __( 'Duplicate', 'elementor' );
|
|
18
|
+
const item = items[ index ]?.item;
|
|
18
19
|
|
|
19
20
|
const onClick = ( ev: React.MouseEvent ) => {
|
|
20
|
-
const newItem = structuredClone(
|
|
21
|
+
const newItem = structuredClone( item );
|
|
21
22
|
|
|
22
23
|
addItem( ev, { item: newItem, index: index + 1 } );
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
return (
|
|
26
27
|
<Tooltip title={ duplicateLabel } placement="top">
|
|
27
|
-
<IconButton
|
|
28
|
+
<IconButton
|
|
29
|
+
size={ SIZE }
|
|
30
|
+
onClick={ onClick }
|
|
31
|
+
aria-label={ duplicateLabel }
|
|
32
|
+
disabled={ isItemDisabled( index ) }
|
|
33
|
+
>
|
|
28
34
|
<CopyIcon fontSize={ SIZE } />
|
|
29
35
|
</IconButton>
|
|
30
36
|
</Tooltip>
|
package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx
RENAMED
|
@@ -7,8 +7,8 @@ import { useRepeaterContext } from '../context/repeater-context';
|
|
|
7
7
|
|
|
8
8
|
const SIZE = 'tiny';
|
|
9
9
|
|
|
10
|
-
export const RemoveItemAction = (
|
|
11
|
-
const { removeItem } = useRepeaterContext();
|
|
10
|
+
export const RemoveItemAction = () => {
|
|
11
|
+
const { removeItem, index = -1 } = useRepeaterContext();
|
|
12
12
|
|
|
13
13
|
if ( index === -1 ) {
|
|
14
14
|
return null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { type Item, type RepeatablePropValue } from '../types';
|
|
4
|
+
|
|
5
|
+
export const ItemContext = createContext< { index: number; value: Item< RepeatablePropValue > } >( {
|
|
6
|
+
index: -1,
|
|
7
|
+
value: {} as Item< RepeatablePropValue >,
|
|
8
|
+
} );
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { createContext, useState } from 'react';
|
|
2
|
+
import { createContext, useMemo, useState } from 'react';
|
|
3
3
|
import { type PropTypeUtil } from '@elementor/editor-props';
|
|
4
4
|
import { type PopupState, usePopupState } from '@elementor/ui';
|
|
5
5
|
|
|
@@ -7,6 +7,7 @@ import { useBoundProp } from '../../../bound-prop-context/use-bound-prop';
|
|
|
7
7
|
import { useSyncExternalState } from '../../../hooks/use-sync-external-state';
|
|
8
8
|
import { eventBus } from '../../../services/event-bus';
|
|
9
9
|
import { type Item, type RepeatablePropValue } from '../types';
|
|
10
|
+
import { ItemContext } from './item-context';
|
|
10
11
|
|
|
11
12
|
type SetterFn< T > = ( prevItems: T ) => T;
|
|
12
13
|
|
|
@@ -27,6 +28,7 @@ type RepeaterContextType< T extends RepeatablePropValue > = {
|
|
|
27
28
|
removeItem: ( index: number ) => void;
|
|
28
29
|
rowRef: HTMLElement | null;
|
|
29
30
|
setRowRef: ( ref: HTMLElement | null | SetterFn< HTMLElement | null > ) => void;
|
|
31
|
+
isItemDisabled: ( index: number ) => boolean;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
const RepeaterContext = createContext< RepeaterContextType< RepeatablePropValue > | null >( null );
|
|
@@ -35,22 +37,25 @@ export const EMPTY_OPEN_ITEM = -1;
|
|
|
35
37
|
|
|
36
38
|
export const useRepeaterContext = () => {
|
|
37
39
|
const context = React.useContext( RepeaterContext );
|
|
40
|
+
const itemContext = React.useContext( ItemContext );
|
|
38
41
|
|
|
39
42
|
if ( ! context ) {
|
|
40
43
|
throw new Error( 'useRepeaterContext must be used within a RepeaterContextProvider' );
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
return context;
|
|
46
|
+
return { ...context, ...itemContext };
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
export const RepeaterContextProvider = < T extends RepeatablePropValue = RepeatablePropValue >( {
|
|
47
50
|
children,
|
|
48
51
|
initial,
|
|
49
52
|
propTypeUtil,
|
|
53
|
+
isItemDisabled = () => false,
|
|
50
54
|
}: React.PropsWithChildren< {
|
|
51
55
|
initial: T;
|
|
52
56
|
propTypeUtil: PropTypeUtil< string, T[] >;
|
|
53
57
|
isSortable?: boolean;
|
|
58
|
+
isItemDisabled?: ( item: Item< T > ) => boolean;
|
|
54
59
|
} > ) => {
|
|
55
60
|
const { value: repeaterValues, setValue: setRepeaterValues } = useBoundProp( propTypeUtil );
|
|
56
61
|
|
|
@@ -61,21 +66,20 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
|
|
|
61
66
|
persistWhen: () => true,
|
|
62
67
|
} );
|
|
63
68
|
|
|
64
|
-
const [
|
|
65
|
-
return items?.map( (
|
|
69
|
+
const [ uniqueKeys, setUniqueKeys ] = useState( () => {
|
|
70
|
+
return items?.map( ( _, index ) => index ) ?? [];
|
|
66
71
|
} );
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} )
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}, [ items ] );
|
|
73
|
+
const itemsWithKeys = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
uniqueKeys
|
|
76
|
+
.map( ( key, index ) => ( {
|
|
77
|
+
key,
|
|
78
|
+
item: items[ index ],
|
|
79
|
+
} ) )
|
|
80
|
+
.filter( ( { item } ) => item !== undefined ),
|
|
81
|
+
[ uniqueKeys, items ]
|
|
82
|
+
);
|
|
79
83
|
|
|
80
84
|
const handleSetItems = ( newItemsWithKeys: ItemWithKey< T >[] ) => {
|
|
81
85
|
setItems( newItemsWithKeys.map( ( { item } ) => item ) );
|
|
@@ -90,11 +94,14 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
|
|
|
90
94
|
const addItem = ( ev: React.MouseEvent, config?: AddItem< T > ) => {
|
|
91
95
|
const item = config?.item ?? { ...initial };
|
|
92
96
|
const newIndex = config?.index ?? items.length;
|
|
97
|
+
const newKey = generateUniqueKey();
|
|
93
98
|
const newItems = [ ...items ];
|
|
94
99
|
|
|
95
100
|
newItems.splice( newIndex, 0, item );
|
|
96
101
|
setItems( newItems );
|
|
97
102
|
|
|
103
|
+
setUniqueKeys( [ ...uniqueKeys.slice( 0, newIndex ), newKey, ...uniqueKeys.slice( newIndex ) ] );
|
|
104
|
+
|
|
98
105
|
setOpenItemIndex( newIndex );
|
|
99
106
|
popoverState.open( rowRef ?? ev );
|
|
100
107
|
|
|
@@ -107,6 +114,7 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
|
|
|
107
114
|
const itemToRemove = items[ index ];
|
|
108
115
|
|
|
109
116
|
setItems( items.filter( ( _, pos ) => pos !== index ) );
|
|
117
|
+
setUniqueKeys( uniqueKeys.filter( ( _, pos ) => pos !== index ) );
|
|
110
118
|
|
|
111
119
|
eventBus.emit( `${ propTypeUtil.key }-item-removed`, {
|
|
112
120
|
itemValue: itemToRemove?.value,
|
|
@@ -133,6 +141,7 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
|
|
|
133
141
|
removeItem,
|
|
134
142
|
rowRef,
|
|
135
143
|
setRowRef,
|
|
144
|
+
isItemDisabled: ( index: number ) => isItemDisabled( itemsWithKeys[ index ].item ),
|
|
136
145
|
} }
|
|
137
146
|
>
|
|
138
147
|
{ children }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type PropTypeUtil } from '@elementor/editor-props';
|
|
3
|
+
|
|
4
|
+
import { SectionContent } from '../section-content';
|
|
5
|
+
import { RepeaterContextProvider } from './context/repeater-context';
|
|
6
|
+
import { type Item, type RepeatablePropValue } from './types';
|
|
7
|
+
|
|
8
|
+
export const ControlRepeater = < T extends RepeatablePropValue >( {
|
|
9
|
+
children,
|
|
10
|
+
initial,
|
|
11
|
+
propTypeUtil,
|
|
12
|
+
isItemDisabled,
|
|
13
|
+
}: React.PropsWithChildren< {
|
|
14
|
+
initial: T;
|
|
15
|
+
propTypeUtil: PropTypeUtil< string, T[] >;
|
|
16
|
+
isItemDisabled?: ( item: Item< T > ) => boolean;
|
|
17
|
+
} > ) => {
|
|
18
|
+
return (
|
|
19
|
+
<SectionContent>
|
|
20
|
+
<RepeaterContextProvider
|
|
21
|
+
initial={ initial }
|
|
22
|
+
propTypeUtil={ propTypeUtil }
|
|
23
|
+
isItemDisabled={ isItemDisabled }
|
|
24
|
+
>
|
|
25
|
+
{ children }
|
|
26
|
+
</RepeaterContextProvider>
|
|
27
|
+
</SectionContent>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { TooltipAddItemAction } from './actions/tooltip-add-item-action';
|
|
2
|
-
export { Header } from './header/header';
|
|
3
2
|
export { ItemsContainer } from './items/items-container';
|
|
4
3
|
export { Item } from './items/item';
|
|
5
|
-
export {
|
|
4
|
+
export { ControlRepeater } from './control-repeater';
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { bindPopover, Box
|
|
2
|
+
import { bindPopover, Box } from '@elementor/ui';
|
|
3
3
|
|
|
4
4
|
import { PropKeyProvider } from '../../../bound-prop-context';
|
|
5
|
+
import { RepeaterPopover } from '../../repeater/repeater-popover';
|
|
5
6
|
import { EMPTY_OPEN_ITEM, useRepeaterContext } from '../context/repeater-context';
|
|
6
7
|
|
|
7
8
|
export const EditItemPopover = ( { children }: { children: React.ReactNode } ) => {
|
|
8
|
-
const { popoverState, openItemIndex, isOpen, rowRef, setOpenItemIndex, setRowRef
|
|
9
|
+
const { popoverState, openItemIndex, isOpen, rowRef, setOpenItemIndex, setRowRef } = useRepeaterContext();
|
|
9
10
|
|
|
10
11
|
if ( ! isOpen || ! rowRef ) {
|
|
11
12
|
return null;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const bind = items[ openItemIndex ].item.$$type;
|
|
15
|
-
|
|
16
15
|
const onClose = () => {
|
|
17
16
|
setRowRef( null );
|
|
18
17
|
popoverState.setAnchorEl( null );
|
|
@@ -20,23 +19,10 @@ export const EditItemPopover = ( { children }: { children: React.ReactNode } ) =
|
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
return (
|
|
23
|
-
<
|
|
24
|
-
disablePortal
|
|
25
|
-
slotProps={ {
|
|
26
|
-
paper: {
|
|
27
|
-
sx: { mt: 0.5, width: rowRef.offsetWidth },
|
|
28
|
-
},
|
|
29
|
-
} }
|
|
30
|
-
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
31
|
-
{ ...bindPopover( popoverState ) }
|
|
32
|
-
onClose={ onClose }
|
|
33
|
-
>
|
|
22
|
+
<RepeaterPopover width={ rowRef.offsetWidth } { ...bindPopover( popoverState ) } onClose={ onClose }>
|
|
34
23
|
<PropKeyProvider bind={ String( openItemIndex ) }>
|
|
35
|
-
<Box>
|
|
36
|
-
{ React.isValidElement< { bind: string; index: number } >( children ) &&
|
|
37
|
-
React.cloneElement( children, { bind, index: openItemIndex } ) }
|
|
38
|
-
</Box>
|
|
24
|
+
<Box>{ children }</Box>
|
|
39
25
|
</PropKeyProvider>
|
|
40
|
-
</
|
|
26
|
+
</RepeaterPopover>
|
|
41
27
|
);
|
|
42
28
|
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { bindTrigger } from '@elementor/ui';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { RepeatableControlContext } from '../../../hooks/use-repeatable-control-context';
|
|
6
|
+
import { RepeaterTag } from '../../repeater/repeater-tag';
|
|
7
|
+
import { useRepeaterContext } from '../context/repeater-context';
|
|
8
|
+
import { RepeaterItemActionsSlot, RepeaterItemIconSlot, RepeaterItemLabelSlot } from '../locations';
|
|
9
|
+
import { type ItemProps, type RepeatablePropValue } from '../types';
|
|
10
|
+
|
|
11
|
+
export const Item = < T extends RepeatablePropValue >( { Label, Icon, actions }: ItemProps< T > ) => {
|
|
12
|
+
const {
|
|
13
|
+
popoverState,
|
|
14
|
+
setRowRef,
|
|
15
|
+
openItemIndex,
|
|
16
|
+
setOpenItemIndex,
|
|
17
|
+
index = -1,
|
|
18
|
+
value,
|
|
19
|
+
isItemDisabled,
|
|
20
|
+
} = useRepeaterContext();
|
|
21
|
+
const repeatableContext = React.useContext( RepeatableControlContext );
|
|
22
|
+
const disableOpen = !! repeatableContext?.props?.readOnly;
|
|
23
|
+
const triggerProps = bindTrigger( popoverState );
|
|
24
|
+
|
|
25
|
+
const onClick = ( ev: React.MouseEvent ) => {
|
|
26
|
+
if ( disableOpen || isItemDisabled( index ) ) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
triggerProps.onClick( ev );
|
|
31
|
+
setOpenItemIndex( index );
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const setRef = ( ref: HTMLDivElement | null ) => {
|
|
35
|
+
if ( ! ref || openItemIndex !== index || ref === popoverState.anchorEl ) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setRowRef( ref );
|
|
40
|
+
popoverState.setAnchorEl( ref );
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<RepeaterTag
|
|
45
|
+
ref={ setRef }
|
|
46
|
+
label={
|
|
47
|
+
<RepeaterItemLabelSlot value={ value }>
|
|
48
|
+
<Label value={ value as T } />
|
|
49
|
+
</RepeaterItemLabelSlot>
|
|
50
|
+
}
|
|
51
|
+
aria-label={ __( 'Open item', 'elementor' ) }
|
|
52
|
+
{ ...triggerProps }
|
|
53
|
+
onClick={ onClick }
|
|
54
|
+
startIcon={
|
|
55
|
+
<RepeaterItemIconSlot value={ value }>
|
|
56
|
+
<Icon value={ value as T } />
|
|
57
|
+
</RepeaterItemIconSlot>
|
|
58
|
+
}
|
|
59
|
+
sx={ {
|
|
60
|
+
minHeight: ( theme ) => theme.spacing( 3.5 ),
|
|
61
|
+
...( isItemDisabled( index ) && {
|
|
62
|
+
'[role="button"]': {
|
|
63
|
+
cursor: 'not-allowed',
|
|
64
|
+
},
|
|
65
|
+
} ),
|
|
66
|
+
} }
|
|
67
|
+
actions={
|
|
68
|
+
<>
|
|
69
|
+
<RepeaterItemActionsSlot index={ index ?? -1 } />
|
|
70
|
+
{ actions }
|
|
71
|
+
</>
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
};
|