@elementor/editor-components 3.35.0-399 → 3.35.0-400

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": "3.35.0-399",
4
+ "version": "3.35.0-400",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,30 +40,30 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor": "3.35.0-399",
44
- "@elementor/editor-canvas": "3.35.0-399",
45
- "@elementor/editor-controls": "3.35.0-399",
46
- "@elementor/editor-documents": "3.35.0-399",
47
- "@elementor/editor-editing-panel": "3.35.0-399",
48
- "@elementor/editor-elements": "3.35.0-399",
49
- "@elementor/editor-elements-panel": "3.35.0-399",
50
- "@elementor/editor-mcp": "3.35.0-399",
51
- "@elementor/editor-panels": "3.35.0-399",
52
- "@elementor/editor-props": "3.35.0-399",
53
- "@elementor/editor-styles-repository": "3.35.0-399",
54
- "@elementor/editor-ui": "3.35.0-399",
55
- "@elementor/editor-v1-adapters": "3.35.0-399",
56
- "@elementor/http-client": "3.35.0-399",
43
+ "@elementor/editor": "3.35.0-400",
44
+ "@elementor/editor-canvas": "3.35.0-400",
45
+ "@elementor/editor-controls": "3.35.0-400",
46
+ "@elementor/editor-documents": "3.35.0-400",
47
+ "@elementor/editor-editing-panel": "3.35.0-400",
48
+ "@elementor/editor-elements": "3.35.0-400",
49
+ "@elementor/editor-elements-panel": "3.35.0-400",
50
+ "@elementor/editor-mcp": "3.35.0-400",
51
+ "@elementor/editor-panels": "3.35.0-400",
52
+ "@elementor/editor-props": "3.35.0-400",
53
+ "@elementor/editor-styles-repository": "3.35.0-400",
54
+ "@elementor/editor-ui": "3.35.0-400",
55
+ "@elementor/editor-v1-adapters": "3.35.0-400",
56
+ "@elementor/http-client": "3.35.0-400",
57
57
  "@elementor/icons": "^1.63.0",
58
- "@elementor/mixpanel": "3.35.0-399",
59
- "@elementor/query": "3.35.0-399",
60
- "@elementor/schema": "3.35.0-399",
61
- "@elementor/store": "3.35.0-399",
58
+ "@elementor/mixpanel": "3.35.0-400",
59
+ "@elementor/query": "3.35.0-400",
60
+ "@elementor/schema": "3.35.0-400",
61
+ "@elementor/store": "3.35.0-400",
62
62
  "@elementor/ui": "1.36.17",
63
- "@elementor/utils": "3.35.0-399",
63
+ "@elementor/utils": "3.35.0-400",
64
64
  "@wordpress/i18n": "^5.13.0",
65
- "@elementor/editor-notifications": "3.35.0-399",
66
- "@elementor/editor-current-user": "3.35.0-399"
65
+ "@elementor/editor-notifications": "3.35.0-400",
66
+ "@elementor/editor-current-user": "3.35.0-400"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "react": "^18.3.1",
@@ -1,12 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { getElementLabel, type V1ElementData } from '@elementor/editor-elements';
4
+ import { notify } from '@elementor/editor-notifications';
4
5
  import { Form as FormElement, ThemeProvider } from '@elementor/editor-ui';
5
6
  import { StarIcon } from '@elementor/icons';
6
7
  import { Alert, Button, FormLabel, Grid, Popover, Snackbar, Stack, TextField, Typography } from '@elementor/ui';
7
8
  import { __ } from '@wordpress/i18n';
8
9
 
9
10
  import { useComponents } from '../../hooks/use-components';
11
+ import { findNonAtomicElementsInElement } from '../../prevent-non-atomic-nesting';
10
12
  import { createUnpublishedComponent } from '../../store/actions/create-unpublished-component';
11
13
  import { type ComponentFormValues } from '../../types';
12
14
  import { trackComponentEvent } from '../../utils/tracking';
@@ -46,6 +48,20 @@ export function CreateComponentForm() {
46
48
  const OPEN_SAVE_AS_COMPONENT_FORM_EVENT = 'elementor/editor/open-save-as-component-form';
47
49
 
48
50
  const openPopup = ( event: CustomEvent< SaveAsComponentEventData > ) => {
51
+ const nonAtomicElements = findNonAtomicElementsInElement( event.detail.element );
52
+
53
+ if ( nonAtomicElements.length > 0 ) {
54
+ notify( {
55
+ type: 'default',
56
+ message: __(
57
+ 'Cannot save as component - contains non-supported elements. Only atomic elements are allowed inside components.',
58
+ 'elementor'
59
+ ),
60
+ id: 'non-atomic-element-save-blocked',
61
+ } );
62
+ return;
63
+ }
64
+
49
65
  setElement( { element: event.detail.element, elementLabel: getElementLabel( event.detail.element.id ) } );
50
66
  setAnchorPosition( event.detail.anchorPosition );
51
67
 
package/src/init.ts CHANGED
@@ -38,6 +38,7 @@ import { initRegenerateOverrideKeys } from './hooks/regenerate-override-keys';
38
38
  import { initMcp } from './mcp';
39
39
  import { PopulateStore } from './populate-store';
40
40
  import { initCircularNestingPrevention } from './prevent-circular-nesting';
41
+ import { initNonAtomicNestingPrevention } from './prevent-non-atomic-nesting';
41
42
  import { componentOverridablePropTypeUtil } from './prop-types/component-overridable-prop-type';
42
43
  import { loadComponentsAssets } from './store/actions/load-components-assets';
43
44
  import { removeComponentStyles } from './store/actions/remove-component-styles';
@@ -132,4 +133,6 @@ export function init() {
132
133
  initMcp();
133
134
 
134
135
  initCircularNestingPrevention();
136
+
137
+ initNonAtomicNestingPrevention();
135
138
  }
@@ -0,0 +1,209 @@
1
+ import { isAtomicWidget } from '@elementor/editor-canvas';
2
+ import { getAllDescendants, getElementType, type V1Element } from '@elementor/editor-elements';
3
+ import { type NotificationData, notify } from '@elementor/editor-notifications';
4
+ import { blockCommand } from '@elementor/editor-v1-adapters';
5
+ import { __getStore as getStore } from '@elementor/store';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { type ComponentsSlice, selectCurrentComponentId } from './store/store';
9
+ import { type ExtendedWindow } from './types';
10
+
11
+ type CreateArgs = {
12
+ container?: V1Element;
13
+ model?: {
14
+ elType?: string;
15
+ widgetType?: string;
16
+ };
17
+ };
18
+
19
+ type MoveArgs = {
20
+ containers?: V1Element[];
21
+ container?: V1Element;
22
+ target?: V1Element;
23
+ };
24
+
25
+ type PasteArgs = {
26
+ containers?: V1Element[];
27
+ container?: V1Element;
28
+ storageType?: string;
29
+ };
30
+
31
+ export type ClipboardElement = {
32
+ elType?: string;
33
+ widgetType?: string;
34
+ elements?: ClipboardElement[];
35
+ };
36
+
37
+ type StorageContent = {
38
+ clipboard?: {
39
+ elements?: ClipboardElement[];
40
+ };
41
+ };
42
+
43
+ const NON_ATOMIC_ELEMENT_ALERT: NotificationData = {
44
+ type: 'default',
45
+ message: __( 'Cannot add this element here - only atomic elements are allowed inside components.', 'elementor' ),
46
+ id: 'non-atomic-element-blocked',
47
+ };
48
+
49
+ export function initNonAtomicNestingPrevention() {
50
+ blockCommand( {
51
+ command: 'document/elements/create',
52
+ condition: blockNonAtomicCreate,
53
+ } );
54
+
55
+ blockCommand( {
56
+ command: 'document/elements/move',
57
+ condition: blockNonAtomicMove,
58
+ } );
59
+
60
+ blockCommand( {
61
+ command: 'document/elements/paste',
62
+ condition: blockNonAtomicPaste,
63
+ } );
64
+ }
65
+
66
+ function isEditingComponent(): boolean {
67
+ const state = getStore()?.getState() as ComponentsSlice | undefined;
68
+
69
+ if ( ! state ) {
70
+ return false;
71
+ }
72
+
73
+ return selectCurrentComponentId( state ) !== null;
74
+ }
75
+
76
+ export function isElementAtomic( elementType: string ): boolean {
77
+ return getElementType( elementType ) !== null;
78
+ }
79
+
80
+ function blockNonAtomicCreate( args: CreateArgs ): boolean {
81
+ if ( ! isEditingComponent() ) {
82
+ return false;
83
+ }
84
+
85
+ const { model } = args;
86
+ const elementType = model?.widgetType || model?.elType;
87
+
88
+ if ( ! elementType ) {
89
+ return false;
90
+ }
91
+
92
+ if ( isElementAtomic( elementType ) ) {
93
+ return false;
94
+ }
95
+
96
+ notify( NON_ATOMIC_ELEMENT_ALERT );
97
+ return true;
98
+ }
99
+
100
+ function blockNonAtomicMove( args: MoveArgs ): boolean {
101
+ if ( ! isEditingComponent() ) {
102
+ return false;
103
+ }
104
+
105
+ const { containers = [ args.container ] } = args;
106
+
107
+ const hasNonAtomicElement = containers.some( ( container ) => {
108
+ if ( ! container ) {
109
+ return false;
110
+ }
111
+
112
+ const allElements = getAllDescendants( container );
113
+
114
+ return allElements.some( ( element ) => ! isAtomicWidget( element ) );
115
+ } );
116
+
117
+ if ( hasNonAtomicElement ) {
118
+ notify( NON_ATOMIC_ELEMENT_ALERT );
119
+ }
120
+
121
+ return hasNonAtomicElement;
122
+ }
123
+
124
+ function blockNonAtomicPaste( args: PasteArgs ): boolean {
125
+ if ( ! isEditingComponent() ) {
126
+ return false;
127
+ }
128
+
129
+ const { storageType } = args;
130
+
131
+ if ( storageType !== 'localstorage' ) {
132
+ return false;
133
+ }
134
+
135
+ const data = (
136
+ window as unknown as ExtendedWindow & { elementorCommon?: { storage?: { get: () => StorageContent } } }
137
+ )?.elementorCommon?.storage?.get();
138
+
139
+ if ( ! data?.clipboard?.elements ) {
140
+ return false;
141
+ }
142
+
143
+ const hasNonAtomicElement = hasNonAtomicElementsInTree( data.clipboard.elements );
144
+
145
+ if ( hasNonAtomicElement ) {
146
+ notify( NON_ATOMIC_ELEMENT_ALERT );
147
+ }
148
+
149
+ return hasNonAtomicElement;
150
+ }
151
+
152
+ export function hasNonAtomicElementsInTree( elements: ClipboardElement[] ): boolean {
153
+ for ( const element of elements ) {
154
+ const elementType = element.widgetType || element.elType;
155
+
156
+ if ( elementType && ! isElementAtomic( elementType ) ) {
157
+ return true;
158
+ }
159
+
160
+ if ( element.elements?.length ) {
161
+ if ( hasNonAtomicElementsInTree( element.elements ) ) {
162
+ return true;
163
+ }
164
+ }
165
+ }
166
+
167
+ return false;
168
+ }
169
+
170
+ export function findNonAtomicElements( elements: ClipboardElement[] ): string[] {
171
+ const nonAtomicElements: string[] = [];
172
+
173
+ for ( const element of elements ) {
174
+ const elementType = element.widgetType || element.elType;
175
+
176
+ if ( elementType && ! isElementAtomic( elementType ) ) {
177
+ nonAtomicElements.push( elementType );
178
+ }
179
+
180
+ if ( element.elements?.length ) {
181
+ nonAtomicElements.push( ...findNonAtomicElements( element.elements ) );
182
+ }
183
+ }
184
+
185
+ return [ ...new Set( nonAtomicElements ) ];
186
+ }
187
+
188
+ type V1ElementLike = {
189
+ elType?: string;
190
+ widgetType?: string;
191
+ elements?: V1ElementLike[];
192
+ };
193
+
194
+ export function findNonAtomicElementsInElement( element: V1ElementLike ): string[] {
195
+ const nonAtomicElements: string[] = [];
196
+ const elementType = element.widgetType || element.elType;
197
+
198
+ if ( elementType && ! isElementAtomic( elementType ) ) {
199
+ nonAtomicElements.push( elementType );
200
+ }
201
+
202
+ if ( element.elements?.length ) {
203
+ for ( const child of element.elements ) {
204
+ nonAtomicElements.push( ...findNonAtomicElementsInElement( child ) );
205
+ }
206
+ }
207
+
208
+ return [ ...new Set( nonAtomicElements ) ];
209
+ }