@elementor/editor-components 4.0.0-683 → 4.0.0-beta5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-components",
3
3
  "description": "Elementor editor components",
4
- "version": "4.0.0-683",
4
+ "version": "4.0.0-beta5",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,31 +40,31 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor": "4.0.0-683",
44
- "@elementor/editor-canvas": "4.0.0-683",
45
- "@elementor/editor-controls": "4.0.0-683",
46
- "@elementor/editor-documents": "4.0.0-683",
47
- "@elementor/editor-editing-panel": "4.0.0-683",
48
- "@elementor/editor-elements": "4.0.0-683",
49
- "@elementor/editor-elements-panel": "4.0.0-683",
50
- "@elementor/editor-mcp": "4.0.0-683",
51
- "@elementor/editor-templates": "4.0.0-683",
52
- "@elementor/editor-panels": "4.0.0-683",
53
- "@elementor/editor-props": "4.0.0-683",
54
- "@elementor/editor-styles-repository": "4.0.0-683",
55
- "@elementor/editor-ui": "4.0.0-683",
56
- "@elementor/editor-v1-adapters": "4.0.0-683",
57
- "@elementor/http-client": "4.0.0-683",
43
+ "@elementor/editor": "4.0.0-beta5",
44
+ "@elementor/editor-canvas": "4.0.0-beta5",
45
+ "@elementor/editor-controls": "4.0.0-beta5",
46
+ "@elementor/editor-documents": "4.0.0-beta5",
47
+ "@elementor/editor-editing-panel": "4.0.0-beta5",
48
+ "@elementor/editor-elements": "4.0.0-beta5",
49
+ "@elementor/editor-elements-panel": "4.0.0-beta5",
50
+ "@elementor/editor-mcp": "4.0.0-beta5",
51
+ "@elementor/editor-templates": "4.0.0-beta5",
52
+ "@elementor/editor-panels": "4.0.0-beta5",
53
+ "@elementor/editor-props": "4.0.0-beta5",
54
+ "@elementor/editor-styles-repository": "4.0.0-beta5",
55
+ "@elementor/editor-ui": "4.0.0-beta5",
56
+ "@elementor/editor-v1-adapters": "4.0.0-beta5",
57
+ "@elementor/http-client": "4.0.0-beta5",
58
58
  "@elementor/icons": "^1.68.0",
59
- "@elementor/events": "4.0.0-683",
60
- "@elementor/query": "4.0.0-683",
61
- "@elementor/schema": "4.0.0-683",
62
- "@elementor/store": "4.0.0-683",
59
+ "@elementor/events": "4.0.0-beta5",
60
+ "@elementor/query": "4.0.0-beta5",
61
+ "@elementor/schema": "4.0.0-beta5",
62
+ "@elementor/store": "4.0.0-beta5",
63
63
  "@elementor/ui": "1.36.17",
64
- "@elementor/utils": "4.0.0-683",
64
+ "@elementor/utils": "4.0.0-beta5",
65
65
  "@wordpress/i18n": "^5.13.0",
66
- "@elementor/editor-notifications": "4.0.0-683",
67
- "@elementor/editor-current-user": "4.0.0-683"
66
+ "@elementor/editor-notifications": "4.0.0-beta5",
67
+ "@elementor/editor-current-user": "4.0.0-beta5"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "react": "^18.3.1",
package/src/api.ts CHANGED
@@ -92,14 +92,15 @@ export const apiClient = {
92
92
  componentId,
93
93
  } )
94
94
  .then( ( res ) => res.data ),
95
- getOverridableProps: async ( componentId: number ) =>
95
+ getOverridableProps: async ( componentIds: number[] ) =>
96
96
  await httpService()
97
- .get< HttpResponse< OverridableProps > >( `${ BASE_URL }/overridable-props`, {
98
- params: {
99
- componentId: componentId.toString(),
100
- },
101
- } )
102
- .then( ( res ) => res.data.data ),
97
+ .get< HttpResponse< Record< number, OverridableProps | null >, { errors: Record< number, string > } > >(
98
+ `${ BASE_URL }/overridable-props`,
99
+ {
100
+ params: { 'componentIds[]': componentIds },
101
+ }
102
+ )
103
+ .then( ( res ) => res.data ),
103
104
  updateArchivedComponents: async ( componentIds: number[], status: DocumentSaveStatus ) =>
104
105
  await httpService()
105
106
  .post< { data: { failedIds: number[]; successIds: number[]; success: boolean } } >(
@@ -1,17 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { ComponentsIcon, CrownFilledIcon } from '@elementor/icons';
3
3
  import { Box, Button, Divider, Link, List, Stack, Typography } from '@elementor/ui';
4
- import { hasProInstalled } from '@elementor/utils';
5
4
  import { __ } from '@wordpress/i18n';
6
5
 
7
6
  import { useComponents } from '../../hooks/use-components';
8
7
  import { useComponentsPermissions } from '../../hooks/use-components-permissions';
8
+ import { isProComponentsSupported, isProOutdatedForComponents } from '../../utils/is-pro-components-supported';
9
9
  import { ComponentItem } from './components-item';
10
10
  import { LoadingComponents } from './loading-components';
11
11
  import { useSearch } from './search-provider';
12
12
 
13
13
  const LEARN_MORE_URL = 'http://go.elementor.com/components-guide-article';
14
14
  const UPGRADE_URL = 'https://go.elementor.com/go-pro-components/';
15
+ const UPDATE_PLUGINS_URL = '/wp-admin/plugins.php';
15
16
 
16
17
  // Override legacy panel CSS reset that sets h1-h6 to font-size:100% and font-weight:normal.
17
18
  // See: assets/dev/scss/editor/panel/_reset.scss (applied via :where() selector in panel.scss).
@@ -34,7 +35,11 @@ export function ComponentsList() {
34
35
  return <EmptySearchResult />;
35
36
  }
36
37
 
37
- return hasProInstalled() ? <EmptyState /> : <ProUpgradeEmptyState />;
38
+ if ( isProOutdatedForComponents() ) {
39
+ return <ProOutdatedEmptyState />;
40
+ }
41
+
42
+ return isProComponentsSupported() ? <EmptyState /> : <ProUpgradeEmptyState />;
38
43
  }
39
44
 
40
45
  return (
@@ -83,6 +88,46 @@ const ProUpgradeEmptyState = () => {
83
88
  );
84
89
  };
85
90
 
91
+ const ProOutdatedEmptyState = () => {
92
+ return (
93
+ <Stack
94
+ alignItems="center"
95
+ justifyContent="start"
96
+ height="100%"
97
+ sx={ { px: 2, py: 4, maxWidth: 268, m: 'auto' } }
98
+ gap={ 2 }
99
+ overflow="hidden"
100
+ >
101
+ <Stack alignItems="center" gap={ 1 }>
102
+ <ComponentsIcon fontSize="large" sx={ { color: 'text.secondary' } } />
103
+
104
+ <Typography align="center" variant="subtitle2" color="text.secondary" sx={ SUBTITLE_OVERRIDE_SX }>
105
+ { __( 'Create Reusable Components', 'elementor' ) }
106
+ </Typography>
107
+
108
+ <Typography align="center" variant="caption" color="secondary">
109
+ { __( 'Create design elements that sync across your entire site.', 'elementor' ) }
110
+ </Typography>
111
+
112
+ <Typography align="center" variant="caption" color="secondary" sx={ { mt: 1 } }>
113
+ { __( 'To create components, update Elementor Pro to the latest version.', 'elementor' ) }
114
+ </Typography>
115
+ </Stack>
116
+
117
+ <Button
118
+ variant="text"
119
+ color="info"
120
+ size="small"
121
+ href={ UPDATE_PLUGINS_URL }
122
+ target="_blank"
123
+ rel="noopener noreferrer"
124
+ >
125
+ { __( 'Update Elementor Pro', 'elementor' ) }
126
+ </Button>
127
+ </Stack>
128
+ );
129
+ };
130
+
86
131
  const EmptyState = () => {
87
132
  const { canCreate } = useComponentsPermissions();
88
133
 
@@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n';
3
3
 
4
4
  import { ComponentsUpgradeAlert } from '../components-upgrade-alert';
5
5
 
6
- const UPGRADE_URL = 'https://go.elementor.com/go-pro-components-create/';
6
+ export const UPGRADE_URL = 'https://go.elementor.com/go-pro-components-exist-footer/';
7
7
 
8
8
  export function ComponentsProNotification() {
9
9
  return (
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+
4
+ import { ComponentsUpdateAlert } from '../components-update-alert';
5
+
6
+ export function ComponentsUpdateNotification() {
7
+ return (
8
+ <ComponentsUpdateAlert
9
+ title={ __( 'Create new Components', 'elementor' ) }
10
+ description={ __( 'To create new components, update Elementor Pro to the latest version.', 'elementor' ) }
11
+ />
12
+ );
13
+ }
@@ -2,12 +2,13 @@ import * as React from 'react';
2
2
  import { useLayoutEffect } from 'react';
3
3
  import { ThemeProvider } from '@elementor/editor-ui';
4
4
  import { Stack } from '@elementor/ui';
5
- import { hasProInstalled } from '@elementor/utils';
6
5
 
7
6
  import { useComponents } from '../../hooks/use-components';
7
+ import { isProComponentsSupported, isProOutdatedForComponents } from '../../utils/is-pro-components-supported';
8
8
  import { ComponentSearch } from './component-search';
9
9
  import { ComponentsList } from './components-list';
10
10
  import { ComponentsProNotification } from './components-pro-notification';
11
+ import { ComponentsUpdateNotification } from './components-update-notification';
11
12
  import { SearchProvider } from './search-provider';
12
13
 
13
14
  const FULL_HEIGHT_STYLE_ID = 'components-full-height-panel';
@@ -54,8 +55,8 @@ const useFullHeightPanel = () => {
54
55
  const ComponentsContent = () => {
55
56
  const { components, isLoading } = useComponents();
56
57
  const hasComponents = ! isLoading && components.length > 0;
57
- const hasPro = hasProInstalled();
58
- const showProNotification = ! hasPro && hasComponents;
58
+ const showProNotification = ! isProComponentsSupported() && hasComponents;
59
+ const isOutdated = isProOutdatedForComponents();
59
60
 
60
61
  useFullHeightPanel();
61
62
 
@@ -63,7 +64,7 @@ const ComponentsContent = () => {
63
64
  <Stack justifyContent="space-between" sx={ { flex: 1, minHeight: 0 } }>
64
65
  { hasComponents && <ComponentSearch /> }
65
66
  <ComponentsList />
66
- { showProNotification && <ComponentsProNotification /> }
67
+ { showProNotification && ( isOutdated ? <ComponentsUpdateNotification /> : <ComponentsProNotification /> ) }
67
68
  </Stack>
68
69
  );
69
70
  };
@@ -0,0 +1,40 @@
1
+ import * as React from 'react';
2
+ import { InfoCircleFilledIcon } from '@elementor/icons';
3
+ import { Alert, AlertAction, AlertTitle, Box, Typography } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ const UPDATE_PLUGINS_URL = '/wp-admin/plugins.php';
7
+
8
+ interface ComponentsUpdateAlertProps {
9
+ title: string;
10
+ description: string;
11
+ }
12
+
13
+ export function ComponentsUpdateAlert( { title, description }: ComponentsUpdateAlertProps ) {
14
+ return (
15
+ <Box sx={ { mt: 'auto', position: 'sticky', bottom: 0 } }>
16
+ <Alert
17
+ variant="standard"
18
+ color="info"
19
+ icon={ <InfoCircleFilledIcon fontSize="tiny" /> }
20
+ role="status"
21
+ size="small"
22
+ action={
23
+ <AlertAction
24
+ variant="contained"
25
+ color="info"
26
+ href={ UPDATE_PLUGINS_URL }
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ >
30
+ { __( 'Upgrade Now', 'elementor' ) }
31
+ </AlertAction>
32
+ }
33
+ sx={ { m: 2, mt: 1 } }
34
+ >
35
+ <AlertTitle>{ title }</AlertTitle>
36
+ <Typography variant="caption">{ description }</Typography>
37
+ </Alert>
38
+ </Box>
39
+ );
40
+ }
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { PencilIcon } from '@elementor/icons';
3
3
  import { Box, Stack } from '@elementor/ui';
4
- import { hasProInstalled } from '@elementor/utils';
5
4
  import { __ } from '@wordpress/i18n';
6
5
 
7
6
  import { useComponentsPermissions } from '../../hooks/use-components-permissions';
8
7
  import { ComponentInstanceProvider } from '../../provider/component-instance-context';
8
+ import { isProComponentsSupported, isProOutdatedForComponents } from '../../utils/is-pro-components-supported';
9
+ import { ComponentsUpdateAlert } from '../components-update-alert';
9
10
  import { ComponentsUpgradeAlert } from '../components-upgrade-alert';
10
11
  import { DetachAction } from './detach-action';
11
12
  import { EmptyState } from './empty-state';
@@ -13,12 +14,11 @@ import { InstancePanelBody } from './instance-panel-body';
13
14
  import { EditComponentAction, InstancePanelHeader } from './instance-panel-header';
14
15
  import { useInstancePanelData } from './use-instance-panel-data';
15
16
 
16
- const EDIT_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-edit/';
17
+ const EDIT_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-Instance-edit-footer/';
17
18
 
18
19
  export function InstanceEditingPanel() {
19
20
  const { canEdit } = useComponentsPermissions();
20
21
  const data = useInstancePanelData();
21
- const hasPro = hasProInstalled();
22
22
 
23
23
  if ( ! data ) {
24
24
  return null;
@@ -51,13 +51,22 @@ export function InstanceEditingPanel() {
51
51
  componentInstanceId={ componentInstanceId }
52
52
  />
53
53
  </ComponentInstanceProvider>
54
- { ! hasPro && (
55
- <ComponentsUpgradeAlert
56
- title={ __( 'Edit components', 'elementor' ) }
57
- description={ __( 'Editing components requires an active Pro subscription.', 'elementor' ) }
58
- upgradeUrl={ EDIT_UPGRADE_URL }
59
- />
60
- ) }
54
+ { ! isProComponentsSupported() &&
55
+ ( isProOutdatedForComponents() ? (
56
+ <ComponentsUpdateAlert
57
+ title={ __( 'Edit Component', 'elementor' ) }
58
+ description={ __(
59
+ 'To edit components, update Elementor Pro to the latest version.',
60
+ 'elementor'
61
+ ) }
62
+ />
63
+ ) : (
64
+ <ComponentsUpgradeAlert
65
+ title={ __( 'Edit components', 'elementor' ) }
66
+ description={ __( 'Editing components requires an active Pro subscription.', 'elementor' ) }
67
+ upgradeUrl={ EDIT_UPGRADE_URL }
68
+ />
69
+ ) ) }
61
70
  </Box>
62
71
  );
63
72
  }
@@ -10,6 +10,7 @@ import {
10
10
  BaseControl,
11
11
  controlsRegistry,
12
12
  type ControlType,
13
+ ControlTypeContainer,
13
14
  createTopLevelObjectType,
14
15
  ElementProvider,
15
16
  isDynamicPropValue,
@@ -18,7 +19,7 @@ import {
18
19
  } from '@elementor/editor-editing-panel';
19
20
  import { type Control, getElementSettings, getElementType } from '@elementor/editor-elements';
20
21
  import { type AnyTransformable, type PropType, type PropValue } from '@elementor/editor-props';
21
- import { Stack } from '@elementor/ui';
22
+ import { Box } from '@elementor/ui';
22
23
 
23
24
  import { useControlsByWidgetType } from '../../hooks/use-controls-by-widget-type';
24
25
  import {
@@ -50,6 +51,7 @@ import { resolveOverridePropValue } from '../../utils/resolve-override-prop-valu
50
51
  import { ControlLabel } from '../control-label';
51
52
  import { OverrideControlInnerElementNotFoundError } from '../errors';
52
53
  import { useResolvedOriginValue } from './use-resolved-origin-value';
54
+ import { correctExposedEmptyOverride } from './utils/correct-exposed-empty-override';
53
55
 
54
56
  type Props = {
55
57
  overrideKey: string;
@@ -123,12 +125,14 @@ function OverrideControl( { overridableProp }: InternalProps ) {
123
125
  return;
124
126
  }
125
127
 
126
- const newPropValue = getTempNewValueForDynamicProp(
128
+ let newPropValue = getTempNewValueForDynamicProp(
127
129
  propType,
128
130
  propValue,
129
131
  newValue[ overridableProp.overrideKey ]
130
132
  );
131
133
 
134
+ newPropValue = correctExposedEmptyOverride( newPropValue, matchingOverride );
135
+
132
136
  const newOverrideValue = createOverrideValue( {
133
137
  matchingOverride,
134
138
  overrideKey: overridableProp.overrideKey,
@@ -214,10 +218,14 @@ function OverrideControl( { overridableProp }: InternalProps ) {
214
218
  >
215
219
  <PropKeyProvider bind={ overridableProp.overrideKey }>
216
220
  <ControlReplacementsProvider replacements={ controlReplacements }>
217
- <Stack direction="column" gap={ 1 } mb={ 1.5 }>
218
- { layout !== 'custom' && <ControlLabel>{ overridableProp.label }</ControlLabel> }
219
- <OriginalControl control={ control } controlProps={ controlProps } />
220
- </Stack>
221
+ <Box mb={ 1.5 }>
222
+ <ControlTypeContainer layout={ layout }>
223
+ { layout !== 'custom' && (
224
+ <ControlLabel>{ overridableProp.label }</ControlLabel>
225
+ ) }
226
+ <OriginalControl control={ control } controlProps={ controlProps } />
227
+ </ControlTypeContainer>
228
+ </Box>
221
229
  </ControlReplacementsProvider>
222
230
  </PropKeyProvider>
223
231
  </PropProvider>
@@ -0,0 +1,28 @@
1
+ import { type ComponentInstanceOverrideProp } from '../../../prop-types/component-instance-override-prop-type';
2
+ import { type ComponentInstanceOverride } from '../../../prop-types/component-instance-overrides-prop-type';
3
+ import {
4
+ type ComponentOverridableProp,
5
+ componentOverridablePropTypeUtil,
6
+ } from '../../../prop-types/component-overridable-prop-type';
7
+
8
+ type OverrideValue = ComponentInstanceOverrideProp | ComponentOverridableProp;
9
+
10
+ // The control receives the resolved value, so when exposing a prop that was never overridden,
11
+ // origin_value will be the resolved value instead of null.
12
+ // So here, we correct this by resetting origin_value to null.
13
+ export function correctExposedEmptyOverride(
14
+ newPropValue: OverrideValue,
15
+ matchingOverride: ComponentInstanceOverride | null
16
+ ): OverrideValue {
17
+ const newOverridableValue = componentOverridablePropTypeUtil.extract( newPropValue );
18
+ const isExposingEmptyOverride = newOverridableValue && matchingOverride === null;
19
+
20
+ if ( ! isExposingEmptyOverride ) {
21
+ return newPropValue;
22
+ }
23
+
24
+ return componentOverridablePropTypeUtil.create( {
25
+ override_key: newOverridableValue.override_key,
26
+ origin_value: null,
27
+ } );
28
+ }
@@ -25,6 +25,7 @@ import { type ComponentsSlice, selectComponentByUid } from './store/store';
25
25
  import { type ComponentRenderContext, type ExtendedWindow } from './types';
26
26
  import { detachComponentInstance } from './utils/detach-component-instance';
27
27
  import { formatComponentElementsId } from './utils/format-component-elements-id';
28
+ import { isProComponentsSupported, isProOutdatedForComponents } from './utils/is-pro-components-supported';
28
29
  import { switchToComponent } from './utils/switch-to-component';
29
30
  import { trackComponentEvent } from './utils/tracking';
30
31
 
@@ -53,21 +54,30 @@ type ComponentModelInstance = BackboneModel< ComponentModel > & {
53
54
 
54
55
  export const COMPONENT_WIDGET_TYPE = 'e-component';
55
56
 
56
- const EDIT_COMPONENT_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-edit/';
57
+ const EDIT_COMPONENT_DB_CLICK_UPGRADE_URL =
58
+ 'https://go.elementor.com/go-pro-components-Instance-edit-canvas-double-click/';
59
+ const EDIT_COMPONENT_CONTEXT_MENU_UPGRADE_URL =
60
+ 'https://go.elementor.com/go-pro-components-Instance-edit-context-menu/';
61
+
62
+ const UPDATE_PLUGINS_URL = '/wp-admin/plugins.php';
57
63
 
58
64
  const COMPONENT_EDIT_UPGRADE_NOTIFICATION_ID = 'component-edit-upgrade';
65
+ const COMPONENT_EDIT_UPDATE_NOTIFICATION_ID = 'component-edit-update';
66
+
67
+ const COMPONENT_EDIT_UPGRADE_AUTO_HIDE_DURATION = 2000;
59
68
 
60
69
  function notifyComponentEditUpgrade() {
61
70
  notify( {
62
71
  type: 'promotion',
63
72
  id: COMPONENT_EDIT_UPGRADE_NOTIFICATION_ID,
64
73
  message: __( 'Editing components requires an active Pro subscription.', 'elementor' ),
74
+ autoHideDuration: COMPONENT_EDIT_UPGRADE_AUTO_HIDE_DURATION,
65
75
  additionalActionProps: [
66
76
  {
67
77
  size: 'small',
68
78
  variant: 'contained',
69
79
  color: 'promotion',
70
- href: EDIT_COMPONENT_UPGRADE_URL,
80
+ href: EDIT_COMPONENT_DB_CLICK_UPGRADE_URL,
71
81
  target: '_blank',
72
82
  children: __( 'Upgrade Now', 'elementor' ),
73
83
  },
@@ -75,6 +85,24 @@ function notifyComponentEditUpgrade() {
75
85
  } );
76
86
  }
77
87
 
88
+ function notifyComponentEditUpdate() {
89
+ notify( {
90
+ type: 'info',
91
+ id: COMPONENT_EDIT_UPDATE_NOTIFICATION_ID,
92
+ message: __( 'To edit components, update Elementor Pro to the latest version.', 'elementor' ),
93
+ additionalActionProps: [
94
+ {
95
+ size: 'small',
96
+ variant: 'contained',
97
+ color: 'info',
98
+ href: UPDATE_PLUGINS_URL,
99
+ target: '_blank',
100
+ children: __( 'Update Now', 'elementor' ),
101
+ },
102
+ ],
103
+ } );
104
+ }
105
+
78
106
  const updateGroups = ( groups: ContextMenuGroup[], config: ContextMenuGroupConfig ): ContextMenuGroup[] => {
79
107
  const disableMap = new Map( Object.entries( config.disable ?? {} ) );
80
108
  const addMap = new Map( Object.entries( config.add ?? {} ) );
@@ -235,16 +263,18 @@ function createComponentView( options: ComponentTypeOptions ): typeof ElementVie
235
263
  _getContextMenuConfig() {
236
264
  const isAdministrator = isUserAdministrator();
237
265
  const hasPro = hasProInstalled();
266
+ const isOutdated = isProOutdatedForComponents();
267
+ const showPromoBadge = ! hasPro && ! isOutdated;
238
268
 
239
269
  const badgeClass = 'elementor-context-menu-list__item__shortcut__promotion-badge';
240
- const proBadge = `<a href="${ EDIT_COMPONENT_UPGRADE_URL }" target="_blank" onclick="event.stopPropagation()" class="${ badgeClass }"><i class="eicon-upgrade-crown"></i></a>`;
270
+ const proBadge = `<a href="${ EDIT_COMPONENT_CONTEXT_MENU_UPGRADE_URL }" target="_blank" onclick="event.stopPropagation()" class="${ badgeClass }"><i class="eicon-upgrade-crown"></i></a>`;
241
271
 
242
272
  const editComponentAction: ContextMenuAction = {
243
273
  name: 'edit component',
244
274
  icon: 'eicon-edit',
245
275
  title: () => __( 'Edit Component', 'elementor' ),
246
- ...( ! hasPro && { shortcut: proBadge, hasShortcutAction: true } ),
247
- isEnabled: () => hasPro,
276
+ ...( showPromoBadge && { shortcut: proBadge, hasShortcutAction: true } ),
277
+ isEnabled: () => isProComponentsSupported() || isOutdated,
248
278
  callback: ( _: unknown, eventData: ContextMenuEventData ) => this.editComponent( eventData ),
249
279
  };
250
280
 
@@ -286,9 +316,12 @@ function createComponentView( options: ComponentTypeOptions ): typeof ElementVie
286
316
  }
287
317
 
288
318
  editComponent( { trigger, location, secondaryLocation }: ContextMenuEventData ) {
289
- const hasPro = hasProInstalled();
319
+ if ( isProOutdatedForComponents() ) {
320
+ notifyComponentEditUpdate();
321
+ return;
322
+ }
290
323
 
291
- if ( ! hasPro || this.isComponentCurrentlyEdited() ) {
324
+ if ( ! isProComponentsSupported() || this.isComponentCurrentlyEdited() ) {
292
325
  return;
293
326
  }
294
327
 
@@ -341,6 +374,11 @@ function createComponentView( options: ComponentTypeOptions ): typeof ElementVie
341
374
  return;
342
375
  }
343
376
 
377
+ if ( isProOutdatedForComponents() ) {
378
+ notifyComponentEditUpdate();
379
+ return;
380
+ }
381
+
344
382
  if ( ! hasProInstalled() ) {
345
383
  notifyComponentEditUpgrade();
346
384
  return;
@@ -3,31 +3,14 @@ import { __dispatch as dispatch, __getState as getState } from '@elementor/store
3
3
  import { apiClient } from '../../api';
4
4
  import { selectIsOverridablePropsLoaded, slice } from '../store';
5
5
 
6
- export function loadComponentsOverridableProps( componentIds: number[] ) {
7
- if ( ! componentIds.length ) {
8
- return;
9
- }
10
-
11
- return Promise.all( componentIds.map( loadComponentOverrides ) );
12
- }
6
+ export async function loadComponentsOverridableProps( componentIds: number[] ) {
7
+ const unloadedIds = componentIds.filter( ( id ) => ! selectIsOverridablePropsLoaded( getState(), id ) );
13
8
 
14
- async function loadComponentOverrides( componentId: number ) {
15
- const isOverridablePropsLoaded = selectIsOverridablePropsLoaded( getState(), componentId );
16
-
17
- if ( isOverridablePropsLoaded ) {
9
+ if ( ! unloadedIds.length ) {
18
10
  return;
19
11
  }
20
12
 
21
- const overridableProps = await apiClient.getOverridableProps( componentId );
22
-
23
- if ( ! overridableProps ) {
24
- return;
25
- }
13
+ const { data } = await apiClient.getOverridableProps( unloadedIds );
26
14
 
27
- dispatch(
28
- slice.actions.setOverridableProps( {
29
- componentId,
30
- overridableProps,
31
- } )
32
- );
15
+ dispatch( slice.actions.loadOverridableProps( data ) );
33
16
  }
@@ -105,6 +105,25 @@ const baseSlice = createSlice( {
105
105
 
106
106
  component.overridableProps = payload.overridableProps;
107
107
  },
108
+ loadOverridableProps: (
109
+ state,
110
+ { payload }: PayloadAction< Record< ComponentId, OverridableProps | null > >
111
+ ) => {
112
+ const componentIds = Object.keys( payload );
113
+
114
+ componentIds.forEach( ( id ) => {
115
+ const componentId = Number( id );
116
+ const overridableProps = payload[ componentId ];
117
+
118
+ const component = state.data.find( ( comp ) => comp.id === componentId );
119
+
120
+ if ( ! component || ! overridableProps ) {
121
+ return;
122
+ }
123
+
124
+ component.overridableProps = overridableProps;
125
+ } );
126
+ },
108
127
  rename: ( state, { payload }: PayloadAction< { componentUid: string; name: string } > ) => {
109
128
  const component = state.data.find( ( comp ) => comp.uid === payload.componentUid );
110
129
 
@@ -9,7 +9,7 @@ import { type DocumentSaveStatus } from '../types';
9
9
  import { getComponentDocuments } from '../utils/get-component-documents';
10
10
 
11
11
  const INSUFFICIENT_PERMISSIONS_ERROR_CODE = 'insufficient_permissions';
12
- const PUBLISH_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-edit/';
12
+ const PUBLISH_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-Instance-draft-failure/';
13
13
  const PUBLISH_UPGRADE_NOTIFICATION_ID = 'component-publish-upgrade';
14
14
 
15
15
  type Options = {
@@ -0,0 +1,11 @@
1
+ import { hasProInstalled, isProAtLeast } from '@elementor/utils';
2
+
3
+ const MIN_PRO_VERSION_FOR_COMPONENTS = '4.0';
4
+
5
+ export function isProComponentsSupported(): boolean {
6
+ return hasProInstalled() && isProAtLeast( MIN_PRO_VERSION_FOR_COMPONENTS );
7
+ }
8
+
9
+ export function isProOutdatedForComponents(): boolean {
10
+ return hasProInstalled() && ! isProAtLeast( MIN_PRO_VERSION_FOR_COMPONENTS );
11
+ }