@elementor/editor-canvas 0.15.4 → 0.18.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.
Files changed (38) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +61 -0
  3. package/dist/index.d.mts +6 -1
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +455 -289
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +445 -277
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +8 -7
  10. package/src/__tests__/styles-prop-resolver.test.ts +1 -0
  11. package/src/components/__tests__/style-renderer.test.tsx +65 -0
  12. package/src/components/style-renderer.tsx +30 -0
  13. package/src/hooks/__tests__/use-style-items.test.ts +133 -0
  14. package/src/hooks/__tests__/use-style-prop-resolver.test.ts +51 -0
  15. package/src/hooks/use-floating-on-element.ts +2 -2
  16. package/src/hooks/use-on-mount.ts +13 -0
  17. package/src/hooks/use-style-items.ts +78 -0
  18. package/src/hooks/use-style-prop-resolver.ts +22 -0
  19. package/src/hooks/use-style-renderer.ts +19 -0
  20. package/src/index.ts +2 -3
  21. package/src/init.tsx +9 -2
  22. package/src/legacy/create-element-type.ts +14 -0
  23. package/src/legacy/create-templated-element-type.ts +1 -1
  24. package/src/legacy/types.ts +9 -0
  25. package/src/prevent-link-in-link-commands.ts +132 -0
  26. package/src/renderers/__tests__/{render-styles.test.ts → create-styles-renderer.test.ts} +35 -19
  27. package/src/renderers/{render-styles.ts → create-styles-renderer.ts} +35 -32
  28. package/src/style-commands/__tests__/paste-style.test.ts +5 -5
  29. package/src/style-commands/__tests__/reset-style.test.ts +2 -2
  30. package/src/style-commands/undoable-actions/paste-element-style.ts +3 -3
  31. package/src/style-commands/undoable-actions/reset-element-style.ts +2 -2
  32. package/src/sync/{get-canvas-iframe-body.ts → get-canvas-iframe-head.ts} +2 -2
  33. package/src/sync/types.ts +7 -0
  34. package/src/utils/abort-previous-runs.ts +16 -0
  35. package/src/__tests__/init-styles-renderer.test.ts +0 -112
  36. package/src/init-styles-renderer.ts +0 -84
  37. /package/src/{legacy → utils}/__tests__/signalized-process.test.ts +0 -0
  38. /package/src/{legacy → utils}/signalized-process.ts +0 -0
@@ -1,15 +1,25 @@
1
- import { type Props } from '@elementor/editor-props';
1
+ import type { Props } from '@elementor/editor-props';
2
2
  import { type Breakpoint, type BreakpointsMap } from '@elementor/editor-responsive';
3
3
  import { type StyleDefinition, type StyleDefinitionState, type StyleDefinitionType } from '@elementor/editor-styles';
4
4
 
5
5
  import { type PropsResolver } from './create-props-resolver';
6
6
  import { UnknownStyleTypeError } from './errors';
7
7
 
8
- type RenderParams = {
8
+ export type StyleItem = {
9
+ id: string;
10
+ value: string;
11
+ };
12
+
13
+ export type StyleRenderer = ReturnType< typeof createStylesRenderer >;
14
+
15
+ type CreateStyleRendererArgs = {
9
16
  resolve: PropsResolver;
10
- styles: StyleDefinition[];
11
17
  breakpoints: BreakpointsMap;
12
18
  selectorPrefix?: string;
19
+ };
20
+
21
+ type StyleRendererArgs = {
22
+ styles: StyleDefinition[];
13
23
  signal?: AbortSignal;
14
24
  };
15
25
 
@@ -23,33 +33,30 @@ const SELECTORS_MAP: Record< StyleDefinitionType, string > = {
23
33
  class: '.',
24
34
  };
25
35
 
26
- export default async function renderStyles( {
27
- resolve,
28
- styles,
29
- breakpoints,
30
- selectorPrefix = '',
31
- signal,
32
- }: RenderParams ) {
33
- const stylesCssPromises = styles.map( async ( style ) => {
34
- const variantCssPromises = Object.values( style.variants ).map( async ( variant ) => {
35
- const css = await propsToCss( { props: variant.props, resolve, signal } );
36
-
37
- return createStyleWrapper()
38
- .forStyle( style )
39
- .withPrefix( selectorPrefix )
40
- .withState( variant.meta.state )
41
- .withMediaQuery( variant.meta.breakpoint ? breakpoints[ variant.meta.breakpoint ] : null )
42
- .wrap( css );
36
+ export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '' }: CreateStyleRendererArgs ) {
37
+ return async ( { styles, signal }: StyleRendererArgs ): Promise< StyleItem[] > => {
38
+ const stylesCssPromises = styles.map( async ( style ) => {
39
+ const variantCssPromises = Object.values( style.variants ).map( async ( variant ) => {
40
+ const css = await propsToCss( { props: variant.props, resolve, signal } );
41
+
42
+ return createStyleWrapper()
43
+ .forStyle( style )
44
+ .withPrefix( selectorPrefix )
45
+ .withState( variant.meta.state )
46
+ .withMediaQuery( variant.meta.breakpoint ? breakpoints[ variant.meta.breakpoint ] : null )
47
+ .wrap( css );
48
+ } );
49
+
50
+ const variantsCss = await Promise.all( variantCssPromises );
51
+
52
+ return {
53
+ id: style.id,
54
+ value: variantsCss.join( '' ),
55
+ };
43
56
  } );
44
57
 
45
- const variantsCss = await Promise.all( variantCssPromises );
46
-
47
- return wrapCssWithStyleElement( style.id, variantsCss.join( '' ) );
48
- } );
49
-
50
- const stylesCss = await Promise.all( stylesCssPromises );
51
-
52
- return stylesCss.join( '' );
58
+ return await Promise.all( stylesCssPromises );
59
+ };
53
60
  }
54
61
 
55
62
  function createStyleWrapper( value: string = '', wrapper?: ( css: string ) => string ) {
@@ -107,7 +114,3 @@ async function propsToCss( { props, resolve, signal }: PropsToCssArgs ) {
107
114
  }, [] )
108
115
  .join( '' );
109
116
  }
110
-
111
- function wrapCssWithStyleElement( id: string, css: string ) {
112
- return `<style data-style-id="${ id }">${ css }</style>`;
113
- }
@@ -11,7 +11,7 @@ import {
11
11
  updateElementStyle,
12
12
  } from '@elementor/editor-elements';
13
13
  import { type StyleDefinition } from '@elementor/editor-styles';
14
- import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
14
+ import { ELEMENTS_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
15
15
 
16
16
  import { initPasteStyleCommand } from '../paste-style';
17
17
  import { getClassesProp, getClipboardElements, isAtomicWidget } from '../utils';
@@ -132,7 +132,7 @@ describe( 'pasteStyles', () => {
132
132
  expect( createElementStyle ).toHaveBeenCalledWith( {
133
133
  elementId: 'test-container',
134
134
  styleId: 's-1',
135
- label: LOCAL_STYLES_RESERVED_LABEL,
135
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
136
136
  classesProp: 'classes',
137
137
  meta: {
138
138
  breakpoint: null,
@@ -195,7 +195,7 @@ describe( 'pasteStyles', () => {
195
195
  // Assert.
196
196
  expect( createElementStyle ).toHaveBeenCalledWith( {
197
197
  elementId: 'test-container',
198
- label: LOCAL_STYLES_RESERVED_LABEL,
198
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
199
199
  classesProp: 'classes',
200
200
  meta: {
201
201
  breakpoint: null,
@@ -461,7 +461,7 @@ describe( 'pasteStyles', () => {
461
461
  // Assert.
462
462
  expect( createElementStyle ).toHaveBeenCalledWith( {
463
463
  elementId: 'test-container-1',
464
- label: LOCAL_STYLES_RESERVED_LABEL,
464
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
465
465
  classesProp: 'classes',
466
466
  meta: {
467
467
  breakpoint: null,
@@ -522,7 +522,7 @@ describe( 'pasteStyles', () => {
522
522
 
523
523
  expect( createElementStyle ).toHaveBeenCalledWith( {
524
524
  elementId: 'test-container-2',
525
- label: LOCAL_STYLES_RESERVED_LABEL,
525
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
526
526
  classesProp: 'classes',
527
527
  styleId: 's-1',
528
528
  meta: {
@@ -5,7 +5,7 @@ import {
5
5
  mockHistoryManager,
6
6
  } from 'test-utils';
7
7
  import { createElementStyle, deleteElementStyle, getElementStyles } from '@elementor/editor-elements';
8
- import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
8
+ import { ELEMENTS_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
9
9
 
10
10
  import { initResetStyleCommand } from '../reset-style';
11
11
  import { getClassesProp, hasAtomicWidgets, isAtomicWidget } from '../utils';
@@ -80,7 +80,7 @@ describe( 'resetStyles', () => {
80
80
  // Assert.
81
81
  expect( createElementStyle ).toHaveBeenCalledWith( {
82
82
  elementId: 'test-container',
83
- label: LOCAL_STYLES_RESERVED_LABEL,
83
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
84
84
  classesProp: 'classes',
85
85
  meta: {
86
86
  breakpoint: null,
@@ -6,7 +6,7 @@ import {
6
6
  type V1Element,
7
7
  } from '@elementor/editor-elements';
8
8
  import { type StyleDefinition } from '@elementor/editor-styles';
9
- import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
9
+ import { ELEMENTS_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
10
10
  import { undoable } from '@elementor/editor-v1-adapters';
11
11
  import { __ } from '@wordpress/i18n';
12
12
 
@@ -55,7 +55,7 @@ export const undoablePasteElementStyle = () =>
55
55
  revertData.styleId = createElementStyle( {
56
56
  elementId,
57
57
  classesProp,
58
- label: LOCAL_STYLES_RESERVED_LABEL,
58
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
59
59
  ...firstVariant,
60
60
  additionalVariants,
61
61
  } );
@@ -92,7 +92,7 @@ export const undoablePasteElementStyle = () =>
92
92
  createElementStyle( {
93
93
  elementId: container.id,
94
94
  classesProp,
95
- label: LOCAL_STYLES_RESERVED_LABEL,
95
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
96
96
  styleId: revertData.styleId,
97
97
  ...firstVariant,
98
98
  additionalVariants,
@@ -1,5 +1,5 @@
1
1
  import { createElementStyle, deleteElementStyle, getElementStyles, type V1Element } from '@elementor/editor-elements';
2
- import { LOCAL_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
2
+ import { ELEMENTS_STYLES_RESERVED_LABEL } from '@elementor/editor-styles-repository';
3
3
  import { undoable } from '@elementor/editor-v1-adapters';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
@@ -45,7 +45,7 @@ export const undoableResetElementStyle = () =>
45
45
  elementId,
46
46
  classesProp,
47
47
  styleId,
48
- label: LOCAL_STYLES_RESERVED_LABEL,
48
+ label: ELEMENTS_STYLES_RESERVED_LABEL,
49
49
  ...firstVariant,
50
50
  additionalVariants,
51
51
  } );
@@ -1,7 +1,7 @@
1
1
  import type { CanvasExtendedWindow } from './types';
2
2
 
3
- export function getCanvasIframeBody() {
3
+ export function getCanvasIframeHead() {
4
4
  const extendedWindow = window as unknown as CanvasExtendedWindow;
5
5
 
6
- return extendedWindow.elementor?.$preview?.[ 0 ]?.contentDocument?.body;
6
+ return extendedWindow.elementor?.$preview?.[ 0 ]?.contentDocument?.head;
7
7
  }
package/src/sync/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type StorageContent } from '../prevent-link-in-link-commands';
2
+
1
3
  export type EnqueueFont = ( fontFamily: string, context?: 'preview' | 'editor' ) => void;
2
4
 
3
5
  export type CanvasExtendedWindow = Window & {
@@ -7,4 +9,9 @@ export type CanvasExtendedWindow = Window & {
7
9
  enqueueFont?: EnqueueFont;
8
10
  };
9
11
  };
12
+ elementorCommon?: {
13
+ storage?: {
14
+ get: ( key?: string ) => StorageContent;
15
+ };
16
+ };
10
17
  };
@@ -0,0 +1,16 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export function abortPreviousRuns< TArgs extends any[], TReturn >(
3
+ cb: ( abortController: AbortController, ...args: TArgs ) => TReturn
4
+ ): ( ...args: TArgs ) => TReturn {
5
+ let abortController: AbortController | null = null;
6
+
7
+ return ( ...args: TArgs ) => {
8
+ if ( abortController ) {
9
+ abortController.abort();
10
+ }
11
+
12
+ abortController = new AbortController();
13
+
14
+ return cb( abortController, ...args );
15
+ };
16
+ }
@@ -1,112 +0,0 @@
1
- import { createDOMElement, createMockPropType, createMockStyleDefinition, dispatchV1ReadyEvent } from 'test-utils';
2
- import { type BreakpointsMap, getBreakpointsMap } from '@elementor/editor-responsive';
3
- import { getStylesSchema } from '@elementor/editor-styles';
4
- import { stylesRepository } from '@elementor/editor-styles-repository';
5
- import { registerDataHook } from '@elementor/editor-v1-adapters';
6
- import { waitFor } from '@testing-library/react';
7
-
8
- import { initStylesRenderer as initStylesRendererBase } from '../init-styles-renderer';
9
- import { createPropsResolver } from '../renderers/create-props-resolver';
10
- import renderStyles from '../renderers/render-styles';
11
- import { getCanvasIframeBody } from '../sync/get-canvas-iframe-body';
12
-
13
- jest.mock( '@elementor/editor-styles-repository' );
14
- jest.mock( '@elementor/editor-responsive' );
15
- jest.mock( '@elementor/editor-styles' );
16
- jest.mock( '@elementor/editor-v1-adapters', () => ( {
17
- ...jest.requireActual( '@elementor/editor-v1-adapters' ),
18
- registerDataHook: jest.fn(),
19
- } ) );
20
- jest.mock( '../renderers/render-styles' );
21
- jest.mock( '../renderers/create-props-resolver' );
22
- jest.mock( '../sync/get-canvas-iframe-body' );
23
-
24
- const initStylesRenderer = () => {
25
- initStylesRendererBase();
26
- dispatchV1ReadyEvent();
27
- };
28
-
29
- describe( 'initStylesRenderer', () => {
30
- it( 'should trigger styles render on each change in the styles repo', async () => {
31
- // Arrange.
32
- let triggerRepoChange = () => {};
33
- let triggerDataHook = () => {};
34
- const mockStyleDef = createMockStyleDefinition();
35
- const mockSchema = { test: createMockPropType( { key: 'test', kind: 'plain' } ) };
36
-
37
- const mockedResolveProps = jest.fn();
38
-
39
- jest.mocked( getStylesSchema ).mockReturnValue( mockSchema );
40
- jest.mocked( createPropsResolver ).mockReturnValue( mockedResolveProps );
41
- jest.mocked( stylesRepository.subscribe ).mockImplementation( ( cb ) => ( triggerRepoChange = cb ) );
42
- jest.mocked( registerDataHook ).mockImplementation( ( type, hook, cb ) => {
43
- if ( type === 'after' && hook === 'editor/documents/attach-preview' ) {
44
- triggerDataHook = cb as never;
45
- }
46
-
47
- return null as never;
48
- } );
49
- jest.mocked( stylesRepository.all ).mockReturnValue( [ mockStyleDef ] );
50
- jest.mocked( getBreakpointsMap ).mockReturnValue( {
51
- mobile: { id: 'mobile', label: 'Mobile' },
52
- } as BreakpointsMap );
53
-
54
- // Act.
55
- initStylesRenderer();
56
-
57
- triggerRepoChange();
58
- triggerRepoChange();
59
- triggerDataHook();
60
-
61
- // Assert.
62
- expect( createPropsResolver ).toHaveBeenCalledTimes( 1 );
63
- expect( createPropsResolver ).toHaveBeenCalledWith( {
64
- transformers: expect.any( Object ),
65
- schema: mockSchema,
66
- onPropResolve: expect.any( Function ),
67
- } );
68
-
69
- expect( renderStyles ).toHaveBeenCalledTimes( 3 );
70
- expect( renderStyles ).toHaveBeenCalledWith( {
71
- resolve: mockedResolveProps,
72
- styles: [ mockStyleDef ],
73
- selectorPrefix: '.elementor',
74
- breakpoints: { mobile: { id: 'mobile', label: 'Mobile' } },
75
- signal: expect.any( AbortSignal ),
76
- } );
77
- } );
78
-
79
- it( 'should create a style container and fill it with render result', async () => {
80
- // Arrange.
81
- const wrapperEl = createDOMElement( { tag: 'div' } );
82
- let triggerStylesChange = () => {};
83
-
84
- jest.mocked( stylesRepository.subscribe ).mockImplementation( ( cb ) => ( triggerStylesChange = cb ) );
85
- jest.mocked( getCanvasIframeBody ).mockReturnValue( wrapperEl );
86
- jest.mocked( renderStyles ).mockReturnValue( Promise.resolve( '.a { color: red; }' ) );
87
-
88
- // Act.
89
- initStylesRenderer();
90
-
91
- triggerStylesChange();
92
-
93
- // Assert.
94
- await waitFor( () => {
95
- expect( wrapperEl.innerHTML ).toEqual(
96
- '<div style="display: none;" data-styles-container="">.a { color: red; }</div>'
97
- );
98
- } );
99
-
100
- // Act.
101
- jest.mocked( renderStyles ).mockResolvedValue( '.a { color: red; display: block; }' );
102
-
103
- triggerStylesChange();
104
-
105
- // Assert.
106
- await waitFor( () => {
107
- expect( wrapperEl.innerHTML ).toEqual(
108
- '<div style="display: none;" data-styles-container="">.a { color: red; display: block; }</div>'
109
- );
110
- } );
111
- } );
112
- } );
@@ -1,84 +0,0 @@
1
- import { getBreakpointsMap } from '@elementor/editor-responsive';
2
- import { getStylesSchema } from '@elementor/editor-styles';
3
- import { stylesRepository } from '@elementor/editor-styles-repository';
4
- import { __privateListenTo as listenTo, registerDataHook, v1ReadyEvent } from '@elementor/editor-v1-adapters';
5
-
6
- import { createPropsResolver } from './renderers/create-props-resolver';
7
- import renderStyles from './renderers/render-styles';
8
- import { styleTransformersRegistry } from './style-transformers-registry';
9
- import { enqueueFont } from './sync/enqueue-font';
10
- import { getCanvasIframeBody } from './sync/get-canvas-iframe-body';
11
-
12
- const WRAPPER_DATA_ATTR = 'data-styles-container';
13
- const SELECTOR_PREFIX = '.elementor';
14
-
15
- export function initStylesRenderer() {
16
- listenTo( v1ReadyEvent(), () => {
17
- let abortController: AbortController | null = null;
18
-
19
- const resolve = createPropsResolver( {
20
- transformers: styleTransformersRegistry,
21
- schema: getStylesSchema(),
22
- onPropResolve: enqueueUsedFonts,
23
- } );
24
-
25
- const injectStyleElements = async () => {
26
- const styleContainer = getStylesContainer();
27
-
28
- // Styles should be printed in a reversed order, so the high priority styles will be printed last.
29
- const styles = stylesRepository.all().reverse();
30
- const breakpoints = getBreakpointsMap();
31
-
32
- if ( abortController ) {
33
- abortController.abort();
34
- }
35
-
36
- abortController = new AbortController();
37
-
38
- styleContainer.innerHTML = await renderStyles( {
39
- styles,
40
- resolve,
41
- breakpoints,
42
- selectorPrefix: SELECTOR_PREFIX,
43
- signal: abortController.signal,
44
- } );
45
- };
46
-
47
- stylesRepository.subscribe( injectStyleElements );
48
-
49
- // Add initial styles rendering as a hook to ensure the whole editor
50
- // waits for styles to render before hiding the loaders.
51
- registerDataHook( 'after', 'editor/documents/attach-preview', injectStyleElements );
52
- } );
53
- }
54
-
55
- function getStylesContainer() {
56
- const preview = getCanvasIframeBody();
57
- const stylesContainer = preview?.querySelector( `[${ WRAPPER_DATA_ATTR }]` );
58
-
59
- if ( stylesContainer ) {
60
- return stylesContainer;
61
- }
62
-
63
- const el = createStylesContainer();
64
-
65
- preview?.prepend( el );
66
-
67
- return el;
68
- }
69
-
70
- function createStylesContainer() {
71
- const el = document.createElement( 'div' );
72
- el.style.display = 'none';
73
- el.setAttribute( WRAPPER_DATA_ATTR, '' );
74
-
75
- return el;
76
- }
77
-
78
- function enqueueUsedFonts( { key, value }: { key: string; value: unknown } ) {
79
- if ( key !== 'font-family' || typeof value !== 'string' ) {
80
- return;
81
- }
82
-
83
- enqueueFont( value );
84
- }
File without changes