@elementor/editor-canvas 4.1.0-697 → 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.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -5
- package/dist/index.mjs +26 -5
- 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/legacy/create-nested-templated-element-type.ts +11 -2
- package/src/legacy/create-templated-element-type.ts +8 -0
- package/src/legacy/types.ts +1 -0
- package/src/renderers/__tests__/create-styles-renderer.test.ts +117 -0
- package/src/renderers/create-styles-renderer.ts +13 -3
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
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) {
|
|
@@ -2008,6 +2016,7 @@ function createTemplatedElementView({
|
|
|
2008
2016
|
this._lastResolvedSettingsHash = settingsHash;
|
|
2009
2017
|
const context = {
|
|
2010
2018
|
id: this.model.get("id"),
|
|
2019
|
+
interaction_id: this.getInteractionId(),
|
|
2011
2020
|
type,
|
|
2012
2021
|
settings,
|
|
2013
2022
|
base_styles: baseStylesDictionary
|
|
@@ -2042,6 +2051,11 @@ function createTemplatedElementView({
|
|
|
2042
2051
|
_openEditingPanel(options) {
|
|
2043
2052
|
this._doAfterRender(() => super._openEditingPanel(options));
|
|
2044
2053
|
}
|
|
2054
|
+
getInteractionId() {
|
|
2055
|
+
const originId = this.model.get("originId");
|
|
2056
|
+
const id = this.model.get("id");
|
|
2057
|
+
return originId ?? id;
|
|
2058
|
+
}
|
|
2045
2059
|
};
|
|
2046
2060
|
}
|
|
2047
2061
|
|
|
@@ -2074,10 +2088,11 @@ function createNestedTemplatedElementType({
|
|
|
2074
2088
|
}
|
|
2075
2089
|
function buildEditorAttributes(model) {
|
|
2076
2090
|
const id = model.get("id");
|
|
2091
|
+
const originId = model.get("originId");
|
|
2077
2092
|
const cid = model.cid ?? "";
|
|
2078
2093
|
const attrs = {
|
|
2079
2094
|
"data-model-cid": cid,
|
|
2080
|
-
"data-interaction-id": id,
|
|
2095
|
+
"data-interaction-id": originId ?? id,
|
|
2081
2096
|
"x-ignore": "true"
|
|
2082
2097
|
};
|
|
2083
2098
|
return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
@@ -2154,6 +2169,7 @@ function createNestedTemplatedElementView({
|
|
|
2154
2169
|
this._lastResolvedSettingsHash = settingsHash;
|
|
2155
2170
|
const context = {
|
|
2156
2171
|
id: model.get("id"),
|
|
2172
|
+
interaction_id: this.getInteractionId(),
|
|
2157
2173
|
type,
|
|
2158
2174
|
settings,
|
|
2159
2175
|
base_styles: baseStylesDictionary,
|
|
@@ -2283,6 +2299,11 @@ function createNestedTemplatedElementView({
|
|
|
2283
2299
|
},
|
|
2284
2300
|
_openEditingPanel(options) {
|
|
2285
2301
|
this._doAfterRender(() => parentOpenEditingPanel.call(this, options));
|
|
2302
|
+
},
|
|
2303
|
+
getInteractionId() {
|
|
2304
|
+
const originId = this.model.get("originId");
|
|
2305
|
+
const id = this.model.get("id");
|
|
2306
|
+
return originId ?? id;
|
|
2286
2307
|
}
|
|
2287
2308
|
});
|
|
2288
2309
|
}
|
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) {
|
|
@@ -1974,6 +1982,7 @@ function createTemplatedElementView({
|
|
|
1974
1982
|
this._lastResolvedSettingsHash = settingsHash;
|
|
1975
1983
|
const context = {
|
|
1976
1984
|
id: this.model.get("id"),
|
|
1985
|
+
interaction_id: this.getInteractionId(),
|
|
1977
1986
|
type,
|
|
1978
1987
|
settings,
|
|
1979
1988
|
base_styles: baseStylesDictionary
|
|
@@ -2008,6 +2017,11 @@ function createTemplatedElementView({
|
|
|
2008
2017
|
_openEditingPanel(options) {
|
|
2009
2018
|
this._doAfterRender(() => super._openEditingPanel(options));
|
|
2010
2019
|
}
|
|
2020
|
+
getInteractionId() {
|
|
2021
|
+
const originId = this.model.get("originId");
|
|
2022
|
+
const id = this.model.get("id");
|
|
2023
|
+
return originId ?? id;
|
|
2024
|
+
}
|
|
2011
2025
|
};
|
|
2012
2026
|
}
|
|
2013
2027
|
|
|
@@ -2040,10 +2054,11 @@ function createNestedTemplatedElementType({
|
|
|
2040
2054
|
}
|
|
2041
2055
|
function buildEditorAttributes(model) {
|
|
2042
2056
|
const id = model.get("id");
|
|
2057
|
+
const originId = model.get("originId");
|
|
2043
2058
|
const cid = model.cid ?? "";
|
|
2044
2059
|
const attrs = {
|
|
2045
2060
|
"data-model-cid": cid,
|
|
2046
|
-
"data-interaction-id": id,
|
|
2061
|
+
"data-interaction-id": originId ?? id,
|
|
2047
2062
|
"x-ignore": "true"
|
|
2048
2063
|
};
|
|
2049
2064
|
return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
@@ -2120,6 +2135,7 @@ function createNestedTemplatedElementView({
|
|
|
2120
2135
|
this._lastResolvedSettingsHash = settingsHash;
|
|
2121
2136
|
const context = {
|
|
2122
2137
|
id: model.get("id"),
|
|
2138
|
+
interaction_id: this.getInteractionId(),
|
|
2123
2139
|
type,
|
|
2124
2140
|
settings,
|
|
2125
2141
|
base_styles: baseStylesDictionary,
|
|
@@ -2249,6 +2265,11 @@ function createNestedTemplatedElementView({
|
|
|
2249
2265
|
},
|
|
2250
2266
|
_openEditingPanel(options) {
|
|
2251
2267
|
this._doAfterRender(() => parentOpenEditingPanel.call(this, options));
|
|
2268
|
+
},
|
|
2269
|
+
getInteractionId() {
|
|
2270
|
+
const originId = this.model.get("originId");
|
|
2271
|
+
const id = this.model.get("id");
|
|
2272
|
+
return originId ?? id;
|
|
2252
2273
|
}
|
|
2253
2274
|
});
|
|
2254
2275
|
}
|
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
|
|
|
@@ -61,13 +61,14 @@ export function createNestedTemplatedElementType( {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function buildEditorAttributes( model:
|
|
64
|
+
function buildEditorAttributes( model: ElementView[ 'model' ] ): string {
|
|
65
65
|
const id = model.get( 'id' );
|
|
66
|
+
const originId = model.get( 'originId' );
|
|
66
67
|
const cid = model.cid ?? '';
|
|
67
68
|
|
|
68
69
|
const attrs: Record< string, string > = {
|
|
69
70
|
'data-model-cid': cid,
|
|
70
|
-
'data-interaction-id': id,
|
|
71
|
+
'data-interaction-id': originId ?? id,
|
|
71
72
|
'x-ignore': 'true',
|
|
72
73
|
};
|
|
73
74
|
|
|
@@ -181,6 +182,7 @@ export function createNestedTemplatedElementView( {
|
|
|
181
182
|
|
|
182
183
|
const context = {
|
|
183
184
|
id: model.get( 'id' ),
|
|
185
|
+
interaction_id: this.getInteractionId(),
|
|
184
186
|
type,
|
|
185
187
|
settings,
|
|
186
188
|
base_styles: baseStylesDictionary,
|
|
@@ -361,5 +363,12 @@ export function createNestedTemplatedElementView( {
|
|
|
361
363
|
_openEditingPanel( options?: { scrollIntoView: boolean } ) {
|
|
362
364
|
this._doAfterRender( () => parentOpenEditingPanel.call( this, options ) );
|
|
363
365
|
},
|
|
366
|
+
|
|
367
|
+
getInteractionId() {
|
|
368
|
+
const originId = this.model.get( 'originId' );
|
|
369
|
+
const id = this.model.get( 'id' );
|
|
370
|
+
|
|
371
|
+
return originId ?? id;
|
|
372
|
+
},
|
|
364
373
|
} ) as unknown as typeof ElementView;
|
|
365
374
|
}
|
|
@@ -162,6 +162,7 @@ export function createTemplatedElementView( {
|
|
|
162
162
|
|
|
163
163
|
const context = {
|
|
164
164
|
id: this.model.get( 'id' ),
|
|
165
|
+
interaction_id: this.getInteractionId(),
|
|
165
166
|
type,
|
|
166
167
|
settings,
|
|
167
168
|
base_styles: baseStylesDictionary,
|
|
@@ -206,5 +207,12 @@ export function createTemplatedElementView( {
|
|
|
206
207
|
_openEditingPanel( options?: { scrollIntoView: boolean } ) {
|
|
207
208
|
this._doAfterRender( () => super._openEditingPanel( options ) );
|
|
208
209
|
}
|
|
210
|
+
|
|
211
|
+
getInteractionId() {
|
|
212
|
+
const originId = this.model.get( 'originId' );
|
|
213
|
+
const id = this.model.get( 'id' );
|
|
214
|
+
|
|
215
|
+
return originId ?? id;
|
|
216
|
+
}
|
|
209
217
|
};
|
|
210
218
|
}
|
package/src/legacy/types.ts
CHANGED
|
@@ -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
|
|