@elementor/editor-canvas 4.0.0-607 → 4.0.0-619
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 +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +137 -26
- package/dist/index.mjs +139 -28
- package/package.json +18 -18
- package/src/components/style-renderer.tsx +29 -2
- package/src/hooks/__tests__/use-style-items.test.ts +109 -5
- package/src/hooks/use-style-items.ts +144 -30
- package/src/init-settings-transformers.ts +2 -0
- package/src/init.tsx +0 -3
- package/src/legacy/types.ts +14 -1
- package/src/renderers/create-styles-renderer.ts +10 -1
- package/src/transformers/shared/__tests__/video-src-transformer.test.ts +159 -0
- package/src/transformers/shared/video-src-transformer.ts +23 -0
|
@@ -8,6 +8,7 @@ import { Portal } from '@elementor/ui';
|
|
|
8
8
|
|
|
9
9
|
import { useDocumentsCssLinks } from '../hooks/use-documents-css-links';
|
|
10
10
|
import { useStyleItems } from '../hooks/use-style-items';
|
|
11
|
+
import { type StyleItem } from '../renderers/create-styles-renderer';
|
|
11
12
|
|
|
12
13
|
export function StyleRenderer() {
|
|
13
14
|
const container = usePortalContainer();
|
|
@@ -21,8 +22,8 @@ export function StyleRenderer() {
|
|
|
21
22
|
|
|
22
23
|
return (
|
|
23
24
|
<Portal container={ container }>
|
|
24
|
-
{ styleItems.map( ( item
|
|
25
|
-
<style key={ `${ item.id }-${
|
|
25
|
+
{ filterUniqueStyleDefinitions( styleItems ).map( ( item ) => (
|
|
26
|
+
<style key={ `${ item.id }-${ item.breakpoint }-${ item.state ?? 'normal' }` }>{ item.value }</style>
|
|
26
27
|
) ) }
|
|
27
28
|
{ linksAttrs.map( ( attrs ) => (
|
|
28
29
|
<link { ...attrs } key={ attrs.id } />
|
|
@@ -34,3 +35,29 @@ export function StyleRenderer() {
|
|
|
34
35
|
function usePortalContainer() {
|
|
35
36
|
return useListenTo( commandEndEvent( 'editor/documents/attach-preview' ), () => getCanvasIframeDocument()?.head );
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
// we load local styles also from components, which are handled differently
|
|
40
|
+
// to avoid having "Encountered two children with the same key" - adding this filtering to avoid rendering the same style twice
|
|
41
|
+
function filterUniqueStyleDefinitions( styleItems: StyleItem[] ) {
|
|
42
|
+
const seen = new Map< string, StyleItem[] >();
|
|
43
|
+
|
|
44
|
+
return styleItems.filter( ( style ) => {
|
|
45
|
+
const existingStyle = seen.get( style.id );
|
|
46
|
+
|
|
47
|
+
if ( existingStyle ) {
|
|
48
|
+
const existingStyleVariant = existingStyle.find(
|
|
49
|
+
( s ) => s.breakpoint === style.breakpoint && s.state === style.state
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if ( existingStyleVariant ) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
existingStyle.push( style );
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
seen.set( style.id, [ style ] );
|
|
61
|
+
return true;
|
|
62
|
+
} );
|
|
63
|
+
}
|
|
@@ -28,7 +28,7 @@ jest.mock( '../use-style-renderer', () => ( {
|
|
|
28
28
|
} ) );
|
|
29
29
|
|
|
30
30
|
jest.mock( '@elementor/editor-responsive', () => ( {
|
|
31
|
-
|
|
31
|
+
useBreakpoints: jest.fn().mockReturnValue( [
|
|
32
32
|
{ id: 'desktop', label: 'Desktop' },
|
|
33
33
|
{ id: 'tablet', label: 'Tablet' },
|
|
34
34
|
{ id: 'mobile', label: 'Mobile' },
|
|
@@ -39,10 +39,12 @@ describe( 'useStyleItems', () => {
|
|
|
39
39
|
beforeEach( () => {
|
|
40
40
|
jest.mocked( useStyleRenderer ).mockReturnValue(
|
|
41
41
|
jest.fn().mockImplementation( ( { styles } ) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
Promise.resolve(
|
|
43
|
+
styles.map( ( style: StyleDefinition ) => ( {
|
|
44
|
+
id: style.id,
|
|
45
|
+
breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
|
|
46
|
+
} ) )
|
|
47
|
+
)
|
|
46
48
|
)
|
|
47
49
|
);
|
|
48
50
|
} );
|
|
@@ -229,4 +231,106 @@ describe( 'useStyleItems', () => {
|
|
|
229
231
|
{ id: 'style1', breakpoint: 'mobile' },
|
|
230
232
|
] );
|
|
231
233
|
} );
|
|
234
|
+
|
|
235
|
+
it( 'should use cached items when no changes detected', async () => {
|
|
236
|
+
// Arrange.
|
|
237
|
+
const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
|
|
238
|
+
Promise.resolve(
|
|
239
|
+
styles.map( ( style: StyleDefinition ) => ( {
|
|
240
|
+
id: style.id,
|
|
241
|
+
breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
|
|
242
|
+
} ) )
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
|
|
247
|
+
|
|
248
|
+
const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [
|
|
249
|
+
createMockStyleDefinition( { id: 'style1' } ),
|
|
250
|
+
createMockStyleDefinition( { id: 'style2' } ),
|
|
251
|
+
] );
|
|
252
|
+
|
|
253
|
+
jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
|
|
254
|
+
|
|
255
|
+
// Act - initial render.
|
|
256
|
+
const { result } = renderHook( () => useStyleItems() );
|
|
257
|
+
|
|
258
|
+
await act( async () => {
|
|
259
|
+
mockProvider.actions.updateProps?.( {
|
|
260
|
+
id: 'style1',
|
|
261
|
+
meta: { breakpoint: null, state: null },
|
|
262
|
+
props: { a: 1 },
|
|
263
|
+
} );
|
|
264
|
+
} );
|
|
265
|
+
|
|
266
|
+
// Assert.
|
|
267
|
+
expect( renderStylesMock ).toHaveBeenCalledTimes( 1 );
|
|
268
|
+
expect( result.current ).toHaveLength( 2 );
|
|
269
|
+
|
|
270
|
+
// Act - trigger update with same props (updateProps mutates in place, same reference).
|
|
271
|
+
renderStylesMock.mockClear();
|
|
272
|
+
|
|
273
|
+
await act( async () => {
|
|
274
|
+
mockProvider.actions.updateProps?.( {
|
|
275
|
+
id: 'style1',
|
|
276
|
+
meta: { breakpoint: null, state: null },
|
|
277
|
+
props: { a: 1 },
|
|
278
|
+
} );
|
|
279
|
+
} );
|
|
280
|
+
|
|
281
|
+
// Assert - renderStyles should not be called when no changes detected.
|
|
282
|
+
expect( renderStylesMock ).not.toHaveBeenCalled();
|
|
283
|
+
expect( result.current ).toHaveLength( 2 );
|
|
284
|
+
} );
|
|
285
|
+
|
|
286
|
+
it( 'should only re-render changed styles on differential update', async () => {
|
|
287
|
+
// Arrange.
|
|
288
|
+
const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
|
|
289
|
+
Promise.resolve(
|
|
290
|
+
styles.map( ( style: StyleDefinition ) => ( {
|
|
291
|
+
id: style.id,
|
|
292
|
+
breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
|
|
293
|
+
} ) )
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
|
|
298
|
+
|
|
299
|
+
const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [
|
|
300
|
+
createMockStyleDefinition( { id: 'style1' } ),
|
|
301
|
+
createMockStyleDefinition( { id: 'style2' } ),
|
|
302
|
+
createMockStyleDefinition( { id: 'style3' } ),
|
|
303
|
+
] );
|
|
304
|
+
|
|
305
|
+
jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
|
|
306
|
+
|
|
307
|
+
// Act - initial render.
|
|
308
|
+
const { result } = renderHook( () => useStyleItems() );
|
|
309
|
+
|
|
310
|
+
await act( async () => {
|
|
311
|
+
mockProvider.actions.updateProps?.( {
|
|
312
|
+
id: 'style1',
|
|
313
|
+
meta: { breakpoint: null, state: null },
|
|
314
|
+
props: { a: 1 },
|
|
315
|
+
} );
|
|
316
|
+
} );
|
|
317
|
+
|
|
318
|
+
// Assert - initial render includes all styles.
|
|
319
|
+
expect( renderStylesMock ).toHaveBeenCalledTimes( 1 );
|
|
320
|
+
expect( renderStylesMock.mock.calls[ 0 ][ 0 ].styles ).toHaveLength( 3 );
|
|
321
|
+
expect( result.current ).toHaveLength( 3 );
|
|
322
|
+
|
|
323
|
+
// Act - update style2 using update action (creates new object reference).
|
|
324
|
+
renderStylesMock.mockClear();
|
|
325
|
+
|
|
326
|
+
await act( async () => {
|
|
327
|
+
mockProvider.actions.update?.( { id: 'style2', label: 'Updated Style 2' } );
|
|
328
|
+
} );
|
|
329
|
+
|
|
330
|
+
// Assert - only the changed style should be rendered.
|
|
331
|
+
expect( renderStylesMock ).toHaveBeenCalledTimes( 1 );
|
|
332
|
+
expect( renderStylesMock.mock.calls[ 0 ][ 0 ].styles ).toHaveLength( 1 );
|
|
333
|
+
expect( renderStylesMock.mock.calls[ 0 ][ 0 ].styles[ 0 ].id ).toBe( 'style2' );
|
|
334
|
+
expect( result.current ).toHaveLength( 3 );
|
|
335
|
+
} );
|
|
232
336
|
} );
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { type BreakpointId,
|
|
3
|
-
import { isClassState, type StyleDefinitionClassState } from '@elementor/editor-styles';
|
|
1
|
+
import { type Dispatch, type SetStateAction, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { type BreakpointId, useBreakpoints } from '@elementor/editor-responsive';
|
|
3
|
+
import { isClassState, type StyleDefinition, type StyleDefinitionClassState } from '@elementor/editor-styles';
|
|
4
4
|
import { type StylesProvider, stylesRepository } from '@elementor/editor-styles-repository';
|
|
5
5
|
import { registerDataHook } from '@elementor/editor-v1-adapters';
|
|
6
6
|
|
|
@@ -11,26 +11,47 @@ import { useOnMount } from './use-on-mount';
|
|
|
11
11
|
import { useStylePropResolver } from './use-style-prop-resolver';
|
|
12
12
|
import { useStyleRenderer } from './use-style-renderer';
|
|
13
13
|
|
|
14
|
+
type StylesCollection = Record< string, StyleDefinition >;
|
|
15
|
+
|
|
16
|
+
type StyleItemsCache = {
|
|
17
|
+
orderedIds: string[];
|
|
18
|
+
itemsById: Map< string, StyleItem[] >;
|
|
19
|
+
};
|
|
20
|
+
|
|
14
21
|
type ProviderAndStyleItems = { provider: StylesProvider; items: StyleItem[] };
|
|
15
22
|
|
|
16
|
-
type ProviderAndSubscriber = {
|
|
23
|
+
type ProviderAndSubscriber = {
|
|
24
|
+
provider: StylesProvider;
|
|
25
|
+
subscriber: ( previous?: StylesCollection, current?: StylesCollection ) => Promise< void >;
|
|
26
|
+
};
|
|
17
27
|
|
|
18
28
|
type ProviderAndStyleItemsMap = Record< string, ProviderAndStyleItems >;
|
|
19
29
|
|
|
20
30
|
export function useStyleItems() {
|
|
21
31
|
const resolve = useStylePropResolver();
|
|
22
32
|
const renderStyles = useStyleRenderer( resolve );
|
|
33
|
+
const breakpoints = useBreakpoints();
|
|
23
34
|
|
|
24
35
|
const [ styleItems, setStyleItems ] = useState< ProviderAndStyleItemsMap >( {} );
|
|
36
|
+
const styleItemsCacheRef = useRef< Map< string, StyleItemsCache > >( new Map() );
|
|
25
37
|
|
|
26
38
|
const providerAndSubscribers = useMemo( () => {
|
|
27
39
|
return stylesRepository.getProviders().map( ( provider ): ProviderAndSubscriber => {
|
|
40
|
+
const providerKey = provider.getKey();
|
|
41
|
+
|
|
42
|
+
if ( ! styleItemsCacheRef.current.has( providerKey ) ) {
|
|
43
|
+
styleItemsCacheRef.current.set( providerKey, { orderedIds: [], itemsById: new Map() } );
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cache = styleItemsCacheRef.current.get( providerKey ) as StyleItemsCache;
|
|
47
|
+
|
|
28
48
|
return {
|
|
29
49
|
provider,
|
|
30
50
|
subscriber: createProviderSubscriber( {
|
|
31
51
|
provider,
|
|
32
52
|
renderStyles,
|
|
33
53
|
setStyleItems,
|
|
54
|
+
cache,
|
|
34
55
|
} ),
|
|
35
56
|
};
|
|
36
57
|
} );
|
|
@@ -54,33 +75,30 @@ export function useStyleItems() {
|
|
|
54
75
|
} );
|
|
55
76
|
} );
|
|
56
77
|
|
|
57
|
-
const
|
|
78
|
+
const breakpointSorter = useMemo(
|
|
79
|
+
() => createBreakpointSorter( breakpoints.map( ( breakpoint ) => breakpoint.id ) ),
|
|
80
|
+
[ breakpoints ]
|
|
81
|
+
);
|
|
58
82
|
|
|
59
83
|
return useMemo(
|
|
60
84
|
() =>
|
|
61
85
|
Object.values( styleItems )
|
|
62
|
-
.sort(
|
|
86
|
+
.sort( prioritySorter )
|
|
63
87
|
.flatMap( ( { items } ) => items )
|
|
64
|
-
.sort(
|
|
65
|
-
.sort(
|
|
66
|
-
|
|
67
|
-
[ styleItems, breakpointsOrder.join( '-' ) ]
|
|
88
|
+
.sort( stateSorter )
|
|
89
|
+
.sort( breakpointSorter ),
|
|
90
|
+
[ styleItems, breakpointSorter ]
|
|
68
91
|
);
|
|
69
92
|
}
|
|
70
|
-
|
|
93
|
+
|
|
94
|
+
function prioritySorter(
|
|
71
95
|
{ provider: providerA }: ProviderAndStyleItems,
|
|
72
96
|
{ provider: providerB }: ProviderAndStyleItems
|
|
73
97
|
) {
|
|
74
98
|
return providerA.priority - providerB.priority;
|
|
75
99
|
}
|
|
76
100
|
|
|
77
|
-
function
|
|
78
|
-
return ( { breakpoint: breakpointA }: StyleItem, { breakpoint: breakpointB }: StyleItem ) =>
|
|
79
|
-
breakpointsOrder.indexOf( breakpointA as BreakpointId ) -
|
|
80
|
-
breakpointsOrder.indexOf( breakpointB as BreakpointId );
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function sortByStateType( { state: stateA }: StyleItem, { state: stateB }: StyleItem ) {
|
|
101
|
+
function stateSorter( { state: stateA }: StyleItem, { state: stateB }: StyleItem ) {
|
|
84
102
|
if (
|
|
85
103
|
isClassState( stateA as StyleDefinitionClassState ) &&
|
|
86
104
|
! isClassState( stateB as StyleDefinitionClassState )
|
|
@@ -98,28 +116,31 @@ function sortByStateType( { state: stateA }: StyleItem, { state: stateB }: Style
|
|
|
98
116
|
return 0;
|
|
99
117
|
}
|
|
100
118
|
|
|
119
|
+
function createBreakpointSorter( breakpointsOrder: BreakpointId[] ) {
|
|
120
|
+
return ( { breakpoint: breakpointA }: StyleItem, { breakpoint: breakpointB }: StyleItem ) =>
|
|
121
|
+
breakpointsOrder.indexOf( breakpointA as BreakpointId ) -
|
|
122
|
+
breakpointsOrder.indexOf( breakpointB as BreakpointId );
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
type CreateProviderSubscriberArgs = {
|
|
102
126
|
provider: StylesProvider;
|
|
103
127
|
renderStyles: StyleRenderer;
|
|
104
128
|
setStyleItems: Dispatch< SetStateAction< ProviderAndStyleItemsMap > >;
|
|
129
|
+
cache: StyleItemsCache;
|
|
105
130
|
};
|
|
106
131
|
|
|
107
|
-
function createProviderSubscriber( { provider, renderStyles, setStyleItems }: CreateProviderSubscriberArgs ) {
|
|
108
|
-
return abortPreviousRuns( ( abortController ) =>
|
|
132
|
+
function createProviderSubscriber( { provider, renderStyles, setStyleItems, cache }: CreateProviderSubscriberArgs ) {
|
|
133
|
+
return abortPreviousRuns( ( abortController, previous?: StylesCollection, current?: StylesCollection ) =>
|
|
109
134
|
signalizedProcess( abortController.signal )
|
|
110
135
|
.then( ( _, signal ) => {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const style = items[ lastPosition - index ];
|
|
136
|
+
const hasDiffInfo = current !== undefined && previous !== undefined;
|
|
137
|
+
const hasCache = cache.orderedIds.length > 0;
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
} );
|
|
139
|
+
if ( hasDiffInfo && hasCache ) {
|
|
140
|
+
return updateItems( previous, current, signal );
|
|
141
|
+
}
|
|
121
142
|
|
|
122
|
-
return
|
|
143
|
+
return createItems( signal );
|
|
123
144
|
} )
|
|
124
145
|
.then( ( items ) => {
|
|
125
146
|
setStyleItems( ( prev ) => ( {
|
|
@@ -130,6 +151,50 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems }: Cr
|
|
|
130
151
|
.execute()
|
|
131
152
|
);
|
|
132
153
|
|
|
154
|
+
async function updateItems( previous: StylesCollection, current: StylesCollection, signal: AbortSignal ) {
|
|
155
|
+
const changedIds = getChangedStyleIds( previous, current );
|
|
156
|
+
|
|
157
|
+
cache.orderedIds = provider.actions
|
|
158
|
+
.all()
|
|
159
|
+
.map( ( style ) => style.id )
|
|
160
|
+
.reverse();
|
|
161
|
+
|
|
162
|
+
if ( changedIds.length > 0 ) {
|
|
163
|
+
const changedStyles = changedIds
|
|
164
|
+
.map( ( id ) => provider.actions.get( id ) )
|
|
165
|
+
.filter( ( style ): style is StyleDefinition => !! style )
|
|
166
|
+
.map( ( style ) => ( {
|
|
167
|
+
...style,
|
|
168
|
+
cssName: provider.actions.resolveCssName( style.id ),
|
|
169
|
+
} ) );
|
|
170
|
+
|
|
171
|
+
return renderStyles( { styles: breakToBreakpoints( changedStyles ), signal } ).then( ( rendered ) => {
|
|
172
|
+
updateCacheItems( cache, rendered );
|
|
173
|
+
|
|
174
|
+
return getOrderedItems( cache );
|
|
175
|
+
} );
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return getOrderedItems( cache );
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function createItems( signal: AbortSignal ) {
|
|
182
|
+
const allStyles = provider.actions.all();
|
|
183
|
+
|
|
184
|
+
const styles = allStyles.reverse().map( ( style ) => {
|
|
185
|
+
return {
|
|
186
|
+
...style,
|
|
187
|
+
cssName: provider.actions.resolveCssName( style.id ),
|
|
188
|
+
};
|
|
189
|
+
} );
|
|
190
|
+
|
|
191
|
+
return renderStyles( { styles: breakToBreakpoints( styles ), signal } ).then( ( rendered ) => {
|
|
192
|
+
rebuildCache( cache, allStyles, rendered );
|
|
193
|
+
|
|
194
|
+
return rendered;
|
|
195
|
+
} );
|
|
196
|
+
}
|
|
197
|
+
|
|
133
198
|
function breakToBreakpoints( styles: RendererStyleDefinition[] ) {
|
|
134
199
|
return Object.values(
|
|
135
200
|
styles.reduce(
|
|
@@ -157,3 +222,52 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems }: Cr
|
|
|
157
222
|
).flatMap( ( breakpointMap ) => Object.values( breakpointMap ) );
|
|
158
223
|
}
|
|
159
224
|
}
|
|
225
|
+
|
|
226
|
+
function getChangedStyleIds( previous: StylesCollection, current: StylesCollection ): string[] {
|
|
227
|
+
const changedIds: string[] = [];
|
|
228
|
+
|
|
229
|
+
for ( const id of Object.keys( current ) ) {
|
|
230
|
+
const currentStyle = current[ id ];
|
|
231
|
+
const previousStyle = previous[ id ];
|
|
232
|
+
|
|
233
|
+
if ( ! previousStyle || currentStyle !== previousStyle ) {
|
|
234
|
+
changedIds.push( id );
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return changedIds;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getOrderedItems( cache: StyleItemsCache ): StyleItem[] {
|
|
242
|
+
return cache.orderedIds
|
|
243
|
+
.map( ( id ) => cache.itemsById.get( id ) )
|
|
244
|
+
.filter( ( items ): items is StyleItem[] => items !== undefined )
|
|
245
|
+
.flat();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function updateCacheItems( cache: StyleItemsCache, changedItems: StyleItem[] ): void {
|
|
249
|
+
for ( const item of changedItems ) {
|
|
250
|
+
const existing = cache.itemsById.get( item.id );
|
|
251
|
+
if ( existing ) {
|
|
252
|
+
const idx = existing.findIndex( ( e ) => e.breakpoint === item.breakpoint && e.state === item.state );
|
|
253
|
+
if ( idx >= 0 ) {
|
|
254
|
+
existing[ idx ] = item;
|
|
255
|
+
} else {
|
|
256
|
+
existing.push( item );
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
cache.itemsById.set( item.id, [ item ] );
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function rebuildCache( cache: StyleItemsCache, allStyles: StyleDefinition[], items: StyleItem[] ): void {
|
|
265
|
+
cache.orderedIds = allStyles.map( ( style ) => style.id ).reverse();
|
|
266
|
+
cache.itemsById.clear();
|
|
267
|
+
|
|
268
|
+
for ( const item of items ) {
|
|
269
|
+
const existing = cache.itemsById.get( item.id ) || [];
|
|
270
|
+
existing.push( item );
|
|
271
|
+
cache.itemsById.set( item.id, existing );
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -9,6 +9,7 @@ import { queryTransformer } from './transformers/settings/query-transformer';
|
|
|
9
9
|
import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
|
|
10
10
|
import { imageTransformer } from './transformers/shared/image-transformer';
|
|
11
11
|
import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
12
|
+
import { videoSrcTransformer } from './transformers/shared/video-src-transformer';
|
|
12
13
|
|
|
13
14
|
export function initSettingsTransformers() {
|
|
14
15
|
settingsTransformersRegistry
|
|
@@ -17,6 +18,7 @@ export function initSettingsTransformers() {
|
|
|
17
18
|
.register( 'query', queryTransformer )
|
|
18
19
|
.register( 'image', imageTransformer )
|
|
19
20
|
.register( 'image-src', imageSrcTransformer )
|
|
21
|
+
.register( 'video-src', videoSrcTransformer )
|
|
20
22
|
.register( 'attributes', attributesTransformer )
|
|
21
23
|
.register( 'date-time', dateTimeTransformer )
|
|
22
24
|
.register( 'html-v2', htmlV2Transformer )
|
package/src/init.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { injectIntoLogic, injectIntoTop } from '@elementor/editor';
|
|
2
|
-
import { init as initInteractionsRepository } from '@elementor/editor-interactions';
|
|
3
2
|
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
4
3
|
|
|
5
4
|
import { ClassesRename } from './components/classes-rename';
|
|
@@ -32,8 +31,6 @@ export function init() {
|
|
|
32
31
|
|
|
33
32
|
initSettingsTransformers();
|
|
34
33
|
|
|
35
|
-
initInteractionsRepository();
|
|
36
|
-
|
|
37
34
|
injectIntoTop( {
|
|
38
35
|
id: 'elements-overlays',
|
|
39
36
|
component: ElementsOverlays,
|
package/src/legacy/types.ts
CHANGED
|
@@ -47,6 +47,9 @@ export type LegacyWindow = Window & {
|
|
|
47
47
|
},
|
|
48
48
|
];
|
|
49
49
|
$previewWrapper: JQueryElement;
|
|
50
|
+
helpers: {
|
|
51
|
+
hasPro: () => boolean;
|
|
52
|
+
};
|
|
50
53
|
};
|
|
51
54
|
};
|
|
52
55
|
|
|
@@ -80,6 +83,7 @@ export declare class ElementView {
|
|
|
80
83
|
length: number;
|
|
81
84
|
findByIndex: ( index: number ) => ElementView;
|
|
82
85
|
each: ( callback: ( view: ElementView ) => void ) => void;
|
|
86
|
+
map: < T >( callback: ( view: ElementView ) => T ) => T[];
|
|
83
87
|
};
|
|
84
88
|
|
|
85
89
|
constructor( ...args: unknown[] );
|
|
@@ -216,7 +220,16 @@ type ToJSON< T > = {
|
|
|
216
220
|
|
|
217
221
|
type ContextMenuGroup = {
|
|
218
222
|
name: string;
|
|
219
|
-
actions:
|
|
223
|
+
actions: ContextMenuAction[];
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export type ContextMenuAction = {
|
|
227
|
+
name: string;
|
|
228
|
+
icon: string;
|
|
229
|
+
title: string | ( () => string );
|
|
230
|
+
shortcut?: string;
|
|
231
|
+
isEnabled: () => boolean;
|
|
232
|
+
callback: ( _: unknown, eventData: unknown ) => void;
|
|
220
233
|
};
|
|
221
234
|
|
|
222
235
|
export type ReplacementSettings = {
|
|
@@ -48,7 +48,16 @@ const SELECTORS_MAP: Record< StyleDefinitionType, string > = {
|
|
|
48
48
|
|
|
49
49
|
export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '' }: CreateStyleRendererArgs ) {
|
|
50
50
|
return async ( { styles, signal }: StyleRendererArgs ): Promise< StyleItem[] > => {
|
|
51
|
-
const
|
|
51
|
+
const seenIds = new Set< string >();
|
|
52
|
+
const uniqueStyles = styles.filter( ( style ) => {
|
|
53
|
+
if ( seenIds.has( style.id ) ) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
seenIds.add( style.id );
|
|
57
|
+
return true;
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
const stylesCssPromises = uniqueStyles.map( async ( style ) => {
|
|
52
61
|
const variantCssPromises = Object.values( style.variants ).map( async ( variant ) => {
|
|
53
62
|
const css = await propsToCss( { props: variant.props, resolve, signal } );
|
|
54
63
|
const customCss = customCssToString( variant.custom_css );
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getMediaAttachment } from '@elementor/wp-media';
|
|
2
|
+
|
|
3
|
+
import { videoSrcTransformer } from '../video-src-transformer';
|
|
4
|
+
|
|
5
|
+
jest.mock( '@elementor/wp-media' );
|
|
6
|
+
|
|
7
|
+
const mockedGetMediaAttachment = jest.mocked( getMediaAttachment );
|
|
8
|
+
|
|
9
|
+
describe( 'videoSrcTransformer', () => {
|
|
10
|
+
beforeEach( () => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
} );
|
|
13
|
+
|
|
14
|
+
it( 'returns url when only url is provided', async () => {
|
|
15
|
+
// Arrange.
|
|
16
|
+
const value = {
|
|
17
|
+
id: null,
|
|
18
|
+
url: 'https://example.com/video.mp4',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Act.
|
|
22
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
23
|
+
|
|
24
|
+
// Assert.
|
|
25
|
+
expect( result ).toEqual( {
|
|
26
|
+
id: null,
|
|
27
|
+
url: 'https://example.com/video.mp4',
|
|
28
|
+
} );
|
|
29
|
+
expect( mockedGetMediaAttachment ).not.toHaveBeenCalled();
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
it( 'resolves url from attachment when id is provided', async () => {
|
|
33
|
+
// Arrange.
|
|
34
|
+
mockedGetMediaAttachment.mockResolvedValue( {
|
|
35
|
+
url: 'https://example.com/resolved-video.mp4',
|
|
36
|
+
} as never );
|
|
37
|
+
const value = {
|
|
38
|
+
id: 123,
|
|
39
|
+
url: null,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Act.
|
|
43
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
44
|
+
|
|
45
|
+
// Assert.
|
|
46
|
+
expect( result ).toEqual( {
|
|
47
|
+
id: 123,
|
|
48
|
+
url: 'https://example.com/resolved-video.mp4',
|
|
49
|
+
} );
|
|
50
|
+
expect( mockedGetMediaAttachment ).toHaveBeenCalledWith( { id: 123 } );
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
it( 'uses attachment url over provided url when id exists', async () => {
|
|
54
|
+
// Arrange.
|
|
55
|
+
mockedGetMediaAttachment.mockResolvedValue( {
|
|
56
|
+
url: 'https://example.com/attachment-url.mp4',
|
|
57
|
+
} as never );
|
|
58
|
+
const value = {
|
|
59
|
+
id: 456,
|
|
60
|
+
url: 'https://example.com/original-url.mp4',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Act.
|
|
64
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
65
|
+
|
|
66
|
+
// Assert.
|
|
67
|
+
expect( result ).toEqual( {
|
|
68
|
+
id: 456,
|
|
69
|
+
url: 'https://example.com/attachment-url.mp4',
|
|
70
|
+
} );
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'falls back to provided url when attachment lookup fails', async () => {
|
|
74
|
+
// Arrange.
|
|
75
|
+
mockedGetMediaAttachment.mockResolvedValue( null as never );
|
|
76
|
+
const value = {
|
|
77
|
+
id: 789,
|
|
78
|
+
url: 'https://example.com/fallback.mp4',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Act.
|
|
82
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
83
|
+
|
|
84
|
+
// Assert.
|
|
85
|
+
expect( result ).toEqual( {
|
|
86
|
+
id: 789,
|
|
87
|
+
url: 'https://example.com/fallback.mp4',
|
|
88
|
+
} );
|
|
89
|
+
} );
|
|
90
|
+
|
|
91
|
+
it( 'handles empty object', async () => {
|
|
92
|
+
// Arrange.
|
|
93
|
+
const value = {} as { id: null; url: null };
|
|
94
|
+
|
|
95
|
+
// Act.
|
|
96
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
97
|
+
|
|
98
|
+
// Assert.
|
|
99
|
+
expect( result ).toEqual( {
|
|
100
|
+
id: null,
|
|
101
|
+
url: undefined,
|
|
102
|
+
} );
|
|
103
|
+
} );
|
|
104
|
+
|
|
105
|
+
it( 'handles both id and url being null', async () => {
|
|
106
|
+
// Arrange.
|
|
107
|
+
const value = {
|
|
108
|
+
id: null,
|
|
109
|
+
url: null,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Act.
|
|
113
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
114
|
+
|
|
115
|
+
// Assert.
|
|
116
|
+
expect( result ).toEqual( {
|
|
117
|
+
id: null,
|
|
118
|
+
url: null,
|
|
119
|
+
} );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'handles id of 0 as falsy (no attachment lookup)', async () => {
|
|
123
|
+
// Arrange.
|
|
124
|
+
const value = {
|
|
125
|
+
id: 0,
|
|
126
|
+
url: 'https://example.com/video.mp4',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Act.
|
|
130
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
131
|
+
|
|
132
|
+
// Assert.
|
|
133
|
+
expect( result ).toEqual( {
|
|
134
|
+
id: null,
|
|
135
|
+
url: 'https://example.com/video.mp4',
|
|
136
|
+
} );
|
|
137
|
+
expect( mockedGetMediaAttachment ).not.toHaveBeenCalled();
|
|
138
|
+
} );
|
|
139
|
+
|
|
140
|
+
it( 'handles undefined url with valid id', async () => {
|
|
141
|
+
// Arrange.
|
|
142
|
+
mockedGetMediaAttachment.mockResolvedValue( {
|
|
143
|
+
url: 'https://example.com/resolved.mp4',
|
|
144
|
+
} as never );
|
|
145
|
+
const value = {
|
|
146
|
+
id: 123,
|
|
147
|
+
url: undefined,
|
|
148
|
+
} as unknown as { id: number; url: null };
|
|
149
|
+
|
|
150
|
+
// Act.
|
|
151
|
+
const result = await videoSrcTransformer( value, { key: 'source' } );
|
|
152
|
+
|
|
153
|
+
// Assert.
|
|
154
|
+
expect( result ).toEqual( {
|
|
155
|
+
id: 123,
|
|
156
|
+
url: 'https://example.com/resolved.mp4',
|
|
157
|
+
} );
|
|
158
|
+
} );
|
|
159
|
+
} );
|