@elementor/editor-elements 4.2.0-beta2 → 4.3.0-1000

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.
@@ -0,0 +1,4 @@
1
+ export { bindSettingsReconcile } from './bind-settings-reconcile';
2
+ export { evaluateWhen } from './utils';
3
+ export { reconcileInitialChildren } from './reconcile-initial-children';
4
+ export type { ChildDependenciesConfig, ChildDependencyRule } from './types';
@@ -0,0 +1,59 @@
1
+ import { resolveInsertIndex } from '../sync/resolve-insert-index';
2
+ import { type V1ElementConfig, type V1ElementData, type V1ElementSettingsProps } from '../sync/types';
3
+ import { createChildrenStash } from './stash';
4
+ import { type ChildDependencyRule } from './types';
5
+ import { evaluateWhen, resolveChildModelData } from './utils';
6
+
7
+ type ReconcileInitialChildrenArgs = {
8
+ elementId: string;
9
+ elementConfig: V1ElementConfig | undefined;
10
+ attributes: {
11
+ elements?: V1ElementData[];
12
+ settings?: V1ElementSettingsProps;
13
+ };
14
+ };
15
+
16
+ export function reconcileInitialChildren( {
17
+ elementId,
18
+ elementConfig,
19
+ attributes,
20
+ }: ReconcileInitialChildrenArgs ): void {
21
+ const stash = createChildrenStash();
22
+ const rules = elementConfig?.children_dependencies;
23
+
24
+ if ( ! rules?.length ) {
25
+ return;
26
+ }
27
+
28
+ const elements = [ ...( attributes.elements ?? [] ) ];
29
+ const settings = attributes.settings ?? {};
30
+
31
+ rules.forEach( ( rule: ChildDependencyRule ) => {
32
+ const isMet = evaluateWhen( rule.when, settings );
33
+ const existingIndex = elements.findIndex( ( element ) => element.elType === rule.child_type );
34
+ const isPresent = existingIndex >= 0;
35
+
36
+ if ( isMet && ! isPresent ) {
37
+ const { modelData, wasStashed } = resolveChildModelData( elementId, rule, stash );
38
+ const insertAt = resolveInsertIndex( rule.position, elements );
39
+
40
+ elements.splice( insertAt, 0, modelData );
41
+
42
+ if ( wasStashed ) {
43
+ stash.clear( elementId, rule.child_type );
44
+ }
45
+
46
+ return;
47
+ }
48
+
49
+ if ( ! isMet && isPresent ) {
50
+ const [ removed ] = elements.splice( existingIndex, 1 );
51
+
52
+ if ( rule.stash && removed ) {
53
+ stash.save( elementId, rule.child_type, removed );
54
+ }
55
+ }
56
+ } );
57
+
58
+ attributes.elements = elements;
59
+ }
@@ -0,0 +1,40 @@
1
+ import { getSessionStorageItem, removeSessionStorageItem, setSessionStorageItem } from '@elementor/session';
2
+
3
+ import { type V1ElementData } from '../sync/types';
4
+ import { type ChildrenStash } from './types';
5
+
6
+ const STASH_KEY_PREFIX = 'elementor/editor-state';
7
+ const STASH_KEY_SEGMENT = 'children-deps';
8
+
9
+ export function createChildrenStash(): ChildrenStash {
10
+ return {
11
+ get( elementId, childType ) {
12
+ return getSessionStorageItem< V1ElementData >( buildStashKey( elementId, childType ) );
13
+ },
14
+ save( elementId, childType, data ) {
15
+ setSessionStorageItem( buildStashKey( elementId, childType ), data );
16
+ },
17
+ clear( elementId, childType ) {
18
+ removeSessionStorageItem( buildStashKey( elementId, childType ) );
19
+ },
20
+ clearAllForElement( elementId ) {
21
+ const prefix = buildElementStashPrefix( elementId );
22
+
23
+ for ( let index = sessionStorage.length - 1; index >= 0; index-- ) {
24
+ const key = sessionStorage.key( index );
25
+
26
+ if ( key?.startsWith( prefix ) ) {
27
+ removeSessionStorageItem( key );
28
+ }
29
+ }
30
+ },
31
+ };
32
+ }
33
+
34
+ function buildStashKey( elementId: string, childType: string ): string {
35
+ return `${ STASH_KEY_PREFIX }/${ elementId }/${ STASH_KEY_SEGMENT }/${ childType }`;
36
+ }
37
+
38
+ function buildElementStashPrefix( elementId: string ): string {
39
+ return `${ STASH_KEY_PREFIX }/${ elementId }/${ STASH_KEY_SEGMENT }/`;
40
+ }
@@ -0,0 +1,20 @@
1
+ import { type Dependency } from '@elementor/editor-props';
2
+
3
+ import { type ElementPosition, type V1ElementData } from '../sync/types';
4
+
5
+ export type ChildDependencyRule = {
6
+ child_type: string;
7
+ when: Dependency;
8
+ position: ElementPosition;
9
+ stash: boolean;
10
+ default_model: V1ElementData | null;
11
+ };
12
+
13
+ export type ChildDependenciesConfig = ChildDependencyRule[];
14
+
15
+ export type ChildrenStash = {
16
+ get: ( elementId: string, childType: string ) => V1ElementData | undefined;
17
+ save: ( elementId: string, childType: string, data: V1ElementData ) => void;
18
+ clear: ( elementId: string, childType: string ) => void;
19
+ clearAllForElement: ( elementId: string ) => void;
20
+ };
@@ -0,0 +1,44 @@
1
+ import { type Dependency, isDependencyMet, type PropValue } from '@elementor/editor-props';
2
+
3
+ import { generateElementId } from '../sync/generate-element-id';
4
+ import { type V1ElementData } from '../sync/types';
5
+ import { type ChildDependencyRule, type ChildrenStash } from './types';
6
+
7
+ export function evaluateWhen( when: Dependency | undefined, settings: Record< string, PropValue > ): boolean {
8
+ return isDependencyMet( when, settings ).isMet;
9
+ }
10
+
11
+ export function ensureModelId( model: V1ElementData ): V1ElementData {
12
+ const { skipDefaultChildren: _skipDefaultChildren, ...rest } = model;
13
+
14
+ return rest.id ? rest : { ...rest, id: generateElementId() };
15
+ }
16
+
17
+ type ResolvedChildModel = {
18
+ modelData: V1ElementData;
19
+ wasStashed: boolean;
20
+ };
21
+
22
+ /**
23
+ * Resolve the model to attach for a child-dependency rule, preferring a previously-stashed
24
+ * (user-customized) model, then the rule's default, else a bare element.
25
+ *
26
+ * `default_model` and the bare fallback never carry an id, so they're run through
27
+ * `ensureModelId` before the model reaches Backbone.
28
+ *
29
+ * @param elementId Parent element id, used as the stash lookup key.
30
+ * @param rule The child-dependency rule being resolved.
31
+ * @param stash Session-backed stash for previously-detached child models.
32
+ */
33
+ export function resolveChildModelData(
34
+ elementId: string,
35
+ rule: ChildDependencyRule,
36
+ stash: ChildrenStash
37
+ ): ResolvedChildModel {
38
+ const stashed = rule.stash ? stash.get( elementId, rule.child_type ) : undefined;
39
+ const modelData = ensureModelId(
40
+ stashed ?? rule.default_model ?? ( { elType: rule.child_type } as V1ElementData )
41
+ );
42
+
43
+ return { modelData, wasStashed: Boolean( stashed ) };
44
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,15 @@
2
2
  export * from './types';
3
3
  export type * from './sync/types';
4
4
 
5
+ // children dependencies (schema-driven settings <-> children sync)
6
+ export {
7
+ bindSettingsReconcile,
8
+ type ChildDependenciesConfig,
9
+ type ChildDependencyRule,
10
+ evaluateWhen,
11
+ reconcileInitialChildren,
12
+ } from './children-dependencies';
13
+
5
14
  // hooks
6
15
  export { useElementChildren, type ElementChildren, type ElementModel } from './hooks/use-element-children';
7
16
  export { useElementEditorSettings } from './hooks/use-element-editor-settings';
@@ -27,7 +36,9 @@ export { addModelToParent, findModelInDocument, removeModelFromParent, resolveCo
27
36
  export { getCurrentDocumentContainer } from './sync/get-current-document-container';
28
37
  export { getCurrentDocumentId } from './sync/get-current-document-id';
29
38
  export { getElementEditorSettings } from './sync/get-element-editor-settings';
39
+ export { getElementIcon } from './sync/get-element-icon';
30
40
  export { getElementLabel } from './sync/get-element-label';
41
+ export { getElementTitle } from './sync/get-element-title';
31
42
  export { getElementSetting, getElementSettings } from './sync/get-element-setting';
32
43
  export { getElementStyles } from './sync/get-element-styles';
33
44
  export { getElementType } from './sync/get-element-type';
@@ -44,6 +55,7 @@ export { moveElement, type MoveElementParams } from './sync/move-element';
44
55
  export { moveElements } from './sync/move-elements';
45
56
  export { removeElements } from './sync/remove-elements';
46
57
  export { replaceElement } from './sync/replace-element';
58
+ export { resolveInsertIndex } from './sync/resolve-insert-index';
47
59
  export { updateElementEditorSettings } from './sync/update-element-editor-settings';
48
60
  export { updateElementSettings, type UpdateElementSettingsArgs } from './sync/update-element-settings';
49
61
 
@@ -0,0 +1,14 @@
1
+ import { type ElementID } from '../types';
2
+ import { getContainer } from './get-container';
3
+ import { getWidgetsCache } from './get-widgets-cache';
4
+
5
+ export function getElementIcon( elementId: ElementID ): string | null {
6
+ const container = getContainer( elementId );
7
+ const type = container?.model.get( 'widgetType' ) || container?.model.get( 'elType' );
8
+
9
+ if ( ! type ) {
10
+ return null;
11
+ }
12
+
13
+ return getWidgetsCache()?.[ type ]?.icon ?? null;
14
+ }
@@ -0,0 +1,48 @@
1
+ import { type ElementID } from '../types';
2
+ import { getElementEditorSettings } from './get-element-editor-settings';
3
+ import { getElementLabel } from './get-element-label';
4
+ import { getElementSetting } from './get-element-setting';
5
+
6
+ function extractString( value: unknown ): string | null {
7
+ if ( typeof value === 'string' ) {
8
+ return value || null;
9
+ }
10
+
11
+ if (
12
+ value &&
13
+ typeof value === 'object' &&
14
+ 'value' in value &&
15
+ typeof ( value as { value: unknown } ).value === 'string'
16
+ ) {
17
+ return ( value as { value: string } ).value || null;
18
+ }
19
+
20
+ return null;
21
+ }
22
+
23
+ export function getElementTitle( elementId: ElementID ): string | null {
24
+ const editorSettings = getElementEditorSettings( elementId );
25
+ const editorTitle = extractString( editorSettings?.title );
26
+
27
+ if ( editorTitle ) {
28
+ return editorTitle;
29
+ }
30
+
31
+ const legacyTitle = extractString( getElementSetting( elementId, '_title' ) );
32
+
33
+ if ( legacyTitle ) {
34
+ return legacyTitle;
35
+ }
36
+
37
+ const presetTitle = extractString( getElementSetting( elementId, 'presetTitle' ) );
38
+
39
+ if ( presetTitle ) {
40
+ return presetTitle;
41
+ }
42
+
43
+ try {
44
+ return getElementLabel( elementId );
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
@@ -0,0 +1,34 @@
1
+ import { type ElementPosition, type V1ElementData } from './types';
2
+
3
+ export function resolveInsertIndex( position: ElementPosition, elements: V1ElementData[] ): number {
4
+ const lastIndex = elements.length;
5
+
6
+ switch ( position.kind ) {
7
+ case 'first':
8
+ return 0;
9
+
10
+ case 'last':
11
+ return lastIndex;
12
+
13
+ case 'index': {
14
+ const index = typeof position.value === 'number' ? position.value : lastIndex;
15
+
16
+ return Math.max( 0, Math.min( index, lastIndex ) );
17
+ }
18
+
19
+ case 'after_type': {
20
+ const anchor = elements.findIndex( ( element ) => element.elType === position.value );
21
+
22
+ return anchor >= 0 ? anchor + 1 : lastIndex;
23
+ }
24
+
25
+ case 'before_type': {
26
+ const anchor = elements.findIndex( ( element ) => element.elType === position.value );
27
+
28
+ return anchor >= 0 ? anchor : lastIndex;
29
+ }
30
+
31
+ default:
32
+ return lastIndex;
33
+ }
34
+ }
package/src/sync/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type PropsSchema, type PropValue, type SizePropValue } from '@elementor/editor-props';
2
2
  import { type ClassState, type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
3
3
 
4
+ import { type ChildDependencyRule } from '../children-dependencies/types';
4
5
  import { type ControlItem, type PseudoState } from '../types';
5
6
 
6
7
  export type ExtendedWindow = Window & {
@@ -162,12 +163,20 @@ export type V1ElementModelProps = {
162
163
  interactions?: string | ElementInteractions;
163
164
  isGlobal?: boolean;
164
165
  skipDefaultChildren?: boolean;
166
+ hydrateDefaultChildren?: boolean;
165
167
  };
166
168
 
167
169
  export type V1ElementData = Omit< V1ElementModelProps, 'elements' > & {
168
170
  elements?: V1ElementData[];
169
171
  };
170
172
 
173
+ export type ElementPositionKind = 'last' | 'first' | 'index' | 'after_type' | 'before_type';
174
+
175
+ export type ElementPosition = {
176
+ kind: ElementPositionKind;
177
+ value: number | string | null;
178
+ };
179
+
171
180
  export type V1ElementEditorSettingsProps = {
172
181
  title?: string;
173
182
  initial_position?: number;
@@ -197,6 +206,7 @@ export type V1ElementConfig< T = object, TChild = unknown > = {
197
206
  show_in_panel?: boolean;
198
207
  allowed_child_types?: string[];
199
208
  default_children?: Array< Record< string, TChild > >;
209
+ children_dependencies?: ChildDependencyRule[];
200
210
  meta?: { [ key: string ]: string | number | boolean | null | NonNullable< V1ElementConfig[ 'meta' ] > };
201
211
  } & T;
202
212
 
@@ -205,4 +215,6 @@ type V1Model< T > = {
205
215
  set: < K extends keyof T >( key: K, value: T[ K ] ) => void;
206
216
  toJSON: ( options?: { remove?: string[] } ) => T;
207
217
  trigger?: ( event: string, ...args: unknown[] ) => void;
218
+ on?: ( event: string, callback: ( ...args: unknown[] ) => void, context?: unknown ) => void;
219
+ off?: ( event: string, callback?: ( ...args: unknown[] ) => void, context?: unknown ) => void;
208
220
  };