@elementor/editor-controls 3.32.0-95 → 3.33.0-100

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-controls",
3
3
  "description": "This package contains the controls model and utils for the Elementor editor",
4
- "version": "3.32.0-95",
4
+ "version": "3.33.0-100",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,21 +40,21 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "3.32.0-95",
44
- "@elementor/editor-elements": "3.32.0-95",
45
- "@elementor/editor-props": "3.32.0-95",
46
- "@elementor/editor-responsive": "3.32.0-95",
47
- "@elementor/editor-ui": "3.32.0-95",
48
- "@elementor/editor-v1-adapters": "3.32.0-95",
49
- "@elementor/env": "3.32.0-95",
50
- "@elementor/http-client": "3.32.0-95",
43
+ "@elementor/editor-current-user": "3.33.0-100",
44
+ "@elementor/editor-elements": "3.33.0-100",
45
+ "@elementor/editor-props": "3.33.0-100",
46
+ "@elementor/editor-responsive": "3.33.0-100",
47
+ "@elementor/editor-ui": "3.33.0-100",
48
+ "@elementor/editor-v1-adapters": "3.33.0-100",
49
+ "@elementor/env": "3.33.0-100",
50
+ "@elementor/http-client": "3.33.0-100",
51
51
  "@elementor/icons": "^1.51.1",
52
- "@elementor/locations": "3.32.0-95",
53
- "@elementor/query": "3.32.0-95",
54
- "@elementor/session": "3.32.0-95",
52
+ "@elementor/locations": "3.33.0-100",
53
+ "@elementor/query": "3.33.0-100",
54
+ "@elementor/session": "3.33.0-100",
55
55
  "@elementor/ui": "1.36.12",
56
- "@elementor/utils": "3.32.0-95",
57
- "@elementor/wp-media": "3.32.0-95",
56
+ "@elementor/utils": "3.33.0-100",
57
+ "@elementor/wp-media": "3.33.0-100",
58
58
  "@wordpress/i18n": "^5.13.0",
59
59
  "@monaco-editor/react": "^4.7.0"
60
60
  },
@@ -7,8 +7,15 @@ export const EditorWrapper = styled( Box )`
7
7
  position: relative;
8
8
  height: 200px;
9
9
 
10
- .monaco-editor .colorpicker-widget {
11
- z-index: 99999999 !important;
10
+ .monaco-editor .suggest-widget {
11
+ width: 220px !important;
12
+ max-width: 220px !important;
13
+ }
14
+
15
+ .visual-content-dimmed {
16
+ opacity: 0.6;
17
+ color: #aaa !important;
18
+ pointer-events: none;
12
19
  }
13
20
  `;
14
21
 
@@ -5,12 +5,13 @@ import { useTheme } from '@elementor/ui';
5
5
  import { Editor } from '@monaco-editor/react';
6
6
 
7
7
  import { EditorWrapper } from './css-editor.styles';
8
- import { setCustomSyntaxRules, validate } from './css-validation';
8
+ import { clearMarkersFromVisualContent, setCustomSyntaxRules, validate } from './css-validation';
9
9
  import { ResizeHandleComponent } from './resize-handle';
10
+ import { preventChangeOnVisualContent } from './visual-content-change-protection';
10
11
 
11
12
  type CssEditorProps = {
12
13
  value: string;
13
- onChange: ( value: string ) => void;
14
+ onChange: ( value: string, isValid: boolean ) => void;
14
15
  };
15
16
 
16
17
  const setVisualContent = ( value: string ): string => {
@@ -29,82 +30,23 @@ const getActual = ( value: string ): string => {
29
30
  .join( '\n' );
30
31
  };
31
32
 
32
- const preventChangeOnVisualContent = ( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ) => {
33
- const model = editor.getModel();
34
- if ( ! model ) {
35
- return;
36
- }
37
-
38
- editor.onKeyDown( ( e ) => {
39
- const position = editor.getPosition();
40
- if ( ! position ) {
41
- return;
42
- }
43
-
44
- const totalLines = model.getLineCount();
45
- const isInProtectedRange = position.lineNumber === 1 || position.lineNumber === totalLines;
46
-
47
- if ( isInProtectedRange ) {
48
- const allowedKeys = [
49
- monaco.KeyCode.UpArrow,
50
- monaco.KeyCode.DownArrow,
51
- monaco.KeyCode.LeftArrow,
52
- monaco.KeyCode.RightArrow,
53
- monaco.KeyCode.Home,
54
- monaco.KeyCode.End,
55
- monaco.KeyCode.PageUp,
56
- monaco.KeyCode.PageDown,
57
- monaco.KeyCode.Tab,
58
- monaco.KeyCode.Escape,
59
- ];
60
-
61
- if ( ! allowedKeys.includes( e.keyCode ) ) {
62
- e.preventDefault();
63
- e.stopPropagation();
64
- }
65
- }
66
- } );
67
- };
68
-
69
33
  const createEditorDidMountHandler = (
70
34
  editorRef: React.MutableRefObject< editor.IStandaloneCodeEditor | null >,
71
- monacoRef: React.MutableRefObject< MonacoEditor | null >,
72
- debounceTimer: React.MutableRefObject< NodeJS.Timeout | null >,
73
- onChange: ( value: string ) => void
35
+ monacoRef: React.MutableRefObject< MonacoEditor | null >
74
36
  ) => {
75
37
  return ( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ) => {
76
38
  editorRef.current = editor;
77
39
  monacoRef.current = monaco;
78
40
 
79
- preventChangeOnVisualContent( editor, monaco );
41
+ preventChangeOnVisualContent( editor );
80
42
 
81
43
  setCustomSyntaxRules( editor, monaco );
82
44
 
83
- editor.onDidChangeModelContent( () => {
84
- const code = editor.getModel()?.getValue() ?? '';
85
- const userContent = getActual( code );
86
-
87
- setCustomSyntaxRules( editor, monaco );
88
-
89
- const currentTimer = debounceTimer.current;
90
- if ( currentTimer ) {
91
- clearTimeout( currentTimer );
92
- }
93
-
94
- const newTimer = setTimeout( () => {
95
- if ( ! editorRef.current || ! monacoRef.current ) {
96
- return;
97
- }
98
-
99
- const hasNoErrors = validate( editorRef.current, monacoRef.current );
100
-
101
- if ( hasNoErrors ) {
102
- onChange( userContent );
103
- }
104
- }, 500 );
105
-
106
- debounceTimer.current = newTimer;
45
+ monaco.editor.onDidChangeMarkers( () => {
46
+ setTimeout( () => clearMarkersFromVisualContent( editor, monaco ), 0 );
107
47
  } );
48
+
49
+ editor.setPosition( { lineNumber: 2, column: ( editor.getModel()?.getLineContent( 2 ).length ?? 0 ) + 1 } );
108
50
  };
109
51
  };
110
52
 
@@ -126,7 +68,35 @@ export const CssEditor = ( { value, onChange }: CssEditorProps ) => {
126
68
  }
127
69
  }, [] );
128
70
 
129
- const handleEditorDidMount = createEditorDidMountHandler( editorRef, monacoRef, debounceTimer, onChange );
71
+ const handleEditorChange = () => {
72
+ if ( ! editorRef.current || ! monacoRef.current ) {
73
+ return;
74
+ }
75
+
76
+ const code = editorRef.current?.getModel()?.getValue() ?? '';
77
+ const userContent = getActual( code );
78
+
79
+ setCustomSyntaxRules( editorRef?.current, monacoRef.current );
80
+
81
+ const currentTimer = debounceTimer.current;
82
+ if ( currentTimer ) {
83
+ clearTimeout( currentTimer );
84
+ }
85
+
86
+ const newTimer = setTimeout( () => {
87
+ if ( ! editorRef.current || ! monacoRef.current ) {
88
+ return;
89
+ }
90
+
91
+ const hasNoErrors = validate( editorRef.current, monacoRef.current );
92
+
93
+ onChange( userContent, hasNoErrors );
94
+ }, 500 );
95
+
96
+ debounceTimer.current = newTimer;
97
+ };
98
+
99
+ const handleEditorDidMount = createEditorDidMountHandler( editorRef, monacoRef );
130
100
 
131
101
  React.useEffect( () => {
132
102
  const timerRef = debounceTimer;
@@ -145,18 +115,24 @@ export const CssEditor = ( { value, onChange }: CssEditorProps ) => {
145
115
  height="100%"
146
116
  language="css"
147
117
  theme={ theme.palette.mode === 'dark' ? 'vs-dark' : 'vs' }
148
- defaultValue={ setVisualContent( value ) }
118
+ value={ setVisualContent( value ) }
149
119
  onMount={ handleEditorDidMount }
120
+ onChange={ handleEditorChange }
150
121
  options={ {
151
- lineNumbers: 'off',
152
- folding: false,
153
- showFoldingControls: 'never',
122
+ lineNumbers: 'on',
123
+ folding: true,
154
124
  minimap: { enabled: false },
155
125
  fontFamily: 'Roboto, Arial, Helvetica, Verdana, sans-serif',
156
126
  fontSize: 12,
157
127
  renderLineHighlight: 'none',
158
128
  hideCursorInOverviewRuler: true,
159
129
  fixedOverflowWidgets: true,
130
+ suggestFontSize: 10,
131
+ suggestLineHeight: 14,
132
+ stickyScroll: {
133
+ enabled: false,
134
+ },
135
+ lineDecorationsWidth: 2,
160
136
  } }
161
137
  />
162
138
  <ResizeHandleComponent
@@ -60,3 +60,16 @@ export function validate( editor: editor.IStandaloneCodeEditor, monaco: MonacoEd
60
60
  const allMarkers = monaco.editor.getModelMarkers( { resource: model.uri } );
61
61
  return allMarkers.filter( ( marker ) => marker.severity === monaco.MarkerSeverity.Error ).length === 0;
62
62
  }
63
+
64
+ export function clearMarkersFromVisualContent( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): void {
65
+ const model = editor.getModel();
66
+
67
+ if ( ! model ) {
68
+ return;
69
+ }
70
+
71
+ const allMarkers = monaco.editor.getModelMarkers( { resource: model.uri } );
72
+ const filteredMarkers = allMarkers.filter( ( marker ) => marker.startLineNumber !== 1 );
73
+ const nonCustomMarkers = filteredMarkers.filter( ( m ) => m.source !== 'custom-css-rules' );
74
+ monaco.editor.setModelMarkers( model, 'css', nonCustomMarkers );
75
+ }
@@ -0,0 +1,69 @@
1
+ import type { editor } from 'monaco-types';
2
+
3
+ export const preventChangeOnVisualContent = ( editor: editor.IStandaloneCodeEditor ) => {
4
+ const model = editor.getModel();
5
+ if ( ! model ) {
6
+ return;
7
+ }
8
+
9
+ const decorationsCollection = editor.createDecorationsCollection();
10
+
11
+ const applyVisualContentStyling = () => {
12
+ const totalLines = model.getLineCount();
13
+ const decorations = [];
14
+
15
+ decorations.push( {
16
+ range: {
17
+ startLineNumber: 1,
18
+ startColumn: 1,
19
+ endLineNumber: 1,
20
+ endColumn: model.getLineContent( 1 ).length + 1,
21
+ },
22
+ options: {
23
+ inlineClassName: 'visual-content-dimmed',
24
+ isWholeLine: false,
25
+ },
26
+ } );
27
+
28
+ if ( totalLines > 1 ) {
29
+ decorations.push( {
30
+ range: {
31
+ startLineNumber: totalLines,
32
+ startColumn: 1,
33
+ endLineNumber: totalLines,
34
+ endColumn: model.getLineContent( totalLines ).length + 1,
35
+ },
36
+ options: {
37
+ inlineClassName: 'visual-content-dimmed',
38
+ isWholeLine: false,
39
+ },
40
+ } );
41
+ }
42
+
43
+ decorationsCollection.set( decorations );
44
+ };
45
+
46
+ applyVisualContentStyling();
47
+
48
+ model.onDidChangeContent( () => {
49
+ applyVisualContentStyling();
50
+ } );
51
+
52
+ const originalPushEditOperations = model.pushEditOperations;
53
+ model.pushEditOperations = function ( beforeCursorState, editOperations, cursorStateComputer ) {
54
+ const totalLines = model.getLineCount();
55
+
56
+ const filteredOperations = editOperations.filter( ( operation ) => {
57
+ const range = operation.range;
58
+ const affectsProtectedLine =
59
+ range.startLineNumber === 1 ||
60
+ range.endLineNumber === 1 ||
61
+ range.startLineNumber === totalLines ||
62
+ range.endLineNumber === totalLines;
63
+
64
+ return ! affectsProtectedLine;
65
+ } );
66
+
67
+ return originalPushEditOperations.call( this, beforeCursorState, filteredOperations, cursorStateComputer );
68
+ };
69
+ };
@@ -5,6 +5,7 @@ import { type PopupState, usePopupState } from '@elementor/ui';
5
5
 
6
6
  import { useBoundProp } from '../../../bound-prop-context/use-bound-prop';
7
7
  import { useSyncExternalState } from '../../../hooks/use-sync-external-state';
8
+ import { eventBus } from '../../../services/event-bus';
8
9
  import { type Item, type RepeatablePropValue } from '../types';
9
10
 
10
11
  type SetterFn< T > = ( prevItems: T ) => T;
@@ -46,7 +47,11 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
46
47
  children,
47
48
  initial,
48
49
  propTypeUtil,
49
- }: React.PropsWithChildren< { initial: T; propTypeUtil: PropTypeUtil< string, T[] >; isSortable?: boolean } > ) => {
50
+ }: React.PropsWithChildren< {
51
+ initial: T;
52
+ propTypeUtil: PropTypeUtil< string, T[] >;
53
+ isSortable?: boolean;
54
+ } > ) => {
50
55
  const { value: repeaterValues, setValue: setRepeaterValues } = useBoundProp( propTypeUtil );
51
56
 
52
57
  const [ items, setItems ] = useSyncExternalState( {
@@ -92,10 +97,20 @@ export const RepeaterContextProvider = < T extends RepeatablePropValue = Repeata
92
97
 
93
98
  setOpenItemIndex( newIndex );
94
99
  popoverState.open( rowRef ?? ev );
100
+
101
+ eventBus.emit( `${ propTypeUtil.key }-item-added`, {
102
+ itemValue: initial.value,
103
+ } );
95
104
  };
96
105
 
97
106
  const removeItem = ( index: number ) => {
107
+ const itemToRemove = items[ index ];
108
+
98
109
  setItems( items.filter( ( _, pos ) => pos !== index ) );
110
+
111
+ eventBus.emit( `${ propTypeUtil.key }-item-removed`, {
112
+ itemValue: itemToRemove?.value,
113
+ } );
99
114
  };
100
115
 
101
116
  const updateItem = ( updatedItem: T, index: number ) => {
@@ -0,0 +1,28 @@
1
+ import { getSelectedElements } from '@elementor/editor-elements';
2
+ import { sendMixpanelEvent } from '@elementor/utils';
3
+
4
+ import { eventBus } from '../../services/event-bus';
5
+ import { type initialTransitionValue } from './data';
6
+
7
+ type TransitionItemValue = typeof initialTransitionValue;
8
+
9
+ const transitionRepeaterMixpanelEvent = {
10
+ eventName: 'click_added_transition',
11
+ location: 'V4 Style Tab',
12
+ secondaryLocation: 'Transition control',
13
+ trigger: 'click',
14
+ };
15
+
16
+ export function subscribeToTransitionEvent() {
17
+ eventBus.subscribe( 'transition-item-added', ( data ) => {
18
+ const payload = data as { itemValue?: TransitionItemValue };
19
+ const value = payload?.itemValue?.selection?.value?.value?.value;
20
+ const selectedElements = getSelectedElements();
21
+ const widgetType = selectedElements[ 0 ]?.type ?? null;
22
+ sendMixpanelEvent( {
23
+ transition_type: value ?? 'unknown',
24
+ ...transitionRepeaterMixpanelEvent,
25
+ widget_type: widgetType,
26
+ } );
27
+ } );
28
+ }
@@ -10,6 +10,7 @@ import { createControl } from '../../create-control';
10
10
  import { RepeatableControl } from '../repeatable-control';
11
11
  import { SelectionSizeControl } from '../selection-size-control';
12
12
  import { initialTransitionValue, transitionProperties } from './data';
13
+ import { subscribeToTransitionEvent } from './trainsition-events';
13
14
  import { TransitionSelector } from './transition-selector';
14
15
 
15
16
  const DURATION_CONFIG = {
@@ -70,6 +71,8 @@ const disableAddItemTooltipContent = (
70
71
  </Alert>
71
72
  );
72
73
 
74
+ subscribeToTransitionEvent();
75
+
73
76
  export const TransitionRepeaterControl = createControl(
74
77
  ( {
75
78
  recentlyUsedListGetter,
@@ -0,0 +1,38 @@
1
+ class EventBus {
2
+ private listeners = new Map< string, Set< ( data?: unknown ) => void > >();
3
+
4
+ subscribe( eventName: string, callback: ( data?: unknown ) => void ) {
5
+ if ( ! this.listeners.has( eventName ) ) {
6
+ this.listeners.set( eventName, new Set() );
7
+ }
8
+ const eventListeners = this.listeners.get( eventName );
9
+ if ( eventListeners ) {
10
+ eventListeners.add( callback );
11
+ }
12
+ }
13
+
14
+ unsubscribe( eventName: string, callback: ( data?: unknown ) => void ) {
15
+ const eventListeners = this.listeners.get( eventName );
16
+ if ( ! eventListeners ) {
17
+ return;
18
+ }
19
+
20
+ eventListeners.delete( callback );
21
+ if ( eventListeners.size === 0 ) {
22
+ this.listeners.delete( eventName );
23
+ }
24
+ }
25
+
26
+ emit( eventName: string, data?: unknown ) {
27
+ const eventListeners = this.listeners.get( eventName );
28
+ if ( eventListeners ) {
29
+ eventListeners.forEach( ( callback ) => callback( data ) );
30
+ }
31
+ }
32
+
33
+ clearAll(): void {
34
+ this.listeners.clear();
35
+ }
36
+ }
37
+
38
+ export const eventBus = new EventBus();