@elementor/editor-editing-panel 3.33.0-99 → 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 +419 -88
- package/dist/index.d.ts +419 -88
- package/dist/index.js +3361 -2421
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3299 -2347
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -21
- package/src/apply-unapply-actions.ts +30 -0
- package/src/components/collapsible-content.tsx +1 -0
- package/src/components/css-classes/css-class-item.tsx +14 -4
- package/src/components/css-classes/css-class-menu.tsx +83 -11
- package/src/components/css-classes/css-class-selector.tsx +22 -1
- package/src/components/css-classes/use-apply-and-unapply-class.ts +4 -13
- package/src/components/custom-css-indicator.tsx +68 -1
- package/src/components/editing-panel-tabs.tsx +13 -2
- package/src/components/interactions-tab.tsx +15 -0
- package/src/components/section-content.tsx +3 -2
- package/src/components/section.tsx +2 -1
- package/src/components/settings-control.tsx +79 -0
- package/src/components/settings-tab.tsx +16 -46
- package/src/components/style-sections/border-section/border-section.tsx +6 -4
- package/src/components/style-sections/effects-section/blend-mode-field.tsx +44 -0
- package/src/components/style-sections/effects-section/effects-section.tsx +4 -1
- package/src/components/style-sections/layout-section/flex-size-field.tsx +10 -8
- package/src/components/style-sections/typography-section/font-family-field.tsx +5 -1
- package/src/components/style-sections/typography-section/font-size-field.tsx +1 -1
- package/src/components/style-sections/typography-section/letter-spacing-field.tsx +1 -1
- package/src/components/style-sections/typography-section/text-color-field.tsx +1 -1
- package/src/components/style-sections/typography-section/word-spacing-field.tsx +1 -1
- package/src/components/style-tab-section.tsx +2 -2
- package/src/components/style-tab.tsx +12 -17
- package/src/components/styles-field-layout.tsx +8 -1
- package/src/contexts/interaction-context.tsx +0 -0
- package/src/controls-registry/conditional-field.tsx +1 -1
- package/src/controls-registry/control-type-container.tsx +17 -5
- package/src/controls-registry/controls-registry.tsx +18 -5
- package/src/controls-registry/element-controls/get-element-by-type.ts +21 -0
- package/src/controls-registry/element-controls/registry.ts +16 -0
- package/src/controls-registry/element-controls/tabs-control/tabs-control.tsx +229 -0
- package/src/controls-registry/element-controls/tabs-control/use-actions.ts +248 -0
- package/src/controls-registry/settings-field.tsx +54 -10
- package/src/controls-registry/styles-field.tsx +2 -9
- package/src/dynamics/components/dynamic-conditional-control.tsx +62 -0
- package/src/dynamics/components/dynamic-selection-control.tsx +81 -25
- package/src/dynamics/components/dynamic-selection.tsx +3 -3
- package/src/dynamics/dynamic-control.tsx +10 -1
- package/src/dynamics/hooks/use-prop-dynamic-tags.ts +24 -6
- package/src/field-indicators-registry.ts +37 -0
- package/src/hooks/use-computed-style.ts +1 -4
- package/src/index.ts +16 -3
- package/src/init.ts +7 -0
- package/src/reset-style-props.tsx +21 -4
- package/src/styles-inheritance/components/infotip/value-component.tsx +2 -0
- package/src/styles-inheritance/components/styles-inheritance-indicator.tsx +1 -13
- package/src/styles-inheritance/components/styles-inheritance-infotip.tsx +5 -1
- package/src/styles-inheritance/hooks/use-normalized-inheritance-chain-items.tsx +18 -2
- package/src/styles-inheritance/init-styles-inheritance-transformers.ts +25 -4
- package/src/styles-inheritance/init.ts +9 -0
- package/src/styles-inheritance/transformers/{background-overlay-transformer.tsx → array-transformer.tsx} +2 -2
- package/src/styles-inheritance/transformers/background-color-overlay-transformer.tsx +0 -6
- package/src/styles-inheritance/transformers/box-shadow-transformer.tsx +32 -0
- package/src/styles-inheritance/transformers/color-transformer.tsx +32 -0
- package/src/styles-inheritance/transformers/repeater-to-items-transformer.tsx +27 -0
- package/src/utils/is-equal.ts +53 -0
- package/src/utils/prop-dependency-utils.ts +107 -19
- package/src/utils/tracking/subscribe.ts +7 -0
- package/src/components/custom-css-field.tsx +0 -20
- package/src/components/custom-css.tsx +0 -36
- package/src/components/style-sections/border-section/border-field.tsx +0 -54
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ControlFormLabel,
|
|
4
|
+
Repeater,
|
|
5
|
+
type RepeaterItem,
|
|
6
|
+
type SetRepeaterValuesMeta,
|
|
7
|
+
useBoundProp,
|
|
8
|
+
} from '@elementor/editor-controls';
|
|
9
|
+
import {
|
|
10
|
+
getElementEditorSettings,
|
|
11
|
+
updateElementEditorSettings,
|
|
12
|
+
useElementChildren,
|
|
13
|
+
useElementEditorSettings,
|
|
14
|
+
} from '@elementor/editor-elements';
|
|
15
|
+
import { type CreateOptions, numberPropTypeUtil } from '@elementor/editor-props';
|
|
16
|
+
import { InfoCircleFilledIcon } from '@elementor/icons';
|
|
17
|
+
import { Alert, Chip, Infotip, type InfotipProps, Stack, Switch, TextField, Typography } from '@elementor/ui';
|
|
18
|
+
import { __ } from '@wordpress/i18n';
|
|
19
|
+
|
|
20
|
+
import { useElement } from '../../../contexts/element-context';
|
|
21
|
+
import { SettingsField } from '../../settings-field';
|
|
22
|
+
import { getElementByType } from '../get-element-by-type';
|
|
23
|
+
import { TAB_ELEMENT_TYPE, type TabItem, useActions } from './use-actions';
|
|
24
|
+
|
|
25
|
+
const TAB_MENU_ELEMENT_TYPE = 'e-tabs-menu';
|
|
26
|
+
const TAB_CONTENT_AREA_ELEMENT_TYPE = 'e-tabs-content-area';
|
|
27
|
+
export const TabsControl = ( { label }: { label: string } ) => {
|
|
28
|
+
return (
|
|
29
|
+
<SettingsField bind="default-active-tab" propDisplayName={ __( 'Tabs', 'elementor' ) }>
|
|
30
|
+
<TabsControlContent label={ label } />
|
|
31
|
+
</SettingsField>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const TabsControlContent = ( { label }: { label: string } ) => {
|
|
36
|
+
const { element } = useElement();
|
|
37
|
+
const { addItem, duplicateItem, moveItem, removeItem } = useActions();
|
|
38
|
+
|
|
39
|
+
const { [ TAB_ELEMENT_TYPE ]: tabLinks } = useElementChildren( element.id, {
|
|
40
|
+
[ TAB_MENU_ELEMENT_TYPE ]: TAB_ELEMENT_TYPE,
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
const tabList = getElementByType( element.id, TAB_MENU_ELEMENT_TYPE );
|
|
44
|
+
const tabContentArea = getElementByType( element.id, TAB_CONTENT_AREA_ELEMENT_TYPE );
|
|
45
|
+
|
|
46
|
+
const repeaterValues: RepeaterItem< TabItem >[] = tabLinks.map( ( tabLink, index ) => {
|
|
47
|
+
const { title: titleSetting } = getElementEditorSettings( tabLink.id ) ?? {};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
id: tabLink.id,
|
|
51
|
+
title: titleSetting,
|
|
52
|
+
index,
|
|
53
|
+
};
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
const setValue = (
|
|
57
|
+
_newValues: RepeaterItem< TabItem >[],
|
|
58
|
+
_options: CreateOptions,
|
|
59
|
+
meta?: SetRepeaterValuesMeta< RepeaterItem< TabItem > >
|
|
60
|
+
) => {
|
|
61
|
+
if ( meta?.action?.type === 'add' ) {
|
|
62
|
+
const items = meta.action.payload;
|
|
63
|
+
|
|
64
|
+
return addItem( { tabContentAreaId: tabContentArea.id, items, tabsMenuId: tabList.id } );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ( meta?.action?.type === 'remove' ) {
|
|
68
|
+
const items = meta.action.payload;
|
|
69
|
+
|
|
70
|
+
return removeItem( {
|
|
71
|
+
items,
|
|
72
|
+
tabContentAreaId: tabContentArea.id,
|
|
73
|
+
} );
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ( meta?.action?.type === 'duplicate' ) {
|
|
77
|
+
const items = meta.action.payload;
|
|
78
|
+
|
|
79
|
+
return duplicateItem( { items, tabContentAreaId: tabContentArea.id } );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ( meta?.action?.type === 'reorder' ) {
|
|
83
|
+
const { from, to } = meta.action.payload;
|
|
84
|
+
|
|
85
|
+
return moveItem( {
|
|
86
|
+
toIndex: to,
|
|
87
|
+
tabsMenuId: tabList.id,
|
|
88
|
+
tabContentAreaId: tabContentArea.id,
|
|
89
|
+
movedElementId: tabLinks[ from ].id,
|
|
90
|
+
movedElementIndex: from,
|
|
91
|
+
} );
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Repeater
|
|
97
|
+
showToggle={ false }
|
|
98
|
+
values={ repeaterValues }
|
|
99
|
+
setValues={ setValue }
|
|
100
|
+
showRemove={ repeaterValues.length > 1 }
|
|
101
|
+
label={ label }
|
|
102
|
+
itemSettings={ {
|
|
103
|
+
getId: ( { item } ) => item.id,
|
|
104
|
+
initialValues: { id: '', title: 'Tab' },
|
|
105
|
+
Label: ItemLabel,
|
|
106
|
+
Content: ItemContent,
|
|
107
|
+
Icon: () => null,
|
|
108
|
+
} }
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const ItemLabel = ( { value, index }: { value: TabItem; index: number } ) => {
|
|
114
|
+
const id = value.id ?? '';
|
|
115
|
+
|
|
116
|
+
const editorSettings = useElementEditorSettings( id );
|
|
117
|
+
|
|
118
|
+
const elementTitle = editorSettings?.title;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Stack sx={ { minHeight: 20 } } direction="row" alignItems="center" gap={ 1.5 }>
|
|
122
|
+
<span>{ elementTitle }</span>
|
|
123
|
+
<ItemDefaultTab index={ index } />
|
|
124
|
+
</Stack>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const ItemDefaultTab = ( { index }: { index: number } ) => {
|
|
129
|
+
const { value: defaultItem } = useBoundProp( numberPropTypeUtil );
|
|
130
|
+
|
|
131
|
+
const isDefault = defaultItem === index;
|
|
132
|
+
|
|
133
|
+
if ( ! isDefault ) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return <Chip size="tiny" shape="rounded" label={ __( 'Default', 'elementor' ) } />;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const ItemContent = ( { value, index }: { value: TabItem; index: number } ) => {
|
|
141
|
+
if ( ! value.id ) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Stack p={ 2 } gap={ 1.5 }>
|
|
147
|
+
<TabLabelControl elementId={ value.id } />
|
|
148
|
+
<SettingsField bind="default-active-tab" propDisplayName={ __( 'Tabs', 'elementor' ) }>
|
|
149
|
+
<DefaultTabControl tabIndex={ index } />
|
|
150
|
+
</SettingsField>
|
|
151
|
+
</Stack>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const DefaultTabControl = ( { tabIndex }: { tabIndex: number } ) => {
|
|
156
|
+
const { value, setValue } = useBoundProp( numberPropTypeUtil );
|
|
157
|
+
|
|
158
|
+
const isDefault = value === tabIndex;
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={ 2 }>
|
|
162
|
+
<ControlFormLabel>{ __( 'Set as default tab', 'elementor' ) }</ControlFormLabel>
|
|
163
|
+
<ConditionalTooltip showTooltip={ isDefault } placement="right">
|
|
164
|
+
<Switch
|
|
165
|
+
size="small"
|
|
166
|
+
checked={ isDefault }
|
|
167
|
+
disabled={ isDefault }
|
|
168
|
+
onChange={ ( { target }: React.ChangeEvent< HTMLInputElement > ) => {
|
|
169
|
+
setValue( target.checked ? tabIndex : null );
|
|
170
|
+
} }
|
|
171
|
+
inputProps={ {
|
|
172
|
+
...( isDefault ? { style: { opacity: 0, cursor: 'not-allowed' } } : {} ),
|
|
173
|
+
} }
|
|
174
|
+
/>
|
|
175
|
+
</ConditionalTooltip>
|
|
176
|
+
</Stack>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const TabLabelControl = ( { elementId }: { elementId: string } ) => {
|
|
181
|
+
const editorSettings = useElementEditorSettings( elementId );
|
|
182
|
+
|
|
183
|
+
const label = editorSettings?.title ?? '';
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Stack gap={ 1 }>
|
|
187
|
+
<ControlFormLabel>{ __( 'Tab name', 'elementor' ) }</ControlFormLabel>
|
|
188
|
+
<TextField
|
|
189
|
+
size="tiny"
|
|
190
|
+
value={ label }
|
|
191
|
+
onChange={ ( { target }: React.ChangeEvent< HTMLInputElement > ) => {
|
|
192
|
+
updateElementEditorSettings( {
|
|
193
|
+
elementId,
|
|
194
|
+
settings: { title: target.value },
|
|
195
|
+
} );
|
|
196
|
+
} }
|
|
197
|
+
/>
|
|
198
|
+
</Stack>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const ConditionalTooltip = ( {
|
|
203
|
+
showTooltip,
|
|
204
|
+
children,
|
|
205
|
+
}: Omit< InfotipProps, 'content' > & { showTooltip: boolean } ) => {
|
|
206
|
+
if ( ! showTooltip ) {
|
|
207
|
+
return children;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Infotip
|
|
212
|
+
arrow={ false }
|
|
213
|
+
content={
|
|
214
|
+
<Alert
|
|
215
|
+
color="secondary"
|
|
216
|
+
icon={ <InfoCircleFilledIcon fontSize="tiny" /> }
|
|
217
|
+
size="small"
|
|
218
|
+
sx={ { width: 288 } }
|
|
219
|
+
>
|
|
220
|
+
<Typography variant="body2">
|
|
221
|
+
{ __( 'To change the default tab, simply set another tab as default.', 'elementor' ) }
|
|
222
|
+
</Typography>
|
|
223
|
+
</Alert>
|
|
224
|
+
}
|
|
225
|
+
>
|
|
226
|
+
<span>{ children }</span>
|
|
227
|
+
</Infotip>
|
|
228
|
+
);
|
|
229
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { type ItemsActionPayload, useBoundProp } from '@elementor/editor-controls';
|
|
2
|
+
import {
|
|
3
|
+
createElements,
|
|
4
|
+
duplicateElements,
|
|
5
|
+
getContainer,
|
|
6
|
+
moveElements,
|
|
7
|
+
removeElements,
|
|
8
|
+
} from '@elementor/editor-elements';
|
|
9
|
+
import { numberPropTypeUtil } from '@elementor/editor-props';
|
|
10
|
+
import { __ } from '@wordpress/i18n';
|
|
11
|
+
|
|
12
|
+
export type TabItem = {
|
|
13
|
+
id: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const TAB_ELEMENT_TYPE = 'e-tab';
|
|
18
|
+
export const TAB_CONTENT_ELEMENT_TYPE = 'e-tab-content';
|
|
19
|
+
|
|
20
|
+
export const useActions = () => {
|
|
21
|
+
const { value, setValue: setDefaultActiveTab } = useBoundProp( numberPropTypeUtil );
|
|
22
|
+
const defaultActiveTab = value ?? 0;
|
|
23
|
+
|
|
24
|
+
const duplicateItem = ( {
|
|
25
|
+
items,
|
|
26
|
+
tabContentAreaId,
|
|
27
|
+
}: {
|
|
28
|
+
items: ItemsActionPayload< TabItem >;
|
|
29
|
+
tabContentAreaId: string;
|
|
30
|
+
} ) => {
|
|
31
|
+
const newDefault = calculateDefaultOnDuplicate( {
|
|
32
|
+
items,
|
|
33
|
+
defaultActiveTab,
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
items.forEach( ( { item, index } ) => {
|
|
37
|
+
const tabId = item.id as string;
|
|
38
|
+
const tabContentAreaContainer = getContainer( tabContentAreaId );
|
|
39
|
+
const tabContentId = tabContentAreaContainer?.children?.[ index ]?.id;
|
|
40
|
+
|
|
41
|
+
if ( ! tabContentId ) {
|
|
42
|
+
throw new Error( 'Original content ID is required for duplication' );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
duplicateElements( {
|
|
46
|
+
elementIds: [ tabId, tabContentId ],
|
|
47
|
+
title: __( 'Duplicate Tab', 'elementor' ),
|
|
48
|
+
onDuplicateElements: () => {
|
|
49
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
50
|
+
setDefaultActiveTab( newDefault, {}, { withHistory: false } );
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
onRestoreElements: () => {
|
|
54
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
55
|
+
setDefaultActiveTab( defaultActiveTab, {}, { withHistory: false } );
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
} );
|
|
59
|
+
} );
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const moveItem = ( {
|
|
63
|
+
toIndex,
|
|
64
|
+
tabsMenuId,
|
|
65
|
+
tabContentAreaId,
|
|
66
|
+
movedElementId,
|
|
67
|
+
movedElementIndex,
|
|
68
|
+
}: {
|
|
69
|
+
toIndex: number;
|
|
70
|
+
tabsMenuId: string;
|
|
71
|
+
tabContentAreaId: string;
|
|
72
|
+
movedElementId: string;
|
|
73
|
+
movedElementIndex: number;
|
|
74
|
+
} ) => {
|
|
75
|
+
const tabContentContainer = getContainer( tabContentAreaId );
|
|
76
|
+
const tabContentId = tabContentContainer?.children?.[ movedElementIndex ]?.id;
|
|
77
|
+
|
|
78
|
+
if ( ! tabContentId ) {
|
|
79
|
+
throw new Error( 'Content ID is required' );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const newDefault = calculateDefaultOnMove( {
|
|
83
|
+
from: movedElementIndex,
|
|
84
|
+
to: toIndex,
|
|
85
|
+
defaultActiveTab,
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
moveElements( {
|
|
89
|
+
title: __( 'Reorder Tabs', 'elementor' ),
|
|
90
|
+
moves: [
|
|
91
|
+
{
|
|
92
|
+
elementId: movedElementId,
|
|
93
|
+
targetContainerId: tabsMenuId,
|
|
94
|
+
options: { at: toIndex },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
elementId: tabContentId,
|
|
98
|
+
targetContainerId: tabContentAreaId,
|
|
99
|
+
options: { at: toIndex },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
onMoveElements: () => {
|
|
103
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
104
|
+
setDefaultActiveTab( newDefault, {}, { withHistory: false } );
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
onRestoreElements: () => {
|
|
108
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
109
|
+
setDefaultActiveTab( defaultActiveTab, {}, { withHistory: false } );
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
} );
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const removeItem = ( {
|
|
116
|
+
items,
|
|
117
|
+
tabContentAreaId,
|
|
118
|
+
}: {
|
|
119
|
+
items: ItemsActionPayload< TabItem >;
|
|
120
|
+
tabContentAreaId: string;
|
|
121
|
+
} ) => {
|
|
122
|
+
const newDefault = calculateDefaultOnRemove( {
|
|
123
|
+
items,
|
|
124
|
+
defaultActiveTab,
|
|
125
|
+
} );
|
|
126
|
+
|
|
127
|
+
removeElements( {
|
|
128
|
+
title: __( 'Tabs', 'elementor' ),
|
|
129
|
+
elementIds: items.flatMap( ( { item, index } ) => {
|
|
130
|
+
const tabId = item.id as string;
|
|
131
|
+
const tabContentContainer = getContainer( tabContentAreaId );
|
|
132
|
+
const tabContentId = tabContentContainer?.children?.[ index ]?.id;
|
|
133
|
+
|
|
134
|
+
if ( ! tabContentId ) {
|
|
135
|
+
throw new Error( 'Content ID is required' );
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return [ tabId, tabContentId ];
|
|
139
|
+
} ),
|
|
140
|
+
onRemoveElements: () => {
|
|
141
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
142
|
+
setDefaultActiveTab( newDefault, {}, { withHistory: false } );
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
onRestoreElements: () => {
|
|
146
|
+
if ( newDefault !== defaultActiveTab ) {
|
|
147
|
+
setDefaultActiveTab( defaultActiveTab, {}, { withHistory: false } );
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
} );
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const addItem = ( {
|
|
154
|
+
tabContentAreaId,
|
|
155
|
+
tabsMenuId,
|
|
156
|
+
items,
|
|
157
|
+
}: {
|
|
158
|
+
tabContentAreaId: string;
|
|
159
|
+
tabsMenuId: string;
|
|
160
|
+
items: ItemsActionPayload< TabItem >;
|
|
161
|
+
} ) => {
|
|
162
|
+
items.forEach( ( { index } ) => {
|
|
163
|
+
const position = index + 1;
|
|
164
|
+
|
|
165
|
+
createElements( {
|
|
166
|
+
title: __( 'Tabs', 'elementor' ),
|
|
167
|
+
elements: [
|
|
168
|
+
{
|
|
169
|
+
containerId: tabContentAreaId,
|
|
170
|
+
model: {
|
|
171
|
+
elType: TAB_CONTENT_ELEMENT_TYPE,
|
|
172
|
+
editor_settings: { title: `Tab ${ position } content`, initial_position: position },
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
containerId: tabsMenuId,
|
|
177
|
+
model: {
|
|
178
|
+
elType: TAB_ELEMENT_TYPE,
|
|
179
|
+
editor_settings: { title: `Tab ${ position } trigger`, initial_position: position },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
} );
|
|
184
|
+
} );
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
duplicateItem,
|
|
189
|
+
moveItem,
|
|
190
|
+
removeItem,
|
|
191
|
+
addItem,
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const calculateDefaultOnMove = ( {
|
|
196
|
+
from,
|
|
197
|
+
to,
|
|
198
|
+
defaultActiveTab,
|
|
199
|
+
}: {
|
|
200
|
+
from: number;
|
|
201
|
+
to: number;
|
|
202
|
+
defaultActiveTab: number;
|
|
203
|
+
} ) => {
|
|
204
|
+
if ( from === defaultActiveTab ) {
|
|
205
|
+
return to;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ( from < defaultActiveTab && to >= defaultActiveTab ) {
|
|
209
|
+
return defaultActiveTab - 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if ( from > defaultActiveTab && to <= defaultActiveTab ) {
|
|
213
|
+
return defaultActiveTab + 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return defaultActiveTab;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const calculateDefaultOnRemove = ( {
|
|
220
|
+
items,
|
|
221
|
+
defaultActiveTab,
|
|
222
|
+
}: {
|
|
223
|
+
items: ItemsActionPayload< TabItem >;
|
|
224
|
+
defaultActiveTab: number;
|
|
225
|
+
} ) => {
|
|
226
|
+
const isDefault = items.some( ( { index } ) => index === defaultActiveTab );
|
|
227
|
+
|
|
228
|
+
if ( isDefault ) {
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const defaultGap = items.reduce( ( acc, { index } ) => ( index < defaultActiveTab ? acc + 1 : acc ), 0 );
|
|
233
|
+
return defaultActiveTab - defaultGap;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const calculateDefaultOnDuplicate = ( {
|
|
237
|
+
items,
|
|
238
|
+
defaultActiveTab,
|
|
239
|
+
}: {
|
|
240
|
+
items: ItemsActionPayload< TabItem >;
|
|
241
|
+
defaultActiveTab: number;
|
|
242
|
+
} ) => {
|
|
243
|
+
const duplicatesBefore = items.reduce( ( acc, { index } ) => {
|
|
244
|
+
const isDuplicatedBeforeDefault = index < defaultActiveTab;
|
|
245
|
+
return isDuplicatedBeforeDefault ? acc + 1 : acc;
|
|
246
|
+
}, 0 );
|
|
247
|
+
return defaultActiveTab + duplicatesBefore;
|
|
248
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import { PropKeyProvider, PropProvider } from '@elementor/editor-controls';
|
|
3
|
+
import { PropKeyProvider, PropProvider, type SetValueMeta } from '@elementor/editor-controls';
|
|
4
4
|
import { setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
5
5
|
import {
|
|
6
6
|
type ElementID,
|
|
@@ -9,12 +9,21 @@ import {
|
|
|
9
9
|
updateElementSettings,
|
|
10
10
|
useElementSettings,
|
|
11
11
|
} from '@elementor/editor-elements';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type CreateOptions,
|
|
14
|
+
isDependencyMet,
|
|
15
|
+
migratePropValue,
|
|
16
|
+
type PropKey,
|
|
17
|
+
type Props,
|
|
18
|
+
type PropsSchema,
|
|
19
|
+
type PropType,
|
|
20
|
+
type PropValue,
|
|
21
|
+
} from '@elementor/editor-props';
|
|
13
22
|
import { undoable } from '@elementor/editor-v1-adapters';
|
|
14
23
|
import { __ } from '@wordpress/i18n';
|
|
15
24
|
|
|
16
25
|
import { useElement } from '../contexts/element-context';
|
|
17
|
-
import { extractOrderedDependencies,
|
|
26
|
+
import { extractOrderedDependencies, getUpdatedValues, type Values } from '../utils/prop-dependency-utils';
|
|
18
27
|
import { createTopLevelObjectType } from './create-top-level-object-type';
|
|
19
28
|
|
|
20
29
|
type SettingsFieldProps = {
|
|
@@ -33,7 +42,11 @@ export const SettingsField = ( { bind, children, propDisplayName }: SettingsFiel
|
|
|
33
42
|
|
|
34
43
|
const elementSettingValues = useElementSettings< PropValue >( elementId, Object.keys( propsSchema ) ) as Values;
|
|
35
44
|
|
|
36
|
-
const
|
|
45
|
+
const migratedValues = useMemo( () => {
|
|
46
|
+
return migratePropValues( elementSettingValues, propsSchema );
|
|
47
|
+
}, [ elementSettingValues, propsSchema ] );
|
|
48
|
+
|
|
49
|
+
const value = { [ bind ]: migratedValues?.[ bind ] ?? null };
|
|
37
50
|
|
|
38
51
|
const propType = createTopLevelObjectType( { schema: propsSchema } );
|
|
39
52
|
|
|
@@ -42,20 +55,25 @@ export const SettingsField = ( { bind, children, propDisplayName }: SettingsFiel
|
|
|
42
55
|
propDisplayName,
|
|
43
56
|
} );
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
59
|
+
const setValue = ( newValue: Values, _: CreateOptions = {}, meta?: SetValueMeta ) => {
|
|
60
|
+
const { withHistory = true } = meta ?? {};
|
|
46
61
|
const dependents = extractOrderedDependencies(
|
|
47
62
|
bind,
|
|
48
63
|
propsSchema,
|
|
49
|
-
|
|
64
|
+
migratedValues,
|
|
50
65
|
dependenciesPerTargetMapping
|
|
51
66
|
);
|
|
52
67
|
|
|
53
|
-
const settings =
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
const settings = getUpdatedValues( newValue, dependents, propsSchema, migratedValues, elementId );
|
|
69
|
+
if ( withHistory ) {
|
|
70
|
+
undoableUpdateElementProp( settings );
|
|
71
|
+
} else {
|
|
72
|
+
updateElementSettings( { id: elementId, props: settings, withHistory: false } );
|
|
73
|
+
}
|
|
56
74
|
};
|
|
57
75
|
|
|
58
|
-
const isDisabled = ( prop: PropType ) => ! isDependencyMet( prop?.dependencies,
|
|
76
|
+
const isDisabled = ( prop: PropType ) => ! isDependencyMet( prop?.dependencies, migratedValues ).isMet;
|
|
59
77
|
|
|
60
78
|
return (
|
|
61
79
|
<PropProvider propType={ propType } value={ value } setValue={ setValue } isDisabled={ isDisabled }>
|
|
@@ -96,3 +114,29 @@ function useUndoableUpdateElementProp( {
|
|
|
96
114
|
);
|
|
97
115
|
}, [ elementId, propDisplayName ] );
|
|
98
116
|
}
|
|
117
|
+
|
|
118
|
+
function migratePropValues( values: Values, schema: PropsSchema ): Values {
|
|
119
|
+
if ( ! values ) {
|
|
120
|
+
return values;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const migrated: Values = {};
|
|
124
|
+
|
|
125
|
+
for ( const [ key, value ] of Object.entries( values ) ) {
|
|
126
|
+
if ( value === null || value === undefined ) {
|
|
127
|
+
migrated[ key ] = value;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const propType = schema[ key ];
|
|
132
|
+
|
|
133
|
+
if ( ! propType ) {
|
|
134
|
+
migrated[ key ] = value;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
migrated[ key ] = migratePropValue( value, propType ) as Values[ string ];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return migrated;
|
|
142
|
+
}
|
|
@@ -4,8 +4,8 @@ import { type PropKey, type PropValue } from '@elementor/editor-props';
|
|
|
4
4
|
import { getStylesSchema } from '@elementor/editor-styles';
|
|
5
5
|
|
|
6
6
|
import { useStylesInheritanceChain } from '../contexts/styles-inheritance-context';
|
|
7
|
+
import { getFieldIndicators } from '../field-indicators-registry';
|
|
7
8
|
import { useStylesField } from '../hooks/use-styles-field';
|
|
8
|
-
import { StylesInheritanceIndicator } from '../styles-inheritance/components/styles-inheritance-indicator';
|
|
9
9
|
import { ConditionalField } from './conditional-field';
|
|
10
10
|
import { createTopLevelObjectType } from './create-top-level-object-type';
|
|
11
11
|
|
|
@@ -36,14 +36,7 @@ export const StylesField = ( { bind, propDisplayName, children }: StylesFieldPro
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
|
-
<ControlAdornmentsProvider
|
|
40
|
-
items={ [
|
|
41
|
-
{
|
|
42
|
-
id: 'styles-inheritance',
|
|
43
|
-
Adornment: StylesInheritanceIndicator,
|
|
44
|
-
},
|
|
45
|
-
] }
|
|
46
|
-
>
|
|
39
|
+
<ControlAdornmentsProvider items={ getFieldIndicators( 'styles' ) }>
|
|
47
40
|
<PropProvider
|
|
48
41
|
propType={ propType }
|
|
49
42
|
value={ { [ bind ]: value } }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { isDependencyMet, type PropsSchema, type PropType, type PropValue } from '@elementor/editor-props';
|
|
3
|
+
|
|
4
|
+
import { type DynamicPropValue } from '../utils';
|
|
5
|
+
|
|
6
|
+
type DynamicConditionalControlProps = React.PropsWithChildren< {
|
|
7
|
+
propType?: PropType;
|
|
8
|
+
propsSchema?: PropsSchema;
|
|
9
|
+
dynamicSettings?: Record< string, DynamicPropValue >;
|
|
10
|
+
} >;
|
|
11
|
+
|
|
12
|
+
export const DynamicConditionalControl: React.FC< DynamicConditionalControlProps > = ( {
|
|
13
|
+
children,
|
|
14
|
+
propType,
|
|
15
|
+
propsSchema,
|
|
16
|
+
dynamicSettings,
|
|
17
|
+
} ) => {
|
|
18
|
+
const defaults = React.useMemo< Record< string, PropValue | null > >( () => {
|
|
19
|
+
if ( ! propsSchema ) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const entries = Object.entries( propsSchema ) as Array< [ string, PropType ] >;
|
|
24
|
+
return entries.reduce< Record< string, PropValue | null > >( ( result, [ key, prop ] ) => {
|
|
25
|
+
result[ key ] = prop?.default ?? null;
|
|
26
|
+
return result;
|
|
27
|
+
}, {} );
|
|
28
|
+
}, [ propsSchema ] );
|
|
29
|
+
|
|
30
|
+
const convertedSettings = React.useMemo( () => {
|
|
31
|
+
if ( ! dynamicSettings ) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Object.entries( dynamicSettings ).reduce< Record< string, PropValue > >(
|
|
36
|
+
( result, [ key, dynamicValue ] ) => {
|
|
37
|
+
if ( dynamicValue && typeof dynamicValue === 'object' && '$$type' in dynamicValue ) {
|
|
38
|
+
result[ key ] = dynamicValue as PropValue;
|
|
39
|
+
} else {
|
|
40
|
+
result[ key ] = {
|
|
41
|
+
$$type: 'plain',
|
|
42
|
+
value: dynamicValue,
|
|
43
|
+
} as PropValue;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
},
|
|
47
|
+
{}
|
|
48
|
+
);
|
|
49
|
+
}, [ dynamicSettings ] );
|
|
50
|
+
|
|
51
|
+
const effectiveSettings = React.useMemo( () => {
|
|
52
|
+
return { ...defaults, ...convertedSettings } as Record< string, PropValue >;
|
|
53
|
+
}, [ defaults, convertedSettings ] );
|
|
54
|
+
|
|
55
|
+
if ( ! propType?.dependencies?.terms.length ) {
|
|
56
|
+
return <>{ children }</>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isHidden = ! isDependencyMet( propType?.dependencies, effectiveSettings ).isMet;
|
|
60
|
+
|
|
61
|
+
return isHidden ? null : <>{ children }</>;
|
|
62
|
+
};
|