@elementor/editor-editing-panel 4.3.0-beta2 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +1224 -1096
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +969 -837
- package/dist/index.mjs.map +1 -1
- package/package.json +25 -24
- package/src/components/content-only-infotip.tsx +51 -0
- package/src/components/css-classes/missing-classes-alert.tsx +1 -1
- package/src/components/editing-panel-tabs.tsx +63 -9
- package/src/controls-registry/controls-registry.tsx +2 -0
- package/src/controls-registry/element-controls/accordion-items-control/accordion-items-control.tsx +12 -1
- package/src/controls-registry/element-controls/list-items-control/list-actions.ts +5 -2
- package/src/controls-registry/element-controls/tabs-control/tabs-control.tsx +12 -3
- package/src/controls-registry/settings-field.tsx +41 -3
- package/src/utils/is-responsive-prop-type.ts +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "4.3.0
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,31 +39,32 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "4.3.0
|
|
43
|
-
"@elementor/editor-canvas": "4.3.0
|
|
44
|
-
"@elementor/editor-controls": "4.3.0
|
|
45
|
-
"@elementor/editor-
|
|
46
|
-
"@elementor/editor-
|
|
47
|
-
"@elementor/editor-
|
|
48
|
-
"@elementor/editor-
|
|
49
|
-
"@elementor/editor-
|
|
50
|
-
"@elementor/editor-
|
|
51
|
-
"@elementor/editor-
|
|
52
|
-
"@elementor/editor-
|
|
53
|
-
"@elementor/editor-styles
|
|
54
|
-
"@elementor/editor-
|
|
55
|
-
"@elementor/editor-
|
|
56
|
-
"@elementor/
|
|
42
|
+
"@elementor/editor": "4.3.0",
|
|
43
|
+
"@elementor/editor-canvas": "4.3.0",
|
|
44
|
+
"@elementor/editor-controls": "4.3.0",
|
|
45
|
+
"@elementor/editor-current-user": "4.3.0",
|
|
46
|
+
"@elementor/editor-documents": "4.3.0",
|
|
47
|
+
"@elementor/editor-elements": "4.3.0",
|
|
48
|
+
"@elementor/editor-interactions": "4.3.0",
|
|
49
|
+
"@elementor/editor-notifications": "4.3.0",
|
|
50
|
+
"@elementor/editor-panels": "4.3.0",
|
|
51
|
+
"@elementor/editor-props": "4.3.0",
|
|
52
|
+
"@elementor/editor-responsive": "4.3.0",
|
|
53
|
+
"@elementor/editor-styles": "4.3.0",
|
|
54
|
+
"@elementor/editor-styles-repository": "4.3.0",
|
|
55
|
+
"@elementor/editor-ui": "4.3.0",
|
|
56
|
+
"@elementor/editor-v1-adapters": "4.3.0",
|
|
57
|
+
"@elementor/http-client": "4.3.0",
|
|
57
58
|
"@elementor/icons": "~1.75.1",
|
|
58
|
-
"@elementor/editor-variables": "4.3.0
|
|
59
|
-
"@elementor/locations": "4.3.0
|
|
60
|
-
"@elementor/menus": "4.3.0
|
|
61
|
-
"@elementor/query": "4.3.0
|
|
62
|
-
"@elementor/schema": "4.3.0
|
|
63
|
-
"@elementor/session": "4.3.0
|
|
59
|
+
"@elementor/editor-variables": "4.3.0",
|
|
60
|
+
"@elementor/locations": "4.3.0",
|
|
61
|
+
"@elementor/menus": "4.3.0",
|
|
62
|
+
"@elementor/query": "4.3.0",
|
|
63
|
+
"@elementor/schema": "4.3.0",
|
|
64
|
+
"@elementor/session": "4.3.0",
|
|
64
65
|
"@elementor/ui": "1.37.5",
|
|
65
|
-
"@elementor/utils": "4.3.0
|
|
66
|
-
"@elementor/wp-media": "4.3.0
|
|
66
|
+
"@elementor/utils": "4.3.0",
|
|
67
|
+
"@elementor/wp-media": "4.3.0",
|
|
67
68
|
"@wordpress/i18n": "^5.13.0"
|
|
68
69
|
},
|
|
69
70
|
"peerDependencies": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type PropsWithChildren, useState } from 'react';
|
|
3
|
+
import { InfoCircleFilledIcon } from '@elementor/icons';
|
|
4
|
+
import { Alert, AlertTitle, Box, Infotip, Link } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
const LEARN_MORE_URL = 'https://go.elementor.com/content-only-access-infotip';
|
|
8
|
+
|
|
9
|
+
const INFOTIP_WIDTH = 300;
|
|
10
|
+
|
|
11
|
+
export const ContentOnlyInfotip = ( { children }: PropsWithChildren ) => {
|
|
12
|
+
const [ isOpen, setIsOpen ] = useState( false );
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Infotip
|
|
16
|
+
open={ isOpen }
|
|
17
|
+
placement="bottom"
|
|
18
|
+
color="secondary"
|
|
19
|
+
content={
|
|
20
|
+
<Alert color="secondary" icon={ <InfoCircleFilledIcon /> } size="small">
|
|
21
|
+
<AlertTitle>{ __( 'Content-only access', 'elementor' ) }</AlertTitle>
|
|
22
|
+
<Box component="span">
|
|
23
|
+
{ __( 'Your Site Admin has limited this role to content editing.', 'elementor' ) }{ ' ' }
|
|
24
|
+
<Link href={ LEARN_MORE_URL } target="_blank" rel="noreferrer" color="info.main">
|
|
25
|
+
{ __( 'Learn More', 'elementor' ) }
|
|
26
|
+
</Link>
|
|
27
|
+
</Box>
|
|
28
|
+
</Alert>
|
|
29
|
+
}
|
|
30
|
+
slotProps={ {
|
|
31
|
+
popper: {
|
|
32
|
+
sx: { width: INFOTIP_WIDTH },
|
|
33
|
+
// The popper is portaled, so it needs its own hover handlers to stay open while the pointer
|
|
34
|
+
// travels from the tab to the "Learn More" link.
|
|
35
|
+
onMouseEnter: () => setIsOpen( true ),
|
|
36
|
+
onMouseLeave: () => setIsOpen( false ),
|
|
37
|
+
},
|
|
38
|
+
} }
|
|
39
|
+
>
|
|
40
|
+
{ /* A disabled MUI tab sets pointer-events: none, so the trigger has to opt back in to receive hover. */ }
|
|
41
|
+
<Box
|
|
42
|
+
component="span"
|
|
43
|
+
sx={ { pointerEvents: 'auto' } }
|
|
44
|
+
onMouseEnter={ () => setIsOpen( true ) }
|
|
45
|
+
onMouseLeave={ () => setIsOpen( false ) }
|
|
46
|
+
>
|
|
47
|
+
{ children }
|
|
48
|
+
</Box>
|
|
49
|
+
</Infotip>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -17,7 +17,7 @@ export function MissingClassesAlert( { onDismiss }: MissingClassesAlertProps ) {
|
|
|
17
17
|
sx={ { mt: 1 } }
|
|
18
18
|
>
|
|
19
19
|
<AlertTitle>{ __( 'Some classes are missing', 'elementor' ) }</AlertTitle>
|
|
20
|
-
<Typography variant="caption"
|
|
20
|
+
<Typography variant="caption" color="primary">
|
|
21
21
|
{ __( 'A class was removed from your site and is no longer active on this element', 'elementor' ) }
|
|
22
22
|
</Typography>
|
|
23
23
|
</Alert>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fragment } from 'react';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
import { useHasContentOnlyAccess } from '@elementor/editor-current-user';
|
|
3
4
|
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
4
5
|
import { Divider, Stack, Tab, TabPanel, Tabs, useTabs } from '@elementor/ui';
|
|
5
6
|
import { __ } from '@wordpress/i18n';
|
|
@@ -8,12 +9,17 @@ import { useElement } from '../contexts/element-context';
|
|
|
8
9
|
import { ScrollProvider } from '../contexts/scroll-context';
|
|
9
10
|
import { useDefaultPanelSettings } from '../hooks/use-default-panel-settings';
|
|
10
11
|
import { useStateByElement } from '../hooks/use-state-by-element';
|
|
12
|
+
import { ContentOnlyInfotip } from './content-only-infotip';
|
|
11
13
|
import { InteractionsTab } from './interactions-tab';
|
|
12
14
|
import { SettingsTab } from './settings-tab';
|
|
13
15
|
import { stickyHeaderStyles, StyleTab } from './style-tab';
|
|
14
16
|
|
|
15
17
|
type TabValue = 'settings' | 'style' | 'interactions';
|
|
16
18
|
|
|
19
|
+
const CONTENT_ONLY_TAB: TabValue = 'settings';
|
|
20
|
+
|
|
21
|
+
const DESIGN_TABS: TabValue[] = [ 'style', 'interactions' ];
|
|
22
|
+
|
|
17
23
|
export const EditingPanelTabs = () => {
|
|
18
24
|
const { element } = useElement();
|
|
19
25
|
|
|
@@ -31,10 +37,24 @@ const PanelTabContent = () => {
|
|
|
31
37
|
const editorDefaults = useDefaultPanelSettings();
|
|
32
38
|
const defaultComponentTab = editorDefaults.defaultTab as TabValue;
|
|
33
39
|
const isPromotedElement = !! getWidgetsCache()?.[ element.type ]?.meta?.is_pro_promotion;
|
|
40
|
+
const hasContentOnlyAccess = useHasContentOnlyAccess();
|
|
41
|
+
|
|
42
|
+
// A promoted element has no General tab, and its Style tab only renders an upsell, so restricting it would leave the panel empty.
|
|
43
|
+
const areDesignTabsRestricted = hasContentOnlyAccess && ! isPromotedElement;
|
|
34
44
|
|
|
35
45
|
const [ storedTab, setCurrentTab ] = useStateByElement< TabValue >( 'tab', defaultComponentTab );
|
|
36
|
-
const currentTab =
|
|
46
|
+
const currentTab = resolveCurrentTab( { storedTab, isPromotedElement, areDesignTabsRestricted } );
|
|
37
47
|
const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( currentTab );
|
|
48
|
+
|
|
49
|
+
const getDesignTabProps = ( tab: TabValue ) => ( {
|
|
50
|
+
...getTabProps( tab ),
|
|
51
|
+
disabled: areDesignTabsRestricted,
|
|
52
|
+
sx: areDesignTabsRestricted ? { pointerEvents: 'auto' } : undefined,
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
const withRestrictionInfotip = ( label: string ) =>
|
|
56
|
+
areDesignTabsRestricted ? <ContentOnlyInfotip>{ label }</ContentOnlyInfotip> : label;
|
|
57
|
+
|
|
38
58
|
return (
|
|
39
59
|
<ScrollProvider>
|
|
40
60
|
<Stack direction="column" sx={ { width: '100%' } }>
|
|
@@ -45,6 +65,10 @@ const PanelTabContent = () => {
|
|
|
45
65
|
sx={ { mt: 0.5 } }
|
|
46
66
|
{ ...getTabsProps() }
|
|
47
67
|
onChange={ ( _: unknown, newValue: TabValue ) => {
|
|
68
|
+
if ( areDesignTabsRestricted && DESIGN_TABS.includes( newValue ) ) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
getTabsProps().onChange( _, newValue );
|
|
49
73
|
setCurrentTab( newValue );
|
|
50
74
|
} }
|
|
@@ -52,8 +76,14 @@ const PanelTabContent = () => {
|
|
|
52
76
|
{ ! isPromotedElement && (
|
|
53
77
|
<Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
|
|
54
78
|
) }
|
|
55
|
-
<Tab
|
|
56
|
-
|
|
79
|
+
<Tab
|
|
80
|
+
label={ withRestrictionInfotip( __( 'Style', 'elementor' ) ) }
|
|
81
|
+
{ ...getDesignTabProps( 'style' ) }
|
|
82
|
+
/>
|
|
83
|
+
<Tab
|
|
84
|
+
label={ withRestrictionInfotip( __( 'Interactions', 'elementor' ) ) }
|
|
85
|
+
{ ...getDesignTabProps( 'interactions' ) }
|
|
86
|
+
/>
|
|
57
87
|
</Tabs>
|
|
58
88
|
<Divider />
|
|
59
89
|
</Stack>
|
|
@@ -62,13 +92,37 @@ const PanelTabContent = () => {
|
|
|
62
92
|
<SettingsTab />
|
|
63
93
|
</TabPanel>
|
|
64
94
|
) }
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
{ ! areDesignTabsRestricted && (
|
|
96
|
+
<>
|
|
97
|
+
<TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
|
|
98
|
+
<StyleTab />
|
|
99
|
+
</TabPanel>
|
|
100
|
+
<TabPanel { ...getTabPanelProps( 'interactions' ) } disablePadding>
|
|
101
|
+
<InteractionsTab />
|
|
102
|
+
</TabPanel>
|
|
103
|
+
</>
|
|
104
|
+
) }
|
|
71
105
|
</Stack>
|
|
72
106
|
</ScrollProvider>
|
|
73
107
|
);
|
|
74
108
|
};
|
|
109
|
+
|
|
110
|
+
const resolveCurrentTab = ( {
|
|
111
|
+
storedTab,
|
|
112
|
+
isPromotedElement,
|
|
113
|
+
areDesignTabsRestricted,
|
|
114
|
+
}: {
|
|
115
|
+
storedTab: TabValue;
|
|
116
|
+
isPromotedElement: boolean;
|
|
117
|
+
areDesignTabsRestricted: boolean;
|
|
118
|
+
} ): TabValue => {
|
|
119
|
+
if ( areDesignTabsRestricted && DESIGN_TABS.includes( storedTab ) ) {
|
|
120
|
+
return CONTENT_ONLY_TAB;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if ( isPromotedElement && storedTab === 'settings' ) {
|
|
124
|
+
return 'style';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return storedTab;
|
|
128
|
+
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
InlineEditingControl,
|
|
11
11
|
KeyValueControl,
|
|
12
12
|
LinkControl,
|
|
13
|
+
NoticeControl,
|
|
13
14
|
NumberControl,
|
|
14
15
|
QueryChipsControl,
|
|
15
16
|
QueryControl,
|
|
@@ -97,6 +98,7 @@ const controlTypes = {
|
|
|
97
98
|
propTypeUtil: timeRangePropTypeUtil,
|
|
98
99
|
},
|
|
99
100
|
'attachment-type': { component: AttachmentTypeControl, layout: 'custom', propTypeUtil: stringPropTypeUtil },
|
|
101
|
+
notice: { component: NoticeControl, layout: 'full', propTypeUtil: stringPropTypeUtil },
|
|
100
102
|
} as const satisfies ControlRegistry;
|
|
101
103
|
|
|
102
104
|
export type ControlType = keyof typeof controlTypes;
|
package/src/controls-registry/element-controls/accordion-items-control/accordion-items-control.tsx
CHANGED
|
@@ -101,11 +101,22 @@ export const AccordionItemsControl = ( { label }: { label: string } ) => {
|
|
|
101
101
|
const ItemLabel = ( { value }: { value: AccordionItem } ) => {
|
|
102
102
|
return (
|
|
103
103
|
<Stack sx={ { minHeight: 20 } } direction="row" alignItems="center" gap={ 1.5 }>
|
|
104
|
-
|
|
104
|
+
{ value.id ? (
|
|
105
|
+
<AccordionItemRepeaterLabel elementId={ value.id } fallbackTitle={ value.title } />
|
|
106
|
+
) : (
|
|
107
|
+
<span>{ value?.title }</span>
|
|
108
|
+
) }
|
|
105
109
|
</Stack>
|
|
106
110
|
);
|
|
107
111
|
};
|
|
108
112
|
|
|
113
|
+
const AccordionItemRepeaterLabel = ( { elementId, fallbackTitle }: { elementId: string; fallbackTitle?: string } ) => {
|
|
114
|
+
const editorSettings = useElementEditorSettings( elementId );
|
|
115
|
+
const label = editorSettings?.title ?? fallbackTitle ?? '';
|
|
116
|
+
|
|
117
|
+
return <span>{ label }</span>;
|
|
118
|
+
};
|
|
119
|
+
|
|
109
120
|
const ItemContent = ( { value }: { value: AccordionItem } ) => {
|
|
110
121
|
if ( ! value.id ) {
|
|
111
122
|
return null;
|
|
@@ -21,7 +21,6 @@ export const LIST_ITEM_ELEMENT_TYPE = 'e-list-item';
|
|
|
21
21
|
const LIST_ITEM_MARKER_ELEMENT_TYPE = 'e-list-item-marker';
|
|
22
22
|
const LIST_ITEM_CONTENT_ELEMENT_TYPE = 'e-list-item-content';
|
|
23
23
|
const PARAGRAPH_WIDGET_TYPE = 'e-paragraph';
|
|
24
|
-
const SVG_WIDGET_TYPE = 'e-svg';
|
|
25
24
|
|
|
26
25
|
const TRAILING_NUMBER = /(\d+)\s*$/;
|
|
27
26
|
|
|
@@ -69,9 +68,13 @@ const buildItemModel = ( position: number, showMarkers: boolean ): V1ElementData
|
|
|
69
68
|
elements: [
|
|
70
69
|
{
|
|
71
70
|
elType: 'widget',
|
|
72
|
-
widgetType:
|
|
71
|
+
widgetType: PARAGRAPH_WIDGET_TYPE,
|
|
73
72
|
id: generateElementId(),
|
|
74
73
|
elements: [],
|
|
74
|
+
settings: {
|
|
75
|
+
tag: { $$type: 'string', value: 'span' },
|
|
76
|
+
paragraph: escapedHtmlPropTypeUtil.create( '•' ),
|
|
77
|
+
},
|
|
75
78
|
},
|
|
76
79
|
],
|
|
77
80
|
},
|
|
@@ -109,16 +109,25 @@ export const TabsControlContent = ( { label }: { label: string } ) => {
|
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
const ItemLabel = ( { value, index }: { value: TabItem; index: number } ) => {
|
|
112
|
-
const elementTitle = value?.title;
|
|
113
|
-
|
|
114
112
|
return (
|
|
115
113
|
<Stack sx={ { minHeight: 20 } } direction="row" alignItems="center" gap={ 1.5 }>
|
|
116
|
-
|
|
114
|
+
{ value.id ? (
|
|
115
|
+
<TabRepeaterLabel elementId={ value.id } fallbackTitle={ value.title } />
|
|
116
|
+
) : (
|
|
117
|
+
<span>{ value?.title }</span>
|
|
118
|
+
) }
|
|
117
119
|
<ItemDefaultTab index={ index } />
|
|
118
120
|
</Stack>
|
|
119
121
|
);
|
|
120
122
|
};
|
|
121
123
|
|
|
124
|
+
const TabRepeaterLabel = ( { elementId, fallbackTitle }: { elementId: string; fallbackTitle?: string } ) => {
|
|
125
|
+
const editorSettings = useElementEditorSettings( elementId );
|
|
126
|
+
const label = editorSettings?.title ?? fallbackTitle ?? '';
|
|
127
|
+
|
|
128
|
+
return <span>{ label }</span>;
|
|
129
|
+
};
|
|
130
|
+
|
|
122
131
|
const ItemDefaultTab = ( { index }: { index: number } ) => {
|
|
123
132
|
const { value: defaultItem } = useBoundProp( numberPropTypeUtil );
|
|
124
133
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import { PropKeyProvider, PropProvider, type SetValueMeta } from '@elementor/editor-controls';
|
|
3
|
+
import { PropKeyProvider, PropProvider, type SetValueMeta, useBoundProp } from '@elementor/editor-controls';
|
|
4
4
|
import { setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
5
5
|
import { type ElementID, getElementLabel, getElementSettings, updateElementSettings } from '@elementor/editor-elements';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type CreateOptions,
|
|
8
|
+
type PropKey,
|
|
9
|
+
type Props,
|
|
10
|
+
responsiveFallbackChain,
|
|
11
|
+
responsivePropTypeUtil,
|
|
12
|
+
} from '@elementor/editor-props';
|
|
13
|
+
import { type BreakpointId, useActiveBreakpoint, useBreakpoints } from '@elementor/editor-responsive';
|
|
7
14
|
import { undoable } from '@elementor/editor-v1-adapters';
|
|
8
15
|
import { __ } from '@wordpress/i18n';
|
|
9
16
|
|
|
10
17
|
import { useElement } from '../contexts/element-context';
|
|
18
|
+
import { isResponsivePropType } from '../utils/is-responsive-prop-type';
|
|
11
19
|
import {
|
|
12
20
|
extractDependencyEffect,
|
|
13
21
|
extractOrderedDependencies,
|
|
@@ -24,6 +32,7 @@ type SettingsFieldProps = {
|
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
const HISTORY_DEBOUNCE_WAIT = 800;
|
|
35
|
+
const DESKTOP_BREAKPOINT: BreakpointId = 'desktop';
|
|
27
36
|
|
|
28
37
|
export const SettingsField = ( { bind, children, propDisplayName }: SettingsFieldProps ) => {
|
|
29
38
|
const {
|
|
@@ -62,7 +71,36 @@ export const SettingsField = ( { bind, children, propDisplayName }: SettingsFiel
|
|
|
62
71
|
|
|
63
72
|
return (
|
|
64
73
|
<PropProvider propType={ propType } value={ value } setValue={ setValue } isDisabled={ isDisabled }>
|
|
65
|
-
<PropKeyProvider bind={ bind }>
|
|
74
|
+
<PropKeyProvider bind={ bind }>
|
|
75
|
+
{ isResponsivePropType( propsSchema[ bind ] ) ? (
|
|
76
|
+
<ResponsiveBinding>{ children }</ResponsiveBinding>
|
|
77
|
+
) : (
|
|
78
|
+
children
|
|
79
|
+
) }
|
|
80
|
+
</PropKeyProvider>
|
|
81
|
+
</PropProvider>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const ResponsiveBinding = ( { children }: { children: React.ReactNode } ) => {
|
|
86
|
+
const { value, setValue, propType, disabled } = useBoundProp( responsivePropTypeUtil );
|
|
87
|
+
const breakpoint = useActiveBreakpoint() ?? DESKTOP_BREAKPOINT;
|
|
88
|
+
const activeBreakpoints = ( useBreakpoints() ?? [] ).map( ( { id } ) => id );
|
|
89
|
+
|
|
90
|
+
const inherited = responsiveFallbackChain( breakpoint )
|
|
91
|
+
.filter( ( key ) => key !== breakpoint && activeBreakpoints.includes( key as BreakpointId ) )
|
|
92
|
+
.map( ( key ) => value?.[ key ] )
|
|
93
|
+
.find( ( entry ) => entry !== null && entry !== undefined );
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<PropProvider
|
|
97
|
+
propType={ propType }
|
|
98
|
+
value={ value }
|
|
99
|
+
setValue={ setValue }
|
|
100
|
+
placeholder={ { [ breakpoint ]: inherited } }
|
|
101
|
+
isDisabled={ () => disabled }
|
|
102
|
+
>
|
|
103
|
+
<PropKeyProvider bind={ breakpoint }>{ children }</PropKeyProvider>
|
|
66
104
|
</PropProvider>
|
|
67
105
|
);
|
|
68
106
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PropType, type UnionPropType } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
export function isResponsivePropType( propType: PropType | undefined ): boolean {
|
|
4
|
+
if ( ! propType ) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if ( 'key' in propType && propType.key === 'responsive' ) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if ( propType.kind === 'union' ) {
|
|
13
|
+
return Object.values( ( propType as UnionPropType ).prop_types ).some( ( inner ) =>
|
|
14
|
+
isResponsivePropType( inner )
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return false;
|
|
19
|
+
}
|