@elementor/editor-canvas 4.1.0-698 → 4.1.0-699
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.js +12 -4
- package/dist/index.mjs +12 -4
- package/package.json +18 -18
- package/src/hooks/__tests__/use-style-items.test.ts +51 -0
- package/src/hooks/use-style-items.ts +1 -1
- package/src/renderers/__tests__/create-styles-renderer.test.ts +117 -0
- package/src/renderers/create-styles-renderer.ts +13 -3
package/dist/index.js
CHANGED
|
@@ -833,14 +833,22 @@ var UnknownStyleStateError = (0, import_utils2.createError)({
|
|
|
833
833
|
var SELECTORS_MAP = {
|
|
834
834
|
class: "."
|
|
835
835
|
};
|
|
836
|
+
var DEFAULT_BREAKPOINT = "desktop";
|
|
837
|
+
var DEFAULT_STATE = "normal";
|
|
838
|
+
function getStyleUniqueKey(style) {
|
|
839
|
+
const breakpoint = style.variants[0]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
|
|
840
|
+
const state = style.variants[0]?.meta?.state ?? DEFAULT_STATE;
|
|
841
|
+
return `${style.id}-${breakpoint}-${state}`;
|
|
842
|
+
}
|
|
836
843
|
function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
|
|
837
844
|
return async ({ styles, signal }) => {
|
|
838
|
-
const
|
|
845
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
839
846
|
const uniqueStyles = styles.filter((style) => {
|
|
840
|
-
|
|
847
|
+
const key = getStyleUniqueKey(style);
|
|
848
|
+
if (seenKeys.has(key)) {
|
|
841
849
|
return false;
|
|
842
850
|
}
|
|
843
|
-
|
|
851
|
+
seenKeys.add(key);
|
|
844
852
|
return true;
|
|
845
853
|
});
|
|
846
854
|
const stylesCssPromises = uniqueStyles.map(async (style) => {
|
|
@@ -1025,7 +1033,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
1025
1033
|
});
|
|
1026
1034
|
return renderStyles({ styles: breakToBreakpoints(styles), signal }).then((rendered) => {
|
|
1027
1035
|
rebuildCache(cache, allStyles, rendered);
|
|
1028
|
-
return
|
|
1036
|
+
return getOrderedItems(cache);
|
|
1029
1037
|
});
|
|
1030
1038
|
}
|
|
1031
1039
|
function breakToBreakpoints(styles) {
|
package/dist/index.mjs
CHANGED
|
@@ -799,14 +799,22 @@ var UnknownStyleStateError = createError({
|
|
|
799
799
|
var SELECTORS_MAP = {
|
|
800
800
|
class: "."
|
|
801
801
|
};
|
|
802
|
+
var DEFAULT_BREAKPOINT = "desktop";
|
|
803
|
+
var DEFAULT_STATE = "normal";
|
|
804
|
+
function getStyleUniqueKey(style) {
|
|
805
|
+
const breakpoint = style.variants[0]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
|
|
806
|
+
const state = style.variants[0]?.meta?.state ?? DEFAULT_STATE;
|
|
807
|
+
return `${style.id}-${breakpoint}-${state}`;
|
|
808
|
+
}
|
|
802
809
|
function createStylesRenderer({ resolve, breakpoints, selectorPrefix = "" }) {
|
|
803
810
|
return async ({ styles, signal }) => {
|
|
804
|
-
const
|
|
811
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
805
812
|
const uniqueStyles = styles.filter((style) => {
|
|
806
|
-
|
|
813
|
+
const key = getStyleUniqueKey(style);
|
|
814
|
+
if (seenKeys.has(key)) {
|
|
807
815
|
return false;
|
|
808
816
|
}
|
|
809
|
-
|
|
817
|
+
seenKeys.add(key);
|
|
810
818
|
return true;
|
|
811
819
|
});
|
|
812
820
|
const stylesCssPromises = uniqueStyles.map(async (style) => {
|
|
@@ -991,7 +999,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
991
999
|
});
|
|
992
1000
|
return renderStyles({ styles: breakToBreakpoints(styles), signal }).then((rendered) => {
|
|
993
1001
|
rebuildCache(cache, allStyles, rendered);
|
|
994
|
-
return
|
|
1002
|
+
return getOrderedItems(cache);
|
|
995
1003
|
});
|
|
996
1004
|
}
|
|
997
1005
|
function breakToBreakpoints(styles) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.1.0-
|
|
4
|
+
"version": "4.1.0-699",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,24 +37,24 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.1.0-
|
|
41
|
-
"@elementor/editor-controls": "4.1.0-
|
|
42
|
-
"@elementor/editor-documents": "4.1.0-
|
|
43
|
-
"@elementor/editor-elements": "4.1.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.1.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.1.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.1.0-
|
|
47
|
-
"@elementor/editor-props": "4.1.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.1.0-
|
|
49
|
-
"@elementor/editor-styles": "4.1.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.1.0-
|
|
51
|
-
"@elementor/editor-ui": "4.1.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.1.0-
|
|
53
|
-
"@elementor/schema": "4.1.0-
|
|
54
|
-
"@elementor/twing": "4.1.0-
|
|
40
|
+
"@elementor/editor": "4.1.0-699",
|
|
41
|
+
"@elementor/editor-controls": "4.1.0-699",
|
|
42
|
+
"@elementor/editor-documents": "4.1.0-699",
|
|
43
|
+
"@elementor/editor-elements": "4.1.0-699",
|
|
44
|
+
"@elementor/editor-interactions": "4.1.0-699",
|
|
45
|
+
"@elementor/editor-mcp": "4.1.0-699",
|
|
46
|
+
"@elementor/editor-notifications": "4.1.0-699",
|
|
47
|
+
"@elementor/editor-props": "4.1.0-699",
|
|
48
|
+
"@elementor/editor-responsive": "4.1.0-699",
|
|
49
|
+
"@elementor/editor-styles": "4.1.0-699",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.1.0-699",
|
|
51
|
+
"@elementor/editor-ui": "4.1.0-699",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.1.0-699",
|
|
53
|
+
"@elementor/schema": "4.1.0-699",
|
|
54
|
+
"@elementor/twing": "4.1.0-699",
|
|
55
55
|
"@elementor/ui": "1.36.17",
|
|
56
|
-
"@elementor/utils": "4.1.0-
|
|
57
|
-
"@elementor/wp-media": "4.1.0-
|
|
56
|
+
"@elementor/utils": "4.1.0-699",
|
|
57
|
+
"@elementor/wp-media": "4.1.0-699",
|
|
58
58
|
"@floating-ui/react": "^0.27.5",
|
|
59
59
|
"@wordpress/i18n": "^5.13.0"
|
|
60
60
|
},
|
|
@@ -289,6 +289,57 @@ describe( 'useStyleItems', () => {
|
|
|
289
289
|
expect( result.current ).toHaveLength( 2 );
|
|
290
290
|
} );
|
|
291
291
|
|
|
292
|
+
it( 'should maintain breakpoint order after style update', async () => {
|
|
293
|
+
// Arrange.
|
|
294
|
+
const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
|
|
295
|
+
Promise.resolve(
|
|
296
|
+
styles.map( ( style: StyleDefinition ) => ( {
|
|
297
|
+
id: style.id,
|
|
298
|
+
breakpoint: style?.variants[ 0 ]?.meta.breakpoint || 'desktop',
|
|
299
|
+
} ) )
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
jest.mocked( useStyleRenderer ).mockReturnValue( renderStylesMock );
|
|
304
|
+
|
|
305
|
+
const mockProvider = createMockStylesProvider( { key: 'provider1', priority: 1 }, [
|
|
306
|
+
createMockStyleDefinitionWithVariants( {
|
|
307
|
+
id: 'style1',
|
|
308
|
+
variants: [
|
|
309
|
+
{ meta: { breakpoint: null, state: null }, props: { padding: '10px' }, custom_css: null },
|
|
310
|
+
{ meta: { breakpoint: 'tablet', state: null }, props: { padding: '8px' }, custom_css: null },
|
|
311
|
+
{ meta: { breakpoint: 'mobile', state: null }, props: { padding: '5px' }, custom_css: null },
|
|
312
|
+
],
|
|
313
|
+
} ),
|
|
314
|
+
] );
|
|
315
|
+
|
|
316
|
+
jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider ] );
|
|
317
|
+
|
|
318
|
+
// Act - initial render.
|
|
319
|
+
const { result } = renderHook( () => useStyleItems() );
|
|
320
|
+
|
|
321
|
+
await act( async () => {
|
|
322
|
+
mockProvider.actions.updateProps?.( {
|
|
323
|
+
id: 'style1',
|
|
324
|
+
meta: { breakpoint: 'tablet', state: null },
|
|
325
|
+
props: { padding: '12px' },
|
|
326
|
+
} );
|
|
327
|
+
} );
|
|
328
|
+
|
|
329
|
+
// Assert - items should be ordered by breakpoint (desktop, tablet, mobile).
|
|
330
|
+
const breakpointOrder = result.current.map( ( item ) => item.breakpoint );
|
|
331
|
+
expect( breakpointOrder ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
|
|
332
|
+
|
|
333
|
+
// Act - update again (should maintain order).
|
|
334
|
+
await act( async () => {
|
|
335
|
+
mockProvider.actions.update?.( { id: 'style1', label: 'Updated' } );
|
|
336
|
+
} );
|
|
337
|
+
|
|
338
|
+
// Assert - order should still be maintained.
|
|
339
|
+
const breakpointOrderAfterUpdate = result.current.map( ( item ) => item.breakpoint );
|
|
340
|
+
expect( breakpointOrderAfterUpdate ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
|
|
341
|
+
} );
|
|
342
|
+
|
|
292
343
|
it( 'should only re-render changed styles on differential update', async () => {
|
|
293
344
|
// Arrange.
|
|
294
345
|
const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
|
|
@@ -191,7 +191,7 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
|
|
|
191
191
|
return renderStyles( { styles: breakToBreakpoints( styles ), signal } ).then( ( rendered ) => {
|
|
192
192
|
rebuildCache( cache, allStyles, rendered );
|
|
193
193
|
|
|
194
|
-
return
|
|
194
|
+
return getOrderedItems( cache );
|
|
195
195
|
} );
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -118,6 +118,123 @@ describe( 'renderStyles', () => {
|
|
|
118
118
|
} );
|
|
119
119
|
} );
|
|
120
120
|
|
|
121
|
+
describe( 'breakpoint deduplication', () => {
|
|
122
|
+
it( 'should render all breakpoints when same id has multiple breakpoint variants', async () => {
|
|
123
|
+
// Arrange - simulates output from breakToBreakpoints in use-style-items.
|
|
124
|
+
const desktopStyle: RendererStyleDefinition = {
|
|
125
|
+
id: 'button-style',
|
|
126
|
+
type: 'class',
|
|
127
|
+
cssName: 'e-button',
|
|
128
|
+
label: 'Button',
|
|
129
|
+
variants: [ { meta: { breakpoint: null, state: null }, props: { 'font-size': '16px' }, custom_css: null } ],
|
|
130
|
+
};
|
|
131
|
+
const tabletStyle: RendererStyleDefinition = {
|
|
132
|
+
...desktopStyle,
|
|
133
|
+
variants: [
|
|
134
|
+
{ meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
const mobileStyle: RendererStyleDefinition = {
|
|
138
|
+
...desktopStyle,
|
|
139
|
+
variants: [
|
|
140
|
+
{ meta: { breakpoint: 'mobile', state: null }, props: { 'font-size': '12px' }, custom_css: null },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const resolve = jest.fn( ( { props } ) => props );
|
|
145
|
+
const renderStyles = createStylesRenderer( {
|
|
146
|
+
breakpoints: {
|
|
147
|
+
tablet: { width: 992, type: 'max-width' },
|
|
148
|
+
mobile: { width: 768, type: 'max-width' },
|
|
149
|
+
} as BreakpointsMap,
|
|
150
|
+
resolve,
|
|
151
|
+
} );
|
|
152
|
+
|
|
153
|
+
// Act.
|
|
154
|
+
const result = await renderStyles( { styles: [ desktopStyle, tabletStyle, mobileStyle ] } );
|
|
155
|
+
|
|
156
|
+
// Assert - all three breakpoints must be rendered (previously tablet/mobile were dropped).
|
|
157
|
+
expect( result ).toHaveLength( 3 );
|
|
158
|
+
expect( result.map( ( r ) => r.breakpoint ) ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
|
|
159
|
+
expect( result[ 0 ].value ).toContain( 'font-size:16px' );
|
|
160
|
+
expect( result[ 1 ].value ).toContain( '@media(max-width:992px)' );
|
|
161
|
+
expect( result[ 1 ].value ).toContain( 'font-size:14px' );
|
|
162
|
+
expect( result[ 2 ].value ).toContain( '@media(max-width:768px)' );
|
|
163
|
+
expect( result[ 2 ].value ).toContain( 'font-size:12px' );
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
it( 'should deduplicate same id + breakpoint + state combinations', async () => {
|
|
167
|
+
// Arrange - two styles with same id, breakpoint, and state should dedupe.
|
|
168
|
+
const style1: RendererStyleDefinition = {
|
|
169
|
+
id: 'button-style',
|
|
170
|
+
type: 'class',
|
|
171
|
+
cssName: 'e-button',
|
|
172
|
+
label: 'Button',
|
|
173
|
+
variants: [
|
|
174
|
+
{ meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
const style2: RendererStyleDefinition = {
|
|
178
|
+
...style1,
|
|
179
|
+
variants: [
|
|
180
|
+
{ meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '16px' }, custom_css: null },
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const resolve = jest.fn( ( { props } ) => props );
|
|
185
|
+
const renderStyles = createStylesRenderer( {
|
|
186
|
+
breakpoints: {
|
|
187
|
+
tablet: { width: 992, type: 'max-width' },
|
|
188
|
+
} as BreakpointsMap,
|
|
189
|
+
resolve,
|
|
190
|
+
} );
|
|
191
|
+
|
|
192
|
+
// Act.
|
|
193
|
+
const result = await renderStyles( { styles: [ style1, style2 ] } );
|
|
194
|
+
|
|
195
|
+
// Assert - should only render first occurrence.
|
|
196
|
+
expect( result ).toHaveLength( 1 );
|
|
197
|
+
expect( result[ 0 ].value ).toContain( 'font-size:14px' );
|
|
198
|
+
} );
|
|
199
|
+
|
|
200
|
+
it( 'should render separately when same id + breakpoint have different states', async () => {
|
|
201
|
+
// Arrange - same id and breakpoint but different states should NOT dedupe.
|
|
202
|
+
const normalStyle: RendererStyleDefinition = {
|
|
203
|
+
id: 'button-style',
|
|
204
|
+
type: 'class',
|
|
205
|
+
cssName: 'e-button',
|
|
206
|
+
label: 'Button',
|
|
207
|
+
variants: [
|
|
208
|
+
{ meta: { breakpoint: 'tablet', state: null }, props: { 'font-size': '14px' }, custom_css: null },
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
const hoverStyle: RendererStyleDefinition = {
|
|
212
|
+
...normalStyle,
|
|
213
|
+
variants: [
|
|
214
|
+
{ meta: { breakpoint: 'tablet', state: 'hover' }, props: { 'font-size': '16px' }, custom_css: null },
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const resolve = jest.fn( ( { props } ) => props );
|
|
219
|
+
const renderStyles = createStylesRenderer( {
|
|
220
|
+
breakpoints: {
|
|
221
|
+
tablet: { width: 992, type: 'max-width' },
|
|
222
|
+
} as BreakpointsMap,
|
|
223
|
+
resolve,
|
|
224
|
+
} );
|
|
225
|
+
|
|
226
|
+
// Act.
|
|
227
|
+
const result = await renderStyles( { styles: [ normalStyle, hoverStyle ] } );
|
|
228
|
+
|
|
229
|
+
// Assert - both should be rendered since states differ.
|
|
230
|
+
expect( result ).toHaveLength( 2 );
|
|
231
|
+
expect( result[ 0 ].state ).toBeNull();
|
|
232
|
+
expect( result[ 0 ].value ).toContain( 'font-size:14px' );
|
|
233
|
+
expect( result[ 1 ].state ).toBe( 'hover' );
|
|
234
|
+
expect( result[ 1 ].value ).toContain( 'font-size:16px' );
|
|
235
|
+
} );
|
|
236
|
+
} );
|
|
237
|
+
|
|
121
238
|
describe( 'custom_css rendering', () => {
|
|
122
239
|
it( 'should not render custom_css if raw is empty', async () => {
|
|
123
240
|
// Arrange.
|
|
@@ -46,14 +46,24 @@ const SELECTORS_MAP: Record< StyleDefinitionType, string > = {
|
|
|
46
46
|
class: '.',
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const DEFAULT_BREAKPOINT = 'desktop';
|
|
50
|
+
const DEFAULT_STATE = 'normal';
|
|
51
|
+
|
|
52
|
+
function getStyleUniqueKey( style: RendererStyleDefinition ): string {
|
|
53
|
+
const breakpoint = style.variants[ 0 ]?.meta?.breakpoint ?? DEFAULT_BREAKPOINT;
|
|
54
|
+
const state = style.variants[ 0 ]?.meta?.state ?? DEFAULT_STATE;
|
|
55
|
+
return `${ style.id }-${ breakpoint }-${ state }`;
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '' }: CreateStyleRendererArgs ) {
|
|
50
59
|
return async ( { styles, signal }: StyleRendererArgs ): Promise< StyleItem[] > => {
|
|
51
|
-
const
|
|
60
|
+
const seenKeys = new Set< string >();
|
|
52
61
|
const uniqueStyles = styles.filter( ( style ) => {
|
|
53
|
-
|
|
62
|
+
const key = getStyleUniqueKey( style );
|
|
63
|
+
if ( seenKeys.has( key ) ) {
|
|
54
64
|
return false;
|
|
55
65
|
}
|
|
56
|
-
|
|
66
|
+
seenKeys.add( key );
|
|
57
67
|
return true;
|
|
58
68
|
} );
|
|
59
69
|
|