@elementor/editor-responsive 0.10.6 → 0.11.1

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.
@@ -1,113 +0,0 @@
1
- import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
3
- import { BreakpointId, ExtendedWindow } from '../types';
4
- import useBreakpoints from '../hooks/use-breakpoints';
5
- import { Tab, Tabs, Tooltip as BaseTooltip, TooltipProps } from '@elementor/ui';
6
- import {
7
- DesktopIcon,
8
- TabletPortraitIcon,
9
- MobilePortraitIcon,
10
- WidescreenIcon,
11
- LaptopIcon,
12
- TabletLandscapeIcon,
13
- MobileLandscapeIcon,
14
- } from '@elementor/icons';
15
- import useBreakpointsActions from '../hooks/use-breakpoints-actions';
16
-
17
- export default function BreakpointsSwitcher() {
18
- const { all, active } = useBreakpoints();
19
- const { activate } = useBreakpointsActions();
20
-
21
- if ( ! all.length || ! active ) {
22
- return null;
23
- }
24
-
25
- const onChange = ( _: unknown, value: BreakpointId ) => {
26
- const extendedWindow = window as unknown as ExtendedWindow;
27
- const config = extendedWindow?.elementor?.editorEvents?.config;
28
-
29
- if ( config ) {
30
- extendedWindow.elementor.editorEvents.dispatchEvent( config.names.topBar.responsiveControls, {
31
- location: config.locations.topBar,
32
- secondaryLocation: config.secondaryLocations.responsiveControls,
33
- trigger: config.triggers.click,
34
- element: config.elements.buttonIcon,
35
- mode: value,
36
- } );
37
- }
38
-
39
- activate( value );
40
- };
41
-
42
- return (
43
- <Tabs
44
- textColor="inherit"
45
- indicatorColor="secondary"
46
- value={ active.id }
47
- onChange={ onChange }
48
- aria-label={ __( 'Switch Device', 'elementor' ) }
49
- sx={ {
50
- '& .MuiTabs-indicator': {
51
- backgroundColor: 'text.primary',
52
- },
53
- } }
54
- >
55
- { all.map( ( { id, label, type, width } ) => {
56
- const Icon = iconsMap[ id ];
57
-
58
- const title = labelsMap[ type || 'default' ]
59
- .replace( '%s', label )
60
- .replace( '%d', width?.toString() || '' );
61
-
62
- return (
63
- <Tab
64
- value={ id }
65
- key={ id }
66
- aria-label={ title }
67
- icon={
68
- <Tooltip title={ title }>
69
- <Icon />
70
- </Tooltip>
71
- }
72
- sx={ { minWidth: 'auto' } }
73
- data-testid={ `switch-device-to-${ id }` }
74
- />
75
- );
76
- } ) }
77
- </Tabs>
78
- );
79
- }
80
-
81
- function Tooltip( props: TooltipProps ) {
82
- return (
83
- <BaseTooltip
84
- PopperProps={ {
85
- sx: {
86
- '&.MuiTooltip-popper .MuiTooltip-tooltip.MuiTooltip-tooltipPlacementBottom': {
87
- mt: 2.5,
88
- },
89
- },
90
- } }
91
- { ...props }
92
- />
93
- );
94
- }
95
-
96
- const iconsMap = {
97
- widescreen: WidescreenIcon,
98
- desktop: DesktopIcon,
99
- laptop: LaptopIcon,
100
- tablet_extra: TabletLandscapeIcon,
101
- tablet: TabletPortraitIcon,
102
- mobile_extra: MobileLandscapeIcon,
103
- mobile: MobilePortraitIcon,
104
- };
105
-
106
- const labelsMap = {
107
- default: '%s',
108
- // translators: %s: Breakpoint label, %d: Breakpoint size.
109
- 'min-width': __( '%s (%dpx and up)', 'elementor' ),
110
-
111
- // translators: %s: Breakpoint label, %d: Breakpoint size.
112
- 'max-width': __( '%s (up to %dpx)', 'elementor' ),
113
- } as const;
@@ -1,20 +0,0 @@
1
- import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
2
- import { renderHook } from '@testing-library/react';
3
- import useBreakpointsActions from '../use-breakpoints-actions';
4
-
5
- jest.mock( '@elementor/editor-v1-adapters', () => ( {
6
- __privateRunCommand: jest.fn(),
7
- } ) );
8
-
9
- describe( '@elementor/editor-responsive - useBreakpointsActions', () => {
10
- it( 'should activate a breakpoint', () => {
11
- // Act.
12
- const { result } = renderHook( () => useBreakpointsActions() );
13
-
14
- result.current.activate( 'tablet' );
15
-
16
- // Assert.
17
- expect( jest.mocked( runCommand ) ).toHaveBeenCalledTimes( 1 );
18
- expect( jest.mocked( runCommand ) ).toHaveBeenCalledWith( 'panel/change-device-mode', { device: 'tablet' } );
19
- } );
20
- } );
@@ -1,84 +0,0 @@
1
- import * as React from 'react';
2
- import { PropsWithChildren } from 'react';
3
- import { slice } from '../../store';
4
- import useBreakpoints from '../use-breakpoints';
5
- import { renderHook } from '@testing-library/react';
6
- import {
7
- __createStore,
8
- __dispatch,
9
- __registerSlice,
10
- SliceState,
11
- Store,
12
- __StoreProvider as StoreProvider,
13
- } from '@elementor/store';
14
-
15
- jest.mock( '@elementor/editor-v1-adapters', () => ( {
16
- __privateRunCommand: jest.fn(),
17
- } ) );
18
-
19
- describe( '@elementor/editor-responsive - useBreakpoints', () => {
20
- let store: Store< SliceState< typeof slice > >;
21
-
22
- beforeEach( () => {
23
- __registerSlice( slice );
24
- store = __createStore();
25
- } );
26
-
27
- it( 'should return all breakpoints sorted by size', () => {
28
- // Arrange.
29
- __dispatch(
30
- slice.actions.init( {
31
- activeId: null,
32
- entities: [
33
- { id: 'tablet', label: 'Tablet Portrait', width: 1024, type: 'max-width' },
34
- { id: 'mobile', label: 'Mobile Portrait', width: 767, type: 'max-width' },
35
- { id: 'widescreen', label: 'Widescreen', width: 2400, type: 'min-width' },
36
- { id: 'desktop', label: 'Desktop' },
37
- ],
38
- } )
39
- );
40
-
41
- // Act.
42
- const { result } = renderHookWithStore( () => useBreakpoints(), store );
43
-
44
- // Assert.
45
- expect( result.current.all ).toEqual( [
46
- { id: 'widescreen', label: 'Widescreen', width: 2400, type: 'min-width' },
47
- { id: 'desktop', label: 'Desktop' },
48
- { id: 'tablet', label: 'Tablet Portrait', width: 1024, type: 'max-width' },
49
- { id: 'mobile', label: 'Mobile Portrait', width: 767, type: 'max-width' },
50
- ] );
51
- } );
52
-
53
- it( 'should return the active breakpoint', () => {
54
- // Arrange.
55
- __dispatch(
56
- slice.actions.init( {
57
- activeId: 'tablet',
58
- entities: [
59
- { id: 'desktop', label: 'Desktop' },
60
- { id: 'tablet', label: 'Tablet Portrait', type: 'max-width', width: 1024 },
61
- ],
62
- } )
63
- );
64
-
65
- // Act.
66
- const { result } = renderHookWithStore( () => useBreakpoints(), store );
67
-
68
- // Assert.
69
- expect( result.current.active ).toEqual( {
70
- id: 'tablet',
71
- label: 'Tablet Portrait',
72
- type: 'max-width',
73
- width: 1024,
74
- } );
75
- } );
76
- } );
77
-
78
- function renderHookWithStore< T >( hook: () => T, store: Store ) {
79
- const wrapper = ( { children }: PropsWithChildren< unknown > ) => (
80
- <StoreProvider store={ store }>{ children }</StoreProvider>
81
- );
82
-
83
- return renderHook( hook, { wrapper } );
84
- }
@@ -1,13 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { BreakpointId } from '../types';
3
- import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
4
-
5
- export default function useBreakpointsActions() {
6
- const activate = useCallback( ( device: BreakpointId ) => {
7
- return runCommand( 'panel/change-device-mode', { device } );
8
- }, [] );
9
-
10
- return {
11
- activate,
12
- };
13
- }
package/src/init.ts DELETED
@@ -1,27 +0,0 @@
1
- import { slice } from './store';
2
- import syncStore from './sync/sync-store';
3
- import { injectIntoResponsive } from '@elementor/editor-app-bar';
4
- import BreakpointsSwitcher from './components/breakpoints-switcher';
5
- import { __registerSlice } from '@elementor/store';
6
-
7
- export default function init() {
8
- initStore();
9
-
10
- registerAppBarUI();
11
- }
12
-
13
- function initStore() {
14
- __registerSlice( slice );
15
-
16
- syncStore();
17
- }
18
-
19
- function registerAppBarUI() {
20
- injectIntoResponsive( {
21
- id: 'responsive-breakpoints-switcher',
22
- component: BreakpointsSwitcher,
23
- options: {
24
- priority: 20, // After document indication.
25
- },
26
- } );
27
- }
@@ -1,46 +0,0 @@
1
- import { __createSlice, PayloadAction } from '@elementor/store';
2
- import { Breakpoint, BreakpointId } from '../types';
3
-
4
- export type State = {
5
- entities: Record< BreakpointId, Breakpoint >;
6
- activeId: BreakpointId | null;
7
- };
8
- const initialState: State = {
9
- entities: {} as State[ 'entities' ],
10
- activeId: null,
11
- };
12
-
13
- export const slice = __createSlice( {
14
- name: 'breakpoints',
15
- initialState,
16
- reducers: {
17
- init(
18
- state,
19
- action: PayloadAction< {
20
- entities: Breakpoint[];
21
- activeId: State[ 'activeId' ];
22
- } >
23
- ) {
24
- state.activeId = action.payload.activeId;
25
- state.entities = normalizeEntities( action.payload.entities );
26
- },
27
-
28
- activateBreakpoint( state, action: PayloadAction< BreakpointId > ) {
29
- if ( state.entities[ action.payload ] ) {
30
- state.activeId = action.payload;
31
- }
32
- },
33
- },
34
- } );
35
-
36
- function normalizeEntities( entities: Breakpoint[] ) {
37
- return entities.reduce(
38
- ( acc, breakpoint ) => {
39
- return {
40
- ...acc,
41
- [ breakpoint.id ]: breakpoint,
42
- };
43
- },
44
- {} as State[ 'entities' ]
45
- );
46
- }
@@ -1,27 +0,0 @@
1
- import { slice } from './index';
2
- import { Breakpoint } from '../types';
3
- import { __createSelector, SliceState } from '@elementor/store';
4
-
5
- type State = SliceState< typeof slice >;
6
-
7
- export const selectEntities = ( state: State ) => state.breakpoints.entities;
8
- export const selectActiveId = ( state: State ) => state.breakpoints.activeId;
9
-
10
- export const selectActiveBreakpoint = __createSelector( selectEntities, selectActiveId, ( entities, activeId ) =>
11
- activeId && entities[ activeId ] ? entities[ activeId ] : null
12
- );
13
-
14
- export const selectSortedBreakpoints = __createSelector( selectEntities, ( entities ) => {
15
- const byWidth = ( a: Breakpoint, b: Breakpoint ) => {
16
- return a.width && b.width ? b.width - a.width : 0;
17
- };
18
-
19
- const all = Object.values( entities );
20
-
21
- const defaults = all.filter( ( breakpoint ) => ! breakpoint.width ); // AKA Desktop.
22
- const minWidth = all.filter( ( breakpoint ) => breakpoint.type === 'min-width' );
23
- const maxWidth = all.filter( ( breakpoint ) => breakpoint.type === 'max-width' );
24
-
25
- // Sort by size, big to small.
26
- return [ ...minWidth.sort( byWidth ), ...defaults, ...maxWidth.sort( byWidth ) ];
27
- } );
@@ -1,162 +0,0 @@
1
- import syncStore from '../sync-store';
2
- import { slice } from '../../store';
3
- import { BreakpointId, ExtendedWindow } from '../../types';
4
- import { __createStore, __dispatch, __registerSlice, SliceState, Store } from '@elementor/store';
5
- import { selectActiveBreakpoint, selectEntities } from '../../store/selectors';
6
-
7
- describe( '@elementor/editor-responsive - Sync Store', () => {
8
- let store: Store< SliceState< typeof slice > >;
9
- let extendedWindow: ExtendedWindow;
10
-
11
- beforeEach( () => {
12
- __registerSlice( slice );
13
- store = __createStore();
14
-
15
- syncStore();
16
-
17
- extendedWindow = window as unknown as ExtendedWindow;
18
- } );
19
-
20
- it( 'should initialize the store when V1 is ready', () => {
21
- // Arrange.
22
- mockV1BreakpointsConfig();
23
-
24
- // Act.
25
- dispatchEvent( new CustomEvent( 'elementor/initialized' ) );
26
-
27
- // Assert.
28
- expect( selectEntities( store.getState() ) ).toEqual( {
29
- desktop: { id: 'desktop', label: 'Desktop' },
30
- mobile: { id: 'mobile', label: 'Mobile Portrait', width: 767, type: 'max-width' },
31
- tablet: { id: 'tablet', label: 'Tablet Portrait', width: 1024, type: 'max-width' },
32
- laptop: { id: 'laptop', label: 'Laptop', width: 1366, type: 'max-width' },
33
- widescreen: { id: 'widescreen', label: 'Widescreen', width: 2400, type: 'min-width' },
34
- mobile_extra: { id: 'mobile_extra', label: 'Mobile Landscape', width: 880, type: 'max-width' },
35
- tablet_extra: { id: 'tablet_extra', label: 'Tablet Landscape', width: 1200, type: 'max-width' },
36
- } );
37
-
38
- expect( extendedWindow.elementor.channels.deviceMode.request ).toHaveBeenCalledTimes( 1 );
39
- expect( extendedWindow.elementor.channels.deviceMode.request ).toHaveBeenCalledWith( 'currentMode' );
40
-
41
- expect( selectActiveBreakpoint( store.getState() ) ).toEqual( {
42
- id: 'mobile',
43
- label: 'Mobile Portrait',
44
- width: 767,
45
- type: 'max-width',
46
- } );
47
- } );
48
-
49
- it( 'should initialize an empty store when V1 breakpoints config is not available', () => {
50
- // Act.
51
- dispatchEvent( new CustomEvent( 'elementor/initialized' ) );
52
-
53
- // Assert.
54
- expect( selectEntities( store.getState() ) ).toEqual( {} );
55
- } );
56
-
57
- it( 'should sync the active breakpoint on change', () => {
58
- // Arrange.
59
- mockV1BreakpointsConfig();
60
-
61
- __dispatch(
62
- slice.actions.init( {
63
- entities: [
64
- { id: 'desktop', label: 'Desktop' },
65
- { id: 'mobile', label: 'Mobile Portrait', width: 767, type: 'max-width' },
66
- ],
67
- activeId: 'mobile',
68
- } )
69
- );
70
-
71
- // Act - Mock a change.
72
- jest.mocked( extendedWindow.elementor.channels.deviceMode.request ).mockReturnValue( 'desktop' );
73
- dispatchEvent( new CustomEvent( 'elementor/device-mode/change' ) );
74
-
75
- // Assert.
76
- expect( selectActiveBreakpoint( store.getState() ) ).toEqual( { id: 'desktop', label: 'Desktop' } );
77
- } );
78
-
79
- it( "should not change the active breakpoint when it's empty", () => {
80
- // Arrange.
81
- mockV1BreakpointsConfig();
82
-
83
- __dispatch(
84
- slice.actions.init( {
85
- entities: [
86
- { id: 'desktop', label: 'Desktop' },
87
- { id: 'mobile', label: 'Mobile Portrait', width: 767, type: 'max-width' },
88
- ],
89
- activeId: 'desktop',
90
- } )
91
- );
92
-
93
- // Act - Mock a change.
94
- jest.mocked( extendedWindow.elementor.channels.deviceMode.request ).mockReturnValue( '' as BreakpointId );
95
- dispatchEvent( new CustomEvent( 'elementor/device-mode/change' ) );
96
-
97
- // Assert.
98
- expect( selectActiveBreakpoint( store.getState() ) ).toEqual( { id: 'desktop', label: 'Desktop' } );
99
- } );
100
- } );
101
-
102
- function mockV1BreakpointsConfig() {
103
- ( window as unknown as ExtendedWindow ).elementor = {
104
- channels: {
105
- deviceMode: { request: jest.fn( () => 'mobile' ) },
106
- },
107
- config: {
108
- responsive: {
109
- breakpoints: {
110
- mobile: {
111
- label: 'Mobile Portrait',
112
- value: 767,
113
- direction: 'max',
114
- is_enabled: true,
115
- },
116
- mobile_extra: {
117
- label: 'Mobile Landscape',
118
- value: 880,
119
- direction: 'max',
120
- is_enabled: true,
121
- },
122
- tablet: {
123
- label: 'Tablet Portrait',
124
- value: 1024,
125
- direction: 'max',
126
- is_enabled: true,
127
- },
128
- tablet_extra: {
129
- label: 'Tablet Landscape',
130
- value: 1200,
131
- direction: 'max',
132
- is_enabled: true,
133
- },
134
- laptop: {
135
- label: 'Laptop',
136
- value: 1366,
137
- direction: 'max',
138
- is_enabled: true,
139
- },
140
- widescreen: {
141
- label: 'Widescreen',
142
- value: 2400,
143
- direction: 'min',
144
- is_enabled: true,
145
- },
146
- } as const,
147
- },
148
- },
149
- editorEvents: {
150
- config: {
151
- elements: {},
152
- locations: {},
153
- names: {
154
- topBar: {},
155
- },
156
- secondaryLocations: {},
157
- triggers: {},
158
- },
159
- dispatchEvent: () => null,
160
- },
161
- };
162
- }
@@ -1,70 +0,0 @@
1
- import { slice } from '../store';
2
- import { __dispatch } from '@elementor/store';
3
- import { Breakpoint, ExtendedWindow } from '../types';
4
- import { __privateListenTo as listenTo, v1ReadyEvent, windowEvent } from '@elementor/editor-v1-adapters';
5
- import { __ } from '@wordpress/i18n';
6
-
7
- export default function syncStore() {
8
- syncInitialization();
9
- syncOnChange();
10
- }
11
-
12
- function syncInitialization() {
13
- const { init } = slice.actions;
14
-
15
- listenTo( v1ReadyEvent(), () => {
16
- __dispatch(
17
- init( {
18
- entities: getBreakpoints(),
19
- activeId: getActiveBreakpoint(),
20
- } )
21
- );
22
- } );
23
- }
24
-
25
- function syncOnChange() {
26
- const { activateBreakpoint } = slice.actions;
27
-
28
- listenTo( deviceModeChangeEvent(), () => {
29
- const activeBreakpoint = getActiveBreakpoint();
30
-
31
- __dispatch( activateBreakpoint( activeBreakpoint ) );
32
- } );
33
- }
34
-
35
- function getBreakpoints() {
36
- const { breakpoints } = ( window as unknown as ExtendedWindow ).elementor?.config?.responsive || {};
37
-
38
- if ( ! breakpoints ) {
39
- return [];
40
- }
41
-
42
- const entities = Object.entries( breakpoints )
43
- .filter( ( [ , breakpoint ] ) => breakpoint.is_enabled )
44
- .map( ( [ id, { value, direction, label } ] ) => {
45
- return {
46
- id,
47
- label,
48
- width: value,
49
- type: direction === 'min' ? 'min-width' : 'max-width',
50
- } as Breakpoint;
51
- } );
52
-
53
- // Desktop breakpoint is not included in V1 config.
54
- entities.push( {
55
- id: 'desktop',
56
- label: __( 'Desktop', 'elementor' ),
57
- } );
58
-
59
- return entities;
60
- }
61
-
62
- function getActiveBreakpoint() {
63
- const extendedWindow = window as unknown as ExtendedWindow;
64
-
65
- return extendedWindow.elementor?.channels?.deviceMode?.request?.( 'currentMode' ) || null;
66
- }
67
-
68
- function deviceModeChangeEvent() {
69
- return windowEvent( 'elementor/device-mode/change' );
70
- }