@elementor/editor-components 4.3.0-999 → 4.3.0-beta2

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.3.0-999",
4
+ "version": "4.3.0-beta2",
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.3.0-999",
44
- "@elementor/editor-canvas": "4.3.0-999",
45
- "@elementor/editor-controls": "4.3.0-999",
46
- "@elementor/editor-documents": "4.3.0-999",
47
- "@elementor/editor-editing-panel": "4.3.0-999",
48
- "@elementor/editor-elements": "4.3.0-999",
49
- "@elementor/editor-elements-panel": "4.3.0-999",
50
- "@elementor/editor-mcp": "4.3.0-999",
51
- "@elementor/editor-templates": "4.3.0-999",
52
- "@elementor/editor-panels": "4.3.0-999",
53
- "@elementor/editor-props": "4.3.0-999",
54
- "@elementor/editor-ui": "4.3.0-999",
55
- "@elementor/editor-v1-adapters": "4.3.0-999",
56
- "@elementor/http-client": "4.3.0-999",
43
+ "@elementor/editor": "4.3.0-beta2",
44
+ "@elementor/editor-canvas": "4.3.0-beta2",
45
+ "@elementor/editor-controls": "4.3.0-beta2",
46
+ "@elementor/editor-documents": "4.3.0-beta2",
47
+ "@elementor/editor-editing-panel": "4.3.0-beta2",
48
+ "@elementor/editor-elements": "4.3.0-beta2",
49
+ "@elementor/editor-elements-panel": "4.3.0-beta2",
50
+ "@elementor/editor-mcp": "4.3.0-beta2",
51
+ "@elementor/editor-templates": "4.3.0-beta2",
52
+ "@elementor/editor-panels": "4.3.0-beta2",
53
+ "@elementor/editor-props": "4.3.0-beta2",
54
+ "@elementor/editor-ui": "4.3.0-beta2",
55
+ "@elementor/editor-v1-adapters": "4.3.0-beta2",
56
+ "@elementor/http-client": "4.3.0-beta2",
57
57
  "@elementor/icons": "~1.75.1",
58
- "@elementor/events": "4.3.0-999",
59
- "@elementor/query": "4.3.0-999",
60
- "@elementor/schema": "4.3.0-999",
61
- "@elementor/store": "4.3.0-999",
58
+ "@elementor/events": "4.3.0-beta2",
59
+ "@elementor/query": "4.3.0-beta2",
60
+ "@elementor/schema": "4.3.0-beta2",
61
+ "@elementor/store": "4.3.0-beta2",
62
62
  "@elementor/ui": "1.37.5",
63
- "@elementor/utils": "4.3.0-999",
63
+ "@elementor/utils": "4.3.0-beta2",
64
64
  "@wordpress/i18n": "^5.13.0",
65
- "@elementor/editor-notifications": "4.3.0-999",
66
- "@elementor/editor-current-user": "4.3.0-999",
67
- "@elementor/editor-embedded-documents-manager": "4.3.0-999"
65
+ "@elementor/editor-notifications": "4.3.0-beta2",
66
+ "@elementor/editor-current-user": "4.3.0-beta2",
67
+ "@elementor/editor-embedded-documents-manager": "4.3.0-beta2"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "react": "^18.3.1",
@@ -7,24 +7,36 @@ export const componentOverridableTransformer = createTransformer(
7
7
  ( value: ComponentOverridable, options: TransformerOptions ) => {
8
8
  const { overrides } = options.renderContext ?? {};
9
9
 
10
+ const originValue = normalizeOriginValue( value.origin_value );
10
11
  const overrideValue = overrides?.[ value.override_key as keyof typeof overrides ];
11
12
 
12
13
  if ( overrideValue ) {
13
- const isOverride = isOriginValueOverride( value.origin_value );
14
-
15
- if ( isOverride ) {
16
- return transformOverride( value, options, overrideValue );
14
+ if ( isOriginValueOverride( originValue ) ) {
15
+ return transformOverride( originValue, options, overrideValue );
17
16
  }
18
17
 
19
18
  return overrideValue;
20
19
  }
21
20
 
22
- return value.origin_value;
21
+ return originValue;
23
22
  }
24
23
  );
25
24
 
25
+ /**
26
+ * Mirrors `Overridable_Prop_Type::normalize_origin_value()` on the PHP side. Rendering resolves
27
+ * props without validating them, so an empty origin value written by an older editor reaches this
28
+ * transformer untouched until the document is next saved.
29
+ *
30
+ * @param originValue The origin_value nested on a component-overridable prop.
31
+ */
32
+ function normalizeOriginValue( originValue: ComponentOverridable[ 'origin_value' ] ) {
33
+ const isEmpty = !! originValue && Object.keys( originValue ).length === 0;
34
+
35
+ return isEmpty ? null : originValue;
36
+ }
37
+
26
38
  function transformOverride(
27
- value: ComponentOverridable,
39
+ originValue: TransformablePropValue< string >,
28
40
  options: {
29
41
  key: string;
30
42
  },
@@ -36,7 +48,7 @@ function transformOverride(
36
48
  return null;
37
49
  }
38
50
 
39
- const transformedValue = transformer( value.origin_value.value, options );
51
+ const transformedValue = transformer( originValue.value, options );
40
52
 
41
53
  if ( ! transformedValue ) {
42
54
  return null;
@@ -49,6 +61,8 @@ function transformOverride(
49
61
  };
50
62
  }
51
63
 
52
- function isOriginValueOverride( originValue: TransformablePropValue< string > ): boolean {
53
- return originValue.$$type === 'override';
64
+ function isOriginValueOverride(
65
+ originValue: TransformablePropValue< string > | null
66
+ ): originValue is TransformablePropValue< string > {
67
+ return originValue?.$$type === 'override';
54
68
  }
@@ -26,6 +26,7 @@ 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
28
  import { isProComponentsSupported, isProOutdatedForComponents } from './utils/is-pro-components-supported';
29
+ import { reconcileComponentInstanceElements } from './utils/reconcile-component-instance-elements';
29
30
  import { switchToComponent } from './utils/switch-to-component';
30
31
  import { trackComponentEvent } from './utils/tracking';
31
32
 
@@ -219,7 +220,11 @@ function createComponentView( options: ComponentTypeOptions ): typeof TemplatedE
219
220
 
220
221
  const instanceId = this.model.get( 'id' );
221
222
  const elements = componentInstance.elements ?? [];
222
- const formattedElements = formatComponentElementsId( elements, [ instanceId ] );
223
+ const reconciledElements = reconcileComponentInstanceElements(
224
+ elements,
225
+ componentInstance.overrides ?? {}
226
+ );
227
+ const formattedElements = formatComponentElementsId( reconciledElements, [ instanceId ] );
223
228
 
224
229
  this.collection = legacyWindow.elementor.createBackboneElementsCollection( formattedElements );
225
230
 
package/src/types.ts CHANGED
@@ -89,7 +89,7 @@ export type ExtendedWindow = Window & {
89
89
 
90
90
  export type ComponentOverridable = {
91
91
  override_key: string;
92
- origin_value: TransformablePropValue< string >;
92
+ origin_value: TransformablePropValue< string > | null;
93
93
  };
94
94
 
95
95
  export type ComponentRenderContext = RenderContext< {
@@ -1,13 +1,30 @@
1
- import { type Document, getV1DocumentsManager } from '@elementor/editor-documents';
1
+ import { type Document } from '@elementor/editor-documents';
2
+ import { ajax } from '@elementor/editor-v1-adapters';
2
3
 
3
- type ComponentDocumentData = Document;
4
+ // Mirrors the v1 documents manager request args so both share one ajax cache entry, keeping
5
+ // `elementor.documents.invalidateCache()` effective. Unlike the manager, this omits `immediately`,
6
+ // so component fetches triggered together merge into a single batched request.
7
+ const getComponentDocumentParams = ( id: number ) => ( {
8
+ action: 'get_document_config',
9
+ unique_id: `document-${ id }`,
10
+ data: { id },
11
+ } );
4
12
 
5
- export const getComponentDocumentData = async ( id: number ) => {
6
- const documentManager = getV1DocumentsManager();
13
+ const pendingRequests = new Map< number, Promise< Document | null > >();
7
14
 
8
- try {
9
- return await documentManager.request< ComponentDocumentData >( id );
10
- } catch {
11
- return null;
15
+ export const getComponentDocumentData = ( id: number ): Promise< Document | null > => {
16
+ const pendingRequest = pendingRequests.get( id );
17
+
18
+ if ( pendingRequest ) {
19
+ return pendingRequest;
12
20
  }
21
+
22
+ const request = ajax
23
+ .load< { id: number }, Document >( getComponentDocumentParams( id ) )
24
+ .catch( () => null )
25
+ .finally( () => pendingRequests.delete( id ) );
26
+
27
+ pendingRequests.set( id, request );
28
+
29
+ return request;
13
30
  };
@@ -0,0 +1,107 @@
1
+ import {
2
+ getWidgetsCache,
3
+ reconcileInitialChildren,
4
+ type V1ElementConfig,
5
+ type V1ElementData,
6
+ type V1ElementSettingsProps,
7
+ } from '@elementor/editor-elements';
8
+ import { type AnyTransformable } from '@elementor/editor-props';
9
+ import { hashString } from '@elementor/utils';
10
+
11
+ import {
12
+ applyOverridesToSettings,
13
+ type ElementSettings,
14
+ type OverridesMapping,
15
+ unwrapOverridableSettings,
16
+ } from '../components/instance-editing-panel/utils/resolve-element-settings';
17
+
18
+ const ELEMENT_ID_LENGTH = 7;
19
+
20
+ function overridesRecordToMapping( overrides: Record< string, unknown > ): OverridesMapping {
21
+ const mapping: OverridesMapping = {};
22
+
23
+ for ( const [ key, value ] of Object.entries( overrides ) ) {
24
+ mapping[ key ] = { value: value as AnyTransformable };
25
+ }
26
+
27
+ return mapping;
28
+ }
29
+
30
+ function getElementConfig( element: V1ElementData ): V1ElementConfig | undefined {
31
+ const type = element.elType === 'widget' ? element.widgetType : element.elType;
32
+
33
+ if ( ! type ) {
34
+ return undefined;
35
+ }
36
+
37
+ return getWidgetsCache()?.[ type ];
38
+ }
39
+
40
+ /**
41
+ * Children inserted by dependency rules come from static `default_model` config, so
42
+ * `reconcileInitialChildren` gives them random ids. Replace them with ids derived from the
43
+ * parent id and the position in the subtree, so the instance-scoped hashing applied later by
44
+ * `formatComponentElementsId` is stable across renders. Keep in sync with `derive_ids()` in
45
+ * `modules/components/utils/reconcile-component-instance-elements.php`.
46
+ *
47
+ * Instance children are not directly editable, so overwriting the ids of a model that
48
+ * `reconcileInitialChildren` restored from the stash is safe and keeps the canvas aligned
49
+ * with the render, which has no stash to restore from.
50
+ *
51
+ * @param element The inserted element to assign ids to, recursively.
52
+ * @param seed Stable string the element's id is hashed from.
53
+ */
54
+ function deriveIds( element: V1ElementData, seed: string ): V1ElementData {
55
+ const id = hashString( seed, ELEMENT_ID_LENGTH );
56
+
57
+ return {
58
+ ...element,
59
+ id,
60
+ elements: ( element.elements ?? [] ).map( ( child, index ) => deriveIds( child, `${ id }_${ index }` ) ),
61
+ };
62
+ }
63
+
64
+ function reconcileElementTree( element: V1ElementData, overridesMapping: OverridesMapping ): V1ElementData {
65
+ const elementConfig = getElementConfig( element );
66
+ const withOverrides = applyOverridesToSettings( ( element.settings ?? {} ) as ElementSettings, overridesMapping );
67
+ const effectiveSettings = unwrapOverridableSettings( withOverrides ) as V1ElementSettingsProps;
68
+
69
+ const originalChildren = element.elements ?? [];
70
+ const originalChildIds = new Set( originalChildren.map( ( child ) => child.id ) );
71
+
72
+ const attributes: {
73
+ elements?: V1ElementData[];
74
+ settings?: V1ElementSettingsProps;
75
+ } = {
76
+ elements: [ ...originalChildren ],
77
+ settings: effectiveSettings,
78
+ };
79
+
80
+ if ( elementConfig?.children_dependencies?.length ) {
81
+ reconcileInitialChildren( {
82
+ elementId: element.id,
83
+ elementConfig,
84
+ attributes,
85
+ } );
86
+ }
87
+
88
+ const reconciledChildren = ( attributes.elements ?? [] )
89
+ .map( ( child ) =>
90
+ originalChildIds.has( child.id ) ? child : deriveIds( child, `${ element.id }_${ child.elType }` )
91
+ )
92
+ .map( ( child ) => reconcileElementTree( child, overridesMapping ) );
93
+
94
+ return {
95
+ ...element,
96
+ elements: reconciledChildren,
97
+ };
98
+ }
99
+
100
+ export function reconcileComponentInstanceElements(
101
+ elements: V1ElementData[],
102
+ overrides: Record< string, unknown > = {}
103
+ ): V1ElementData[] {
104
+ const overridesMapping = overridesRecordToMapping( overrides );
105
+
106
+ return elements.map( ( element ) => reconcileElementTree( element, overridesMapping ) );
107
+ }