@elementor/editor-elements 4.3.0-973 → 4.3.0-975
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/dist/index.d.mts +43 -2
- package/dist/index.d.ts +43 -2
- package/dist/index.js +248 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +231 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/children-dependencies/bind-settings-reconcile.ts +148 -0
- package/src/children-dependencies/index.ts +4 -0
- package/src/children-dependencies/reconcile-initial-children.ts +60 -0
- package/src/children-dependencies/stash.ts +40 -0
- package/src/children-dependencies/types.ts +20 -0
- package/src/children-dependencies/utils.ts +5 -0
- package/src/index.ts +10 -0
- package/src/sync/resolve-insert-index.ts +34 -0
- package/src/sync/types.ts +11 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type PropValue } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
import { getContainer } from '../sync/get-container';
|
|
4
|
+
import { addModelToParent, removeModelFromParent } from '../sync/resolve-element';
|
|
5
|
+
import { resolveInsertIndex } from '../sync/resolve-insert-index';
|
|
6
|
+
import { type V1Element, type V1ElementConfig, type V1ElementData, type V1ElementModelProps } from '../sync/types';
|
|
7
|
+
import { createChildrenStash } from './stash';
|
|
8
|
+
import { type ChildDependencyRule, type ChildrenStash } from './types';
|
|
9
|
+
import { evaluateWhen } from './utils';
|
|
10
|
+
|
|
11
|
+
type SettingsChangeSource = {
|
|
12
|
+
get: ( key: string ) => unknown;
|
|
13
|
+
toJSON: () => Record< string, PropValue >;
|
|
14
|
+
on?: ( event: string, callback: ( ...args: unknown[] ) => void ) => void;
|
|
15
|
+
off?: ( event: string, callback: ( ...args: unknown[] ) => void ) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ReactiveModel = {
|
|
19
|
+
get: ( key: string ) => unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type BindSettingsReconcileArgs = {
|
|
23
|
+
model: ReactiveModel;
|
|
24
|
+
elementConfig: V1ElementConfig | undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type Detach = () => void;
|
|
28
|
+
|
|
29
|
+
export function bindSettingsReconcile( { model, elementConfig }: BindSettingsReconcileArgs ): Detach {
|
|
30
|
+
const stash = createChildrenStash();
|
|
31
|
+
const rules = elementConfig?.children_dependencies;
|
|
32
|
+
|
|
33
|
+
if ( ! rules?.length ) {
|
|
34
|
+
return () => {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const settingsModel = model.get( 'settings' ) as SettingsChangeSource | undefined;
|
|
38
|
+
const elementId = model.get( 'id' ) as string | undefined;
|
|
39
|
+
|
|
40
|
+
if ( ! settingsModel?.on || ! settingsModel?.off || ! elementId ) {
|
|
41
|
+
return () => {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lastMet = new Map< string, boolean >();
|
|
45
|
+
|
|
46
|
+
rules.forEach( ( rule ) => {
|
|
47
|
+
lastMet.set( rule.child_type, evaluateWhen( rule.when, settingsModel.toJSON() ) );
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
const onChange = () => {
|
|
51
|
+
const currentSettings = settingsModel.toJSON();
|
|
52
|
+
|
|
53
|
+
rules.forEach( ( rule ) => {
|
|
54
|
+
const previous = lastMet.get( rule.child_type ) ?? false;
|
|
55
|
+
const current = evaluateWhen( rule.when, currentSettings );
|
|
56
|
+
|
|
57
|
+
if ( previous === current ) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lastMet.set( rule.child_type, current );
|
|
62
|
+
|
|
63
|
+
if ( current ) {
|
|
64
|
+
attachChildFromRule( elementId, rule, stash );
|
|
65
|
+
} else {
|
|
66
|
+
detachChildFromRule( elementId, rule, stash );
|
|
67
|
+
}
|
|
68
|
+
} );
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
settingsModel.on( 'change', onChange );
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
settingsModel.off?.( 'change', onChange );
|
|
75
|
+
stash.clearAllForElement( elementId );
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function attachChildFromRule( parentId: string, rule: ChildDependencyRule, stash: ChildrenStash ): void {
|
|
80
|
+
const parent = getContainer( parentId ) ?? undefined;
|
|
81
|
+
const currentChildren = getDirectChildData( parent );
|
|
82
|
+
const alreadyPresent = currentChildren.some( ( child ) => child.elType === rule.child_type );
|
|
83
|
+
|
|
84
|
+
if ( alreadyPresent ) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stashed = rule.stash ? stash.get( parentId, rule.child_type ) : undefined;
|
|
89
|
+
const modelData = stashed ?? rule.default_model ?? ( { elType: rule.child_type } as V1ElementData );
|
|
90
|
+
const insertAt = resolveInsertIndex( rule.position, currentChildren );
|
|
91
|
+
|
|
92
|
+
const attached = addModelToParent( parentId, modelData as unknown as V1ElementModelProps, { at: insertAt } );
|
|
93
|
+
|
|
94
|
+
if ( ! attached ) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if ( stashed ) {
|
|
99
|
+
stash.clear( parentId, rule.child_type );
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
requestNavigatorRefresh( parentId );
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function detachChildFromRule( parentId: string, rule: ChildDependencyRule, stash: ChildrenStash ): void {
|
|
106
|
+
const parent = getContainer( parentId ) ?? undefined;
|
|
107
|
+
const child = parent?.children?.find( ( candidate ) => candidate.model.get( 'elType' ) === rule.child_type );
|
|
108
|
+
|
|
109
|
+
if ( ! child ) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const childSnapshot = child.model.toJSON() as unknown as V1ElementData;
|
|
114
|
+
const removed = removeModelFromParent( parentId, child.id );
|
|
115
|
+
|
|
116
|
+
if ( ! removed ) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if ( rule.stash ) {
|
|
121
|
+
stash.save( parentId, rule.child_type, childSnapshot );
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
requestNavigatorRefresh( parentId );
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Backbone `add`/`remove` on the parent elements collection is silent (see
|
|
128
|
+
// assets/dev/js/editor/document/component.js addModelToParent / removeModelFromParent),
|
|
129
|
+
// so the Marionette Navigator CompositeView never observes our mutation. The
|
|
130
|
+
// legacy navigator exposes an `elementor/navigator/refresh-children` custom
|
|
131
|
+
// window event (assets/dev/js/editor/regions/navigator/element.js) that
|
|
132
|
+
// re-renders the subtree for a given element id — dispatch it here so the
|
|
133
|
+
// Structure panel stays in sync with the model.
|
|
134
|
+
function requestNavigatorRefresh( parentId: string ): void {
|
|
135
|
+
if ( typeof window === 'undefined' || typeof window.dispatchEvent !== 'function' ) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
window.dispatchEvent(
|
|
140
|
+
new CustomEvent( 'elementor/navigator/refresh-children', {
|
|
141
|
+
detail: { elementId: parentId },
|
|
142
|
+
} )
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getDirectChildData( parent: V1Element | undefined ): V1ElementData[] {
|
|
147
|
+
return ( parent?.children ?? [] ).map( ( child ) => child.model.toJSON() as unknown as V1ElementData );
|
|
148
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
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 } 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 stashed = rule.stash ? stash.get( elementId, rule.child_type ) : undefined;
|
|
38
|
+
const modelData = stashed ?? rule.default_model ?? ( { elType: rule.child_type } as V1ElementData );
|
|
39
|
+
const insertAt = resolveInsertIndex( rule.position, elements );
|
|
40
|
+
|
|
41
|
+
elements.splice( insertAt, 0, modelData );
|
|
42
|
+
|
|
43
|
+
if ( stashed ) {
|
|
44
|
+
stash.clear( elementId, rule.child_type );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ( ! isMet && isPresent ) {
|
|
51
|
+
const [ removed ] = elements.splice( existingIndex, 1 );
|
|
52
|
+
|
|
53
|
+
if ( rule.stash && removed ) {
|
|
54
|
+
stash.save( elementId, rule.child_type, removed );
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} );
|
|
58
|
+
|
|
59
|
+
attributes.elements = elements;
|
|
60
|
+
}
|
|
@@ -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
|
+
};
|
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';
|
|
@@ -46,6 +55,7 @@ export { moveElement, type MoveElementParams } from './sync/move-element';
|
|
|
46
55
|
export { moveElements } from './sync/move-elements';
|
|
47
56
|
export { removeElements } from './sync/remove-elements';
|
|
48
57
|
export { replaceElement } from './sync/replace-element';
|
|
58
|
+
export { resolveInsertIndex } from './sync/resolve-insert-index';
|
|
49
59
|
export { updateElementEditorSettings } from './sync/update-element-editor-settings';
|
|
50
60
|
export { updateElementSettings, type UpdateElementSettingsArgs } from './sync/update-element-settings';
|
|
51
61
|
|
|
@@ -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 & {
|
|
@@ -168,6 +169,13 @@ export type V1ElementData = Omit< V1ElementModelProps, 'elements' > & {
|
|
|
168
169
|
elements?: V1ElementData[];
|
|
169
170
|
};
|
|
170
171
|
|
|
172
|
+
export type ElementPositionKind = 'last' | 'first' | 'index' | 'after_type' | 'before_type';
|
|
173
|
+
|
|
174
|
+
export type ElementPosition = {
|
|
175
|
+
kind: ElementPositionKind;
|
|
176
|
+
value: number | string | null;
|
|
177
|
+
};
|
|
178
|
+
|
|
171
179
|
export type V1ElementEditorSettingsProps = {
|
|
172
180
|
title?: string;
|
|
173
181
|
initial_position?: number;
|
|
@@ -197,6 +205,7 @@ export type V1ElementConfig< T = object, TChild = unknown > = {
|
|
|
197
205
|
show_in_panel?: boolean;
|
|
198
206
|
allowed_child_types?: string[];
|
|
199
207
|
default_children?: Array< Record< string, TChild > >;
|
|
208
|
+
children_dependencies?: ChildDependencyRule[];
|
|
200
209
|
meta?: { [ key: string ]: string | number | boolean | null | NonNullable< V1ElementConfig[ 'meta' ] > };
|
|
201
210
|
} & T;
|
|
202
211
|
|
|
@@ -205,4 +214,6 @@ type V1Model< T > = {
|
|
|
205
214
|
set: < K extends keyof T >( key: K, value: T[ K ] ) => void;
|
|
206
215
|
toJSON: ( options?: { remove?: string[] } ) => T;
|
|
207
216
|
trigger?: ( event: string, ...args: unknown[] ) => void;
|
|
217
|
+
on?: ( event: string, callback: ( ...args: unknown[] ) => void, context?: unknown ) => void;
|
|
218
|
+
off?: ( event: string, callback?: ( ...args: unknown[] ) => void, context?: unknown ) => void;
|
|
208
219
|
};
|