@elementor/editor-components 4.0.0-manual → 4.0.1

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.
Files changed (106) hide show
  1. package/dist/index.d.mts +1422 -1
  2. package/dist/index.d.ts +1422 -1
  3. package/dist/index.js +2096 -4814
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2028 -4837
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +23 -23
  8. package/src/components/components-tab/components-list.tsx +92 -4
  9. package/src/components/components-tab/components-pro-notification.tsx +9 -15
  10. package/src/components/components-tab/components-update-notification.tsx +13 -0
  11. package/src/components/components-tab/components.tsx +52 -3
  12. package/src/components/components-tab/loading-components.tsx +26 -14
  13. package/src/components/components-update-alert.tsx +40 -0
  14. package/src/components/components-upgrade-alert.tsx +39 -0
  15. package/src/components/detach-instance-confirmation-dialog.tsx +50 -0
  16. package/src/components/instance-editing-panel/detach-action.tsx +76 -0
  17. package/src/components/instance-editing-panel/empty-state.tsx +9 -2
  18. package/src/components/instance-editing-panel/instance-editing-panel.tsx +34 -6
  19. package/src/components/instance-editing-panel/override-prop-control.tsx +14 -6
  20. package/src/components/instance-editing-panel/use-instance-panel-data.ts +2 -2
  21. package/src/components/instance-editing-panel/utils/correct-exposed-empty-override.ts +28 -0
  22. package/src/consts.ts +1 -0
  23. package/src/create-component-type.ts +130 -29
  24. package/src/index.ts +92 -0
  25. package/src/init.ts +6 -4
  26. package/src/store/actions/update-overridable-prop.ts +4 -10
  27. package/src/store/dispatchers.ts +63 -0
  28. package/src/store/extensible-slice.ts +168 -0
  29. package/src/store/selectors.ts +53 -0
  30. package/src/store/store-types.ts +48 -0
  31. package/src/store/store.ts +7 -169
  32. package/src/sync/publish-draft-components-in-page-before-save.ts +42 -1
  33. package/src/types.ts +1 -1
  34. package/src/utils/detach-component-instance/detach-component-instance.ts +172 -0
  35. package/src/utils/detach-component-instance/index.ts +1 -0
  36. package/src/utils/detach-component-instance/regenerate-local-style-ids.ts +53 -0
  37. package/src/utils/detach-component-instance/resolve-detached-instance.ts +94 -0
  38. package/src/utils/detach-component-instance/resolve-overridable-settings.ts +121 -0
  39. package/src/utils/is-component-instance.ts +1 -1
  40. package/src/utils/is-pro-components-supported.ts +11 -0
  41. package/src/utils/tracking.ts +2 -1
  42. package/src/extended/components/component-introduction.tsx +0 -77
  43. package/src/extended/components/component-panel-header/component-badge.tsx +0 -73
  44. package/src/extended/components/component-panel-header/component-panel-header.tsx +0 -98
  45. package/src/extended/components/component-properties-panel/component-properties-panel-content.tsx +0 -176
  46. package/src/extended/components/component-properties-panel/component-properties-panel.tsx +0 -43
  47. package/src/extended/components/component-properties-panel/properties-empty-state.tsx +0 -51
  48. package/src/extended/components/component-properties-panel/properties-group.tsx +0 -196
  49. package/src/extended/components/component-properties-panel/property-item.tsx +0 -124
  50. package/src/extended/components/component-properties-panel/sortable.tsx +0 -92
  51. package/src/extended/components/component-properties-panel/use-current-editable-item.ts +0 -73
  52. package/src/extended/components/component-properties-panel/utils/generate-unique-label.ts +0 -21
  53. package/src/extended/components/component-properties-panel/utils/validate-group-label.ts +0 -24
  54. package/src/extended/components/components-tab/component-item.tsx +0 -180
  55. package/src/extended/components/components-tab/components.tsx +0 -58
  56. package/src/extended/components/components-tab/delete-confirmation-dialog.tsx +0 -26
  57. package/src/extended/components/create-component-form/create-component-form.tsx +0 -282
  58. package/src/extended/components/create-component-form/hooks/use-form.ts +0 -72
  59. package/src/extended/components/create-component-form/utils/get-component-event-data.ts +0 -54
  60. package/src/extended/components/edit-component/component-modal.tsx +0 -133
  61. package/src/extended/components/edit-component/edit-component.tsx +0 -166
  62. package/src/extended/components/edit-component/use-canvas-document.ts +0 -9
  63. package/src/extended/components/edit-component/use-element-rect.ts +0 -81
  64. package/src/extended/components/instance-editing-panel/instance-editing-panel.tsx +0 -60
  65. package/src/extended/components/overridable-props/indicator.tsx +0 -83
  66. package/src/extended/components/overridable-props/overridable-prop-control.tsx +0 -127
  67. package/src/extended/components/overridable-props/overridable-prop-form.tsx +0 -135
  68. package/src/extended/components/overridable-props/overridable-prop-indicator.tsx +0 -138
  69. package/src/extended/components/overridable-props/utils/validate-prop-label.ts +0 -38
  70. package/src/extended/consts.ts +0 -3
  71. package/src/extended/hooks/use-navigate-back.ts +0 -24
  72. package/src/extended/init.ts +0 -104
  73. package/src/extended/mcp/index.ts +0 -14
  74. package/src/extended/mcp/save-as-component-tool.ts +0 -436
  75. package/src/extended/store/actions/add-overridable-group.ts +0 -59
  76. package/src/extended/store/actions/archive-component.ts +0 -19
  77. package/src/extended/store/actions/create-unpublished-component.ts +0 -102
  78. package/src/extended/store/actions/delete-overridable-group.ts +0 -38
  79. package/src/extended/store/actions/delete-overridable-prop.ts +0 -70
  80. package/src/extended/store/actions/rename-component.ts +0 -49
  81. package/src/extended/store/actions/rename-overridable-group.ts +0 -39
  82. package/src/extended/store/actions/reorder-group-props.ts +0 -43
  83. package/src/extended/store/actions/reorder-overridable-groups.ts +0 -30
  84. package/src/extended/store/actions/reset-sanitized-components.ts +0 -7
  85. package/src/extended/store/actions/set-overridable-prop.ts +0 -117
  86. package/src/extended/store/actions/update-component-sanitized-attribute.ts +0 -8
  87. package/src/extended/store/actions/update-current-component.ts +0 -21
  88. package/src/extended/store/actions/update-overridable-prop-params.ts +0 -58
  89. package/src/extended/store/utils/groups-transformers.ts +0 -187
  90. package/src/extended/sync/before-save.ts +0 -52
  91. package/src/extended/sync/cleanup-overridable-props-on-delete.ts +0 -85
  92. package/src/extended/sync/create-components-before-save.ts +0 -113
  93. package/src/extended/sync/handle-component-edit-mode-container.ts +0 -114
  94. package/src/extended/sync/prevent-non-atomic-nesting.ts +0 -198
  95. package/src/extended/sync/revert-overridables-on-copy-or-duplicate.ts +0 -66
  96. package/src/extended/sync/sanitize-overridable-props.ts +0 -32
  97. package/src/extended/sync/set-component-overridable-props-settings-before-save.ts +0 -23
  98. package/src/extended/sync/update-archived-component-before-save.ts +0 -32
  99. package/src/extended/sync/update-component-title-before-save.ts +0 -19
  100. package/src/extended/utils/component-form-schema.ts +0 -32
  101. package/src/extended/utils/component-name-validation.ts +0 -27
  102. package/src/extended/utils/create-component-model.ts +0 -28
  103. package/src/extended/utils/get-container-for-new-element.ts +0 -49
  104. package/src/extended/utils/is-editing-component.ts +0 -13
  105. package/src/extended/utils/replace-element-with-component.ts +0 -11
  106. package/src/extended/utils/revert-overridable-settings.ts +0 -207
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-manual",
4
+ "version": "4.0.1",
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-manual",
44
- "@elementor/editor-canvas": "4.0.0-manual",
45
- "@elementor/editor-controls": "4.0.0-manual",
46
- "@elementor/editor-documents": "4.0.0-manual",
47
- "@elementor/editor-editing-panel": "4.0.0-manual",
48
- "@elementor/editor-elements": "4.0.0-manual",
49
- "@elementor/editor-elements-panel": "4.0.0-manual",
50
- "@elementor/editor-mcp": "4.0.0-manual",
51
- "@elementor/editor-templates": "4.0.0-manual",
52
- "@elementor/editor-panels": "4.0.0-manual",
53
- "@elementor/editor-props": "4.0.0-manual",
54
- "@elementor/editor-styles-repository": "4.0.0-manual",
55
- "@elementor/editor-ui": "4.0.0-manual",
56
- "@elementor/editor-v1-adapters": "4.0.0-manual",
57
- "@elementor/http-client": "4.0.0-manual",
43
+ "@elementor/editor": "4.0.1",
44
+ "@elementor/editor-canvas": "4.0.1",
45
+ "@elementor/editor-controls": "4.0.1",
46
+ "@elementor/editor-documents": "4.0.1",
47
+ "@elementor/editor-editing-panel": "4.0.1",
48
+ "@elementor/editor-elements": "4.0.1",
49
+ "@elementor/editor-elements-panel": "4.0.1",
50
+ "@elementor/editor-mcp": "4.0.1",
51
+ "@elementor/editor-templates": "4.0.1",
52
+ "@elementor/editor-panels": "4.0.1",
53
+ "@elementor/editor-props": "4.0.1",
54
+ "@elementor/editor-styles-repository": "4.0.1",
55
+ "@elementor/editor-ui": "4.0.1",
56
+ "@elementor/editor-v1-adapters": "4.0.1",
57
+ "@elementor/http-client": "4.0.1",
58
58
  "@elementor/icons": "^1.68.0",
59
- "@elementor/events": "4.0.0-manual",
60
- "@elementor/query": "4.0.0-manual",
61
- "@elementor/schema": "4.0.0-manual",
62
- "@elementor/store": "4.0.0-manual",
59
+ "@elementor/events": "4.0.1",
60
+ "@elementor/query": "4.0.1",
61
+ "@elementor/schema": "4.0.1",
62
+ "@elementor/store": "4.0.1",
63
63
  "@elementor/ui": "1.36.17",
64
- "@elementor/utils": "4.0.0-manual",
64
+ "@elementor/utils": "4.0.1",
65
65
  "@wordpress/i18n": "^5.13.0",
66
- "@elementor/editor-notifications": "4.0.0-manual",
67
- "@elementor/editor-current-user": "4.0.0-manual"
66
+ "@elementor/editor-notifications": "4.0.1",
67
+ "@elementor/editor-current-user": "4.0.1"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "react": "^18.3.1",
@@ -1,15 +1,18 @@
1
1
  import * as React from 'react';
2
- import { ComponentsIcon } from '@elementor/icons';
3
- import { Box, Divider, Link, List, Stack, Typography } from '@elementor/ui';
2
+ import { ComponentsIcon, CrownFilledIcon } from '@elementor/icons';
3
+ import { Box, Button, Divider, Link, List, Stack, Typography } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
6
  import { useComponents } from '../../hooks/use-components';
7
7
  import { useComponentsPermissions } from '../../hooks/use-components-permissions';
8
+ import { isProComponentsSupported, isProOutdatedForComponents } from '../../utils/is-pro-components-supported';
8
9
  import { ComponentItem } from './components-item';
9
10
  import { LoadingComponents } from './loading-components';
10
11
  import { useSearch } from './search-provider';
11
12
 
12
13
  const LEARN_MORE_URL = 'http://go.elementor.com/components-guide-article';
14
+ const UPGRADE_URL = 'https://go.elementor.com/go-pro-components/';
15
+ const UPDATE_PLUGINS_URL = '/wp-admin/plugins.php';
13
16
 
14
17
  // Override legacy panel CSS reset that sets h1-h6 to font-size:100% and font-weight:normal.
15
18
  // See: assets/dev/scss/editor/panel/_reset.scss (applied via :where() selector in panel.scss).
@@ -28,7 +31,15 @@ export function ComponentsList() {
28
31
  const isEmpty = ! components?.length;
29
32
 
30
33
  if ( isEmpty ) {
31
- return searchValue.length ? <EmptySearchResult /> : <EmptyState />;
34
+ if ( searchValue.length ) {
35
+ return <EmptySearchResult />;
36
+ }
37
+
38
+ if ( isProOutdatedForComponents() ) {
39
+ return <ProOutdatedEmptyState />;
40
+ }
41
+
42
+ return isProComponentsSupported() ? <EmptyState /> : <ProUpgradeEmptyState />;
32
43
  }
33
44
 
34
45
  return (
@@ -40,7 +51,84 @@ export function ComponentsList() {
40
51
  );
41
52
  }
42
53
 
43
- export const EmptyState = () => {
54
+ const ProUpgradeEmptyState = () => {
55
+ return (
56
+ <Stack
57
+ alignItems="center"
58
+ justifyContent="start"
59
+ height="100%"
60
+ sx={ { px: 2, py: 4 } }
61
+ gap={ 2 }
62
+ overflow="hidden"
63
+ >
64
+ <Stack alignItems="center" gap={ 1 }>
65
+ <ComponentsIcon fontSize="large" sx={ { color: 'text.secondary' } } />
66
+
67
+ <Typography align="center" variant="subtitle2" color="text.secondary" sx={ SUBTITLE_OVERRIDE_SX }>
68
+ { __( 'Create Reusable Components', 'elementor' ) }
69
+ </Typography>
70
+
71
+ <Typography align="center" variant="caption" color="secondary" sx={ { maxWidth: 200 } }>
72
+ { __( 'Create design elements that sync across your entire site.', 'elementor' ) }
73
+ </Typography>
74
+ </Stack>
75
+
76
+ <Button
77
+ variant="contained"
78
+ color="promotion"
79
+ size="small"
80
+ startIcon={ <CrownFilledIcon /> }
81
+ href={ UPGRADE_URL }
82
+ target="_blank"
83
+ rel="noopener noreferrer"
84
+ >
85
+ { __( 'Upgrade now', 'elementor' ) }
86
+ </Button>
87
+ </Stack>
88
+ );
89
+ };
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
+
131
+ const EmptyState = () => {
44
132
  const { canCreate } = useComponentsPermissions();
45
133
 
46
134
  return (
@@ -1,22 +1,16 @@
1
1
  import * as React from 'react';
2
- import { InfoAlert } from '@elementor/editor-ui';
3
- import { Box, Typography } from '@elementor/ui';
4
2
  import { __ } from '@wordpress/i18n';
5
3
 
4
+ import { ComponentsUpgradeAlert } from '../components-upgrade-alert';
5
+
6
+ export const UPGRADE_URL = 'https://go.elementor.com/go-pro-components-exist-footer/';
7
+
6
8
  export function ComponentsProNotification() {
7
9
  return (
8
- <Box sx={ { px: 2 } }>
9
- <InfoAlert>
10
- <Typography variant="caption" component="span">
11
- <Typography variant="caption" component="span" fontWeight="bold">
12
- { __( 'Try Components for free:', 'elementor' ) }
13
- </Typography>{ ' ' }
14
- { __(
15
- 'Soon Components will be part of the Pro subscription, but what you create now will remain on your site.',
16
- 'elementor'
17
- ) }
18
- </Typography>
19
- </InfoAlert>
20
- </Box>
10
+ <ComponentsUpgradeAlert
11
+ title={ __( 'Create new components', 'elementor' ) }
12
+ description={ __( 'Creating new components requires an active Pro subscription.', 'elementor' ) }
13
+ upgradeUrl={ UPGRADE_URL }
14
+ />
21
15
  );
22
16
  }
@@ -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
+ }
@@ -1,22 +1,71 @@
1
1
  import * as React from 'react';
2
+ import { useLayoutEffect } from 'react';
2
3
  import { ThemeProvider } from '@elementor/editor-ui';
4
+ import { Stack } from '@elementor/ui';
3
5
 
4
6
  import { useComponents } from '../../hooks/use-components';
7
+ import { isProComponentsSupported, isProOutdatedForComponents } from '../../utils/is-pro-components-supported';
5
8
  import { ComponentSearch } from './component-search';
6
9
  import { ComponentsList } from './components-list';
7
10
  import { ComponentsProNotification } from './components-pro-notification';
11
+ import { ComponentsUpdateNotification } from './components-update-notification';
8
12
  import { SearchProvider } from './search-provider';
9
13
 
14
+ const FULL_HEIGHT_STYLE_ID = 'components-full-height-panel';
15
+
16
+ const FULL_HEIGHT_CSS = `
17
+ #elementor-panel-page-elements {
18
+ display: flex;
19
+ flex-direction: column;
20
+ height: 100%;
21
+ }
22
+
23
+ #elementor-panel-elements {
24
+ display: flex;
25
+ flex-direction: column;
26
+ flex: 1;
27
+ min-height: 0;
28
+ }
29
+
30
+ #elementor-panel-elements-wrapper {
31
+ display: flex;
32
+ flex-direction: column;
33
+ flex: 1;
34
+ min-height: 0;
35
+ }
36
+ `;
37
+
38
+ const useFullHeightPanel = () => {
39
+ useLayoutEffect( () => {
40
+ let style = document.getElementById( FULL_HEIGHT_STYLE_ID );
41
+
42
+ if ( ! style ) {
43
+ style = document.createElement( 'style' );
44
+ style.id = FULL_HEIGHT_STYLE_ID;
45
+ style.textContent = FULL_HEIGHT_CSS;
46
+ document.head.appendChild( style );
47
+ }
48
+
49
+ return () => {
50
+ document.getElementById( FULL_HEIGHT_STYLE_ID )?.remove();
51
+ };
52
+ }, [] );
53
+ };
54
+
10
55
  const ComponentsContent = () => {
11
56
  const { components, isLoading } = useComponents();
12
57
  const hasComponents = ! isLoading && components.length > 0;
58
+ const showProNotification = ! isProComponentsSupported() && hasComponents;
59
+ const isOutdated = isProOutdatedForComponents();
60
+
61
+ useFullHeightPanel();
13
62
 
14
63
  return (
15
- <>
64
+ <Stack justifyContent="space-between" sx={ { flex: 1, minHeight: 0 } }>
16
65
  { hasComponents && <ComponentSearch /> }
17
- { hasComponents && <ComponentsProNotification /> }
18
66
  <ComponentsList />
19
- </>
67
+ { showProNotification && ( isOutdated ? <ComponentsUpdateNotification /> : <ComponentsProNotification /> ) }
68
+ </Stack>
20
69
  );
21
70
  };
22
71
 
@@ -1,41 +1,53 @@
1
1
  import * as React from 'react';
2
- import { Box, ListItemButton, Skeleton, Stack } from '@elementor/ui';
2
+ import { Box, Skeleton, Stack } from '@elementor/ui';
3
3
 
4
- const ROWS = Array.from( { length: 6 }, ( _, index ) => index );
4
+ const ROWS = Array.from( { length: 3 }, ( _, index ) => index );
5
+
6
+ const STAGGER_DELAY_MS = 80;
5
7
 
6
8
  export const LoadingComponents = () => {
7
9
  return (
8
10
  <Stack
9
11
  aria-label="Loading components"
10
- gap={ 1 }
12
+ gap={ 1.5 }
11
13
  sx={ {
12
14
  pointerEvents: 'none',
13
15
  position: 'relative',
14
16
  maxHeight: '300px',
15
17
  overflow: 'hidden',
18
+ px: 1,
16
19
  '&:after': {
17
20
  position: 'absolute',
18
- top: 0,
21
+ bottom: 0,
19
22
  content: '""',
20
23
  left: 0,
21
24
  width: '100%',
22
- height: '300px',
23
- background: 'linear-gradient(to top, white, transparent)',
25
+ height: '40%',
24
26
  pointerEvents: 'none',
27
+ zIndex: 1,
25
28
  },
26
29
  } }
27
30
  >
28
31
  { ROWS.map( ( row ) => (
29
- <ListItemButton
32
+ <Box
30
33
  key={ row }
31
- sx={ { border: 'solid 1px', borderColor: 'divider', py: 0.5, px: 1 } }
32
- shape="rounded"
34
+ display="flex"
35
+ alignItems="center"
36
+ gap={ 1.5 }
37
+ sx={ {
38
+ py: 0.75,
39
+ px: 1.5,
40
+ opacity: 0,
41
+ animation: `e-loading-fade-in 0.4s ease-out ${ row * STAGGER_DELAY_MS }ms forwards`,
42
+ '@keyframes e-loading-fade-in': {
43
+ from: { opacity: 0, transform: 'translateY(4px)' },
44
+ to: { opacity: 1, transform: 'translateY(0)' },
45
+ },
46
+ } }
33
47
  >
34
- <Box display="flex" gap={ 1 } width="100%">
35
- <Skeleton variant="text" width={ '24px' } height={ '36px' } />
36
- <Skeleton variant="text" width={ '100%' } height={ '36px' } />
37
- </Box>
38
- </ListItemButton>
48
+ <Skeleton animation="wave" variant="rounded" width={ 24 } height={ 24 } />
49
+ <Skeleton animation="wave" variant="rounded" width="60%" height={ 14 } />
50
+ </Box>
39
51
  ) ) }
40
52
  </Stack>
41
53
  );
@@ -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
+ }
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import { CrownFilledIcon } from '@elementor/icons';
3
+ import { Alert, AlertAction, AlertTitle, Box, Typography } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ interface ComponentsUpgradeAlertProps {
7
+ title: string;
8
+ description: string;
9
+ upgradeUrl: string;
10
+ }
11
+
12
+ export function ComponentsUpgradeAlert( { title, description, upgradeUrl }: ComponentsUpgradeAlertProps ) {
13
+ return (
14
+ <Box sx={ { mt: 'auto', position: 'sticky', bottom: 0 } }>
15
+ <Alert
16
+ variant="standard"
17
+ color="promotion"
18
+ icon={ <CrownFilledIcon fontSize="tiny" /> }
19
+ role="status"
20
+ size="small"
21
+ action={
22
+ <AlertAction
23
+ variant="contained"
24
+ color="promotion"
25
+ href={ upgradeUrl }
26
+ target="_blank"
27
+ rel="noopener noreferrer"
28
+ >
29
+ { __( 'Upgrade now', 'elementor' ) }
30
+ </AlertAction>
31
+ }
32
+ sx={ { m: 2, mt: 1 } }
33
+ >
34
+ <AlertTitle>{ title }</AlertTitle>
35
+ <Typography variant="caption">{ description }</Typography>
36
+ </Alert>
37
+ </Box>
38
+ );
39
+ }
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { closeDialog, ConfirmationDialog, openDialog } from '@elementor/editor-ui';
3
+ import { AlertTriangleFilledIcon } from '@elementor/icons';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ type DetachInstanceConfirmationDialogProps = {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ onConfirm: () => void;
10
+ };
11
+
12
+ export function DetachInstanceConfirmationDialog( {
13
+ open,
14
+ onClose,
15
+ onConfirm,
16
+ }: DetachInstanceConfirmationDialogProps ) {
17
+ return (
18
+ <ConfirmationDialog open={ open } onClose={ onClose }>
19
+ <ConfirmationDialog.Title icon={ AlertTriangleFilledIcon } iconColor="secondary">
20
+ { __( 'Detach from Component?', 'elementor' ) }
21
+ </ConfirmationDialog.Title>
22
+ <ConfirmationDialog.Content>
23
+ <ConfirmationDialog.ContentText>
24
+ { __(
25
+ 'Detaching this instance will break its link to the Component. Changes to the Component will no longer apply. Continue?',
26
+ 'elementor'
27
+ ) }
28
+ </ConfirmationDialog.ContentText>
29
+ </ConfirmationDialog.Content>
30
+ <ConfirmationDialog.Actions
31
+ onClose={ onClose }
32
+ onConfirm={ onConfirm }
33
+ confirmLabel={ __( 'Detach', 'elementor' ) }
34
+ color="primary"
35
+ />
36
+ </ConfirmationDialog>
37
+ );
38
+ }
39
+
40
+ // Used imperatively from the context menu (Marionette view).
41
+ export function openDetachConfirmDialog( onConfirm: () => void ) {
42
+ const handleConfirm = () => {
43
+ closeDialog();
44
+ onConfirm();
45
+ };
46
+
47
+ openDialog( {
48
+ component: <DetachInstanceConfirmationDialog open onClose={ closeDialog } onConfirm={ handleConfirm } />,
49
+ } );
50
+ }
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { notify } from '@elementor/editor-notifications';
4
+ import { DetachIcon } from '@elementor/icons';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { type ExtendedWindow } from '../../types';
8
+ import { detachComponentInstance } from '../../utils/detach-component-instance';
9
+ import { DetachInstanceConfirmationDialog } from '../detach-instance-confirmation-dialog';
10
+ import { EditComponentAction } from './instance-panel-header';
11
+
12
+ export const DetachAction = ( {
13
+ componentInstanceId,
14
+ componentId,
15
+ }: {
16
+ componentInstanceId: string;
17
+ componentId: number;
18
+ } ) => {
19
+ const [ isDetachDialogOpen, setIsDetachDialogOpen ] = useState( false );
20
+
21
+ const handleDetachConfirm = async () => {
22
+ setIsDetachDialogOpen( false );
23
+
24
+ try {
25
+ await detachComponentInstance( {
26
+ instanceId: componentInstanceId,
27
+ componentId,
28
+ trackingInfo: getDetachTrackingInfo(),
29
+ } );
30
+ } catch {
31
+ notify( {
32
+ type: 'error',
33
+ message: __( 'Failed to detach component instance.', 'elementor' ),
34
+ id: 'detach-component-instance-failed',
35
+ } );
36
+ }
37
+ };
38
+
39
+ const handleDetachCancel = () => {
40
+ setIsDetachDialogOpen( false );
41
+ };
42
+
43
+ const handleDetachClick = () => {
44
+ setIsDetachDialogOpen( true );
45
+ };
46
+
47
+ const detachLabel = __( 'Detach from Component', 'elementor' );
48
+
49
+ return (
50
+ <>
51
+ <EditComponentAction label={ detachLabel } icon={ DetachIcon } onClick={ handleDetachClick } />
52
+ <DetachInstanceConfirmationDialog
53
+ open={ isDetachDialogOpen }
54
+ onClose={ handleDetachCancel }
55
+ onConfirm={ handleDetachConfirm }
56
+ />
57
+ </>
58
+ );
59
+ };
60
+
61
+ function getDetachTrackingInfo() {
62
+ const extendedWindow = window as unknown as ExtendedWindow;
63
+ const config = extendedWindow?.elementorCommon?.eventsManager?.config;
64
+
65
+ if ( ! config ) {
66
+ return {
67
+ location: '',
68
+ trigger: '',
69
+ };
70
+ }
71
+
72
+ return {
73
+ location: ( config.locations.components as Record< string, string > ).instanceEditingPanel,
74
+ trigger: config.triggers.click,
75
+ };
76
+ }
@@ -34,8 +34,15 @@ export const EmptyState = ( { onEditComponent }: { onEditComponent?: () => void
34
34
  <Typography align="center" variant="caption" maxWidth="170px">
35
35
  { message }
36
36
  </Typography>
37
- { canEdit && !! onEditComponent && (
38
- <Button variant="outlined" color="secondary" size="small" sx={ { mt: 1 } } onClick={ onEditComponent }>
37
+ { canEdit && (
38
+ <Button
39
+ variant="outlined"
40
+ color="secondary"
41
+ size="small"
42
+ sx={ { mt: 1 } }
43
+ disabled={ ! onEditComponent }
44
+ onClick={ onEditComponent }
45
+ >
39
46
  <PencilIcon fontSize="small" />
40
47
  { __( 'Edit component', 'elementor' ) }
41
48
  </Button>
@@ -1,15 +1,23 @@
1
1
  import * as React from 'react';
2
2
  import { PencilIcon } from '@elementor/icons';
3
- import { Box } from '@elementor/ui';
3
+ import { Box, Stack } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
+ import { useComponentsPermissions } from '../../hooks/use-components-permissions';
6
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';
10
+ import { ComponentsUpgradeAlert } from '../components-upgrade-alert';
11
+ import { DetachAction } from './detach-action';
7
12
  import { EmptyState } from './empty-state';
8
13
  import { InstancePanelBody } from './instance-panel-body';
9
14
  import { EditComponentAction, InstancePanelHeader } from './instance-panel-header';
10
15
  import { useInstancePanelData } from './use-instance-panel-data';
11
16
 
17
+ const EDIT_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-Instance-edit-footer/';
18
+
12
19
  export function InstanceEditingPanel() {
20
+ const { canEdit } = useComponentsPermissions();
13
21
  const data = useInstancePanelData();
14
22
 
15
23
  if ( ! data ) {
@@ -21,17 +29,21 @@ export function InstanceEditingPanel() {
21
29
  /* translators: %s: component name. */
22
30
  const panelTitle = __( 'Edit %s', 'elementor' ).replace( '%s', component.name );
23
31
 
32
+ const actions = (
33
+ <Stack direction="row" gap={ 0.5 }>
34
+ <DetachAction componentInstanceId={ componentInstanceId } componentId={ componentId } />
35
+ { canEdit && <EditComponentAction disabled label={ panelTitle } icon={ PencilIcon } /> }
36
+ </Stack>
37
+ );
38
+
24
39
  return (
25
- <Box data-testid="instance-editing-panel">
40
+ <Box data-testid="instance-editing-panel" sx={ { display: 'flex', flexDirection: 'column', height: '100%' } }>
26
41
  <ComponentInstanceProvider
27
42
  componentId={ componentId }
28
43
  overrides={ overrides }
29
44
  overridableProps={ overridableProps }
30
45
  >
31
- <InstancePanelHeader
32
- componentName={ component.name }
33
- actions={ <EditComponentAction disabled label={ panelTitle } icon={ PencilIcon } /> }
34
- />
46
+ <InstancePanelHeader componentName={ component.name } actions={ actions } />
35
47
  <InstancePanelBody
36
48
  groups={ groups }
37
49
  isEmpty={ isEmpty }
@@ -39,6 +51,22 @@ export function InstanceEditingPanel() {
39
51
  componentInstanceId={ componentInstanceId }
40
52
  />
41
53
  </ComponentInstanceProvider>
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
+ ) ) }
42
70
  </Box>
43
71
  );
44
72
  }