@elementor/editor-styles-repository 4.3.0-beta2 → 4.3.0

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 CHANGED
@@ -88,9 +88,19 @@ var DEFAULT_CAPABILITIES = {
88
88
  update: true,
89
89
  updateProps: true
90
90
  };
91
+ var DENIED_CAPABILITIES = {
92
+ create: false,
93
+ delete: false,
94
+ update: false,
95
+ updateProps: false
96
+ };
91
97
  var useUserStylesCapability = () => {
92
98
  const { capabilities } = (0, import_editor_current_user.useCurrentUserCapabilities)();
99
+ const hasContentOnlyAccess = (0, import_editor_current_user.useHasContentOnlyAccess)();
93
100
  const userCan = (providerKey) => {
101
+ if (hasContentOnlyAccess) {
102
+ return DENIED_CAPABILITIES;
103
+ }
94
104
  const provider = stylesRepository.getProviderByKey(providerKey);
95
105
  if (!provider?.capabilities) {
96
106
  return DEFAULT_CAPABILITIES;
@@ -197,7 +207,25 @@ var documentElementsStylesProvider = createStylesProvider({
197
207
  },
198
208
  priority: 50,
199
209
  isPregeneratedLink: ({ id }) => PREGENERATED_LINK_PATTERN.test(id),
200
- subscribe: (cb) => (0, import_editor_v1_adapters.__privateListenTo)(import_editor_elements.styleRerenderEvents, () => cb()),
210
+ subscribe: (cb) => {
211
+ let scheduledFrame = null;
212
+ const unsubscribe = (0, import_editor_v1_adapters.__privateListenTo)(import_editor_elements.styleRerenderEvents, () => {
213
+ if (scheduledFrame !== null) {
214
+ return;
215
+ }
216
+ scheduledFrame = requestAnimationFrame(() => {
217
+ scheduledFrame = null;
218
+ cb();
219
+ });
220
+ });
221
+ return () => {
222
+ if (scheduledFrame !== null) {
223
+ cancelAnimationFrame(scheduledFrame);
224
+ scheduledFrame = null;
225
+ }
226
+ unsubscribe();
227
+ };
228
+ },
201
229
  actions: {
202
230
  all: (meta = {}) => {
203
231
  let elements = (0, import_editor_elements.getElements)();
package/dist/index.mjs CHANGED
@@ -45,16 +45,26 @@ function useProviders() {
45
45
  import { useMemo } from "react";
46
46
 
47
47
  // src/hooks/use-user-styles-capability.ts
48
- import { useCurrentUserCapabilities } from "@elementor/editor-current-user";
48
+ import { useCurrentUserCapabilities, useHasContentOnlyAccess } from "@elementor/editor-current-user";
49
49
  var DEFAULT_CAPABILITIES = {
50
50
  create: true,
51
51
  delete: true,
52
52
  update: true,
53
53
  updateProps: true
54
54
  };
55
+ var DENIED_CAPABILITIES = {
56
+ create: false,
57
+ delete: false,
58
+ update: false,
59
+ updateProps: false
60
+ };
55
61
  var useUserStylesCapability = () => {
56
62
  const { capabilities } = useCurrentUserCapabilities();
63
+ const hasContentOnlyAccess = useHasContentOnlyAccess();
57
64
  const userCan = (providerKey) => {
65
+ if (hasContentOnlyAccess) {
66
+ return DENIED_CAPABILITIES;
67
+ }
58
68
  const provider = stylesRepository.getProviderByKey(providerKey);
59
69
  if (!provider?.capabilities) {
60
70
  return DEFAULT_CAPABILITIES;
@@ -167,7 +177,25 @@ var documentElementsStylesProvider = createStylesProvider({
167
177
  },
168
178
  priority: 50,
169
179
  isPregeneratedLink: ({ id }) => PREGENERATED_LINK_PATTERN.test(id),
170
- subscribe: (cb) => listenTo(styleRerenderEvents, () => cb()),
180
+ subscribe: (cb) => {
181
+ let scheduledFrame = null;
182
+ const unsubscribe = listenTo(styleRerenderEvents, () => {
183
+ if (scheduledFrame !== null) {
184
+ return;
185
+ }
186
+ scheduledFrame = requestAnimationFrame(() => {
187
+ scheduledFrame = null;
188
+ cb();
189
+ });
190
+ });
191
+ return () => {
192
+ if (scheduledFrame !== null) {
193
+ cancelAnimationFrame(scheduledFrame);
194
+ scheduledFrame = null;
195
+ }
196
+ unsubscribe();
197
+ };
198
+ },
171
199
  actions: {
172
200
  all: (meta = {}) => {
173
201
  let elements = getElements();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-styles-repository",
3
3
  "description": "Elementor Editor Styles Repository",
4
- "version": "4.3.0-beta2",
4
+ "version": "4.3.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -33,13 +33,13 @@
33
33
  "dev": "tsup --config=../../tsup.dev.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@elementor/editor-current-user": "4.3.0-beta2",
37
- "@elementor/editor-elements": "4.3.0-beta2",
38
- "@elementor/editor-props": "4.3.0-beta2",
39
- "@elementor/editor-styles": "4.3.0-beta2",
40
- "@elementor/editor-v1-adapters": "4.3.0-beta2",
41
- "@elementor/schema": "4.3.0-beta2",
42
- "@elementor/utils": "4.3.0-beta2",
36
+ "@elementor/editor-current-user": "4.3.0",
37
+ "@elementor/editor-elements": "4.3.0",
38
+ "@elementor/editor-props": "4.3.0",
39
+ "@elementor/editor-styles": "4.3.0",
40
+ "@elementor/editor-v1-adapters": "4.3.0",
41
+ "@elementor/schema": "4.3.0",
42
+ "@elementor/utils": "4.3.0",
43
43
  "@wordpress/i18n": "^5.13.0"
44
44
  },
45
45
  "peerDependencies": {
@@ -1,12 +1,24 @@
1
1
  import { createMockElement, createMockStyleDefinition } from 'test-utils';
2
- import { getCurrentDocumentId, getElements, getElementStyles, updateElementStyle } from '@elementor/editor-elements';
2
+ import {
3
+ ELEMENT_STYLE_CHANGE_EVENT,
4
+ getCurrentDocumentId,
5
+ getElements,
6
+ getElementStyles,
7
+ updateElementStyle,
8
+ } from '@elementor/editor-elements';
3
9
  import type { StyleDefinition } from '@elementor/editor-styles';
10
+ import { __privateListenTo as listenTo } from '@elementor/editor-v1-adapters';
4
11
 
5
12
  import { InvalidElementsStyleProviderMetaError } from '../errors';
6
13
  import { documentElementsStylesProvider } from '../providers/document-elements-styles-provider';
7
14
 
8
15
  jest.mock( '@elementor/editor-elements' );
9
16
 
17
+ jest.mock( '@elementor/editor-v1-adapters', () => ( {
18
+ ...jest.requireActual( '@elementor/editor-v1-adapters' ),
19
+ __privateListenTo: jest.fn(),
20
+ } ) );
21
+
10
22
  describe( 'documentElementsStylesProvider', () => {
11
23
  beforeEach( () => {
12
24
  jest.mocked( getElements ).mockReturnValue( [
@@ -155,4 +167,94 @@ describe( 'documentElementsStylesProvider', () => {
155
167
  )
156
168
  ).toThrow( new InvalidElementsStyleProviderMetaError() );
157
169
  } );
170
+
171
+ describe( 'subscribe', () => {
172
+ const setupListener = () => {
173
+ const unsubscribeFromEvents = jest.fn();
174
+ let emit = () => {};
175
+
176
+ jest.mocked( listenTo ).mockImplementation( ( _events, callback ) => {
177
+ emit = () =>
178
+ callback( {
179
+ type: 'window-event',
180
+ event: ELEMENT_STYLE_CHANGE_EVENT,
181
+ originalEvent: new CustomEvent( ELEMENT_STYLE_CHANGE_EVENT ),
182
+ } );
183
+
184
+ return unsubscribeFromEvents;
185
+ } );
186
+
187
+ const frames: FrameRequestCallback[] = [];
188
+
189
+ jest.spyOn( window, 'requestAnimationFrame' ).mockImplementation( ( frame: FrameRequestCallback ) => {
190
+ frames.push( frame );
191
+
192
+ return frames.length;
193
+ } );
194
+
195
+ return {
196
+ emitStyleRerenderEvent: () => emit(),
197
+ runFrame: ( index: number ) => frames[ index ]( 0 ),
198
+ frames: () => frames,
199
+ unsubscribeFromEvents,
200
+ };
201
+ };
202
+
203
+ it( 'should notify subscribers once per frame regardless of the number of events', () => {
204
+ // Arrange.
205
+ const listener = setupListener();
206
+ const subscriber = jest.fn();
207
+
208
+ documentElementsStylesProvider.subscribe( subscriber );
209
+
210
+ // Act.
211
+ listener.emitStyleRerenderEvent();
212
+ listener.emitStyleRerenderEvent();
213
+ listener.emitStyleRerenderEvent();
214
+
215
+ // Assert.
216
+ expect( listener.frames() ).toHaveLength( 1 );
217
+ expect( subscriber ).not.toHaveBeenCalled();
218
+
219
+ // Act.
220
+ listener.runFrame( 0 );
221
+
222
+ // Assert.
223
+ expect( subscriber ).toHaveBeenCalledTimes( 1 );
224
+ } );
225
+
226
+ it( 'should schedule a new frame after the pending one ran', () => {
227
+ // Arrange.
228
+ const listener = setupListener();
229
+ const subscriber = jest.fn();
230
+
231
+ documentElementsStylesProvider.subscribe( subscriber );
232
+
233
+ // Act.
234
+ listener.emitStyleRerenderEvent();
235
+ listener.runFrame( 0 );
236
+ listener.emitStyleRerenderEvent();
237
+ listener.runFrame( 1 );
238
+
239
+ // Assert.
240
+ expect( subscriber ).toHaveBeenCalledTimes( 2 );
241
+ } );
242
+
243
+ it( 'should cancel a pending frame when unsubscribing', () => {
244
+ // Arrange.
245
+ const listener = setupListener();
246
+ const subscriber = jest.fn();
247
+ const cancelAnimationFrameSpy = jest.spyOn( window, 'cancelAnimationFrame' ).mockImplementation();
248
+
249
+ const unsubscribe = documentElementsStylesProvider.subscribe( subscriber );
250
+
251
+ // Act.
252
+ listener.emitStyleRerenderEvent();
253
+ unsubscribe();
254
+
255
+ // Assert.
256
+ expect( cancelAnimationFrameSpy ).toHaveBeenCalledTimes( 1 );
257
+ expect( listener.unsubscribeFromEvents ).toHaveBeenCalledTimes( 1 );
258
+ } );
259
+ } );
158
260
  } );
@@ -0,0 +1,68 @@
1
+ import { useCurrentUserCapabilities, useHasContentOnlyAccess } from '@elementor/editor-current-user';
2
+ import { renderHook } from '@testing-library/react';
3
+
4
+ import { documentElementsStylesProvider } from '../../providers/document-elements-styles-provider';
5
+ import { stylesRepository } from '../../styles-repository';
6
+ import { type StylesProvider } from '../../types';
7
+ import { useUserStylesCapability } from '../use-user-styles-capability';
8
+
9
+ jest.mock( '@elementor/editor-current-user' );
10
+ jest.mock( '../../styles-repository' );
11
+
12
+ const UPDATE_CAPABILITY = 'update_style';
13
+ const PROVIDER_WITH_CAPABILITIES_KEY = 'provider-with-capabilities';
14
+
15
+ const providerWithCapabilities: Pick< StylesProvider, 'getKey' | 'capabilities' > = {
16
+ getKey: () => PROVIDER_WITH_CAPABILITIES_KEY,
17
+ capabilities: {
18
+ create: UPDATE_CAPABILITY,
19
+ delete: UPDATE_CAPABILITY,
20
+ update: UPDATE_CAPABILITY,
21
+ updateProps: UPDATE_CAPABILITY,
22
+ },
23
+ };
24
+
25
+ const deniedCapabilities = {
26
+ create: false,
27
+ delete: false,
28
+ update: false,
29
+ updateProps: false,
30
+ };
31
+
32
+ describe( 'useUserStylesCapability', () => {
33
+ beforeEach( () => {
34
+ jest.mocked( useHasContentOnlyAccess ).mockReturnValue( false );
35
+
36
+ jest.mocked( useCurrentUserCapabilities ).mockReturnValue( {
37
+ capabilities: [ UPDATE_CAPABILITY ],
38
+ canUser: ( capability ) => capability === UPDATE_CAPABILITY,
39
+ isAdmin: false,
40
+ } );
41
+ } );
42
+
43
+ it( 'should deny all capabilities for content-only users on a provider without capabilities', () => {
44
+ // Arrange
45
+ jest.mocked( useHasContentOnlyAccess ).mockReturnValue( true );
46
+
47
+ jest.mocked( stylesRepository.getProviderByKey ).mockReturnValue( documentElementsStylesProvider );
48
+
49
+ // Act
50
+ const { result } = renderHook( () => useUserStylesCapability() );
51
+
52
+ // Assert
53
+ expect( result.current.userCan( 'document-elements-42' ) ).toEqual( deniedCapabilities );
54
+ } );
55
+
56
+ it( 'should deny all capabilities for content-only users on a provider with capabilities', () => {
57
+ // Arrange
58
+ jest.mocked( useHasContentOnlyAccess ).mockReturnValue( true );
59
+
60
+ jest.mocked( stylesRepository.getProviderByKey ).mockReturnValue( providerWithCapabilities as StylesProvider );
61
+
62
+ // Act
63
+ const { result } = renderHook( () => useUserStylesCapability() );
64
+
65
+ // Assert
66
+ expect( result.current.userCan( PROVIDER_WITH_CAPABILITIES_KEY ) ).toEqual( deniedCapabilities );
67
+ } );
68
+ } );
@@ -1,4 +1,4 @@
1
- import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
1
+ import { useCurrentUserCapabilities, useHasContentOnlyAccess } from '@elementor/editor-current-user';
2
2
 
3
3
  import { stylesRepository } from '../styles-repository';
4
4
  import { type UserCapabilities } from '../types';
@@ -14,10 +14,22 @@ const DEFAULT_CAPABILITIES: UserCan = {
14
14
  updateProps: true,
15
15
  };
16
16
 
17
+ const DENIED_CAPABILITIES: UserCan = {
18
+ create: false,
19
+ delete: false,
20
+ update: false,
21
+ updateProps: false,
22
+ };
23
+
17
24
  export const useUserStylesCapability = () => {
18
25
  const { capabilities } = useCurrentUserCapabilities();
26
+ const hasContentOnlyAccess = useHasContentOnlyAccess();
19
27
 
20
28
  const userCan = ( providerKey: string ): UserCan => {
29
+ if ( hasContentOnlyAccess ) {
30
+ return DENIED_CAPABILITIES;
31
+ }
32
+
21
33
  const provider = stylesRepository.getProviderByKey( providerKey );
22
34
 
23
35
  if ( ! provider?.capabilities ) {
@@ -30,7 +30,29 @@ export const documentElementsStylesProvider = createStylesProvider( {
30
30
  },
31
31
  priority: 50,
32
32
  isPregeneratedLink: ( { id } ) => PREGENERATED_LINK_PATTERN.test( id ),
33
- subscribe: ( cb ) => listenTo( styleRerenderEvents, () => cb() ),
33
+ subscribe: ( cb ) => {
34
+ let scheduledFrame: number | null = null;
35
+
36
+ const unsubscribe = listenTo( styleRerenderEvents, () => {
37
+ if ( scheduledFrame !== null ) {
38
+ return;
39
+ }
40
+
41
+ scheduledFrame = requestAnimationFrame( () => {
42
+ scheduledFrame = null;
43
+ cb();
44
+ } );
45
+ } );
46
+
47
+ return () => {
48
+ if ( scheduledFrame !== null ) {
49
+ cancelAnimationFrame( scheduledFrame );
50
+ scheduledFrame = null;
51
+ }
52
+
53
+ unsubscribe();
54
+ };
55
+ },
34
56
  actions: {
35
57
  all: ( meta = {} ) => {
36
58
  let elements = getElements();