@elementor/editor-controls 3.32.0-22 → 3.32.0-24

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-22",
4
+ "version": "3.32.0-24",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,24 +40,26 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "3.32.0-22",
44
- "@elementor/editor-elements": "3.32.0-22",
45
- "@elementor/editor-props": "3.32.0-22",
46
- "@elementor/editor-responsive": "3.32.0-22",
47
- "@elementor/editor-ui": "3.32.0-22",
48
- "@elementor/editor-v1-adapters": "3.32.0-22",
49
- "@elementor/env": "3.32.0-22",
50
- "@elementor/http-client": "3.32.0-22",
43
+ "@elementor/editor-current-user": "3.32.0-24",
44
+ "@elementor/editor-elements": "3.32.0-24",
45
+ "@elementor/editor-props": "3.32.0-24",
46
+ "@elementor/editor-responsive": "3.32.0-24",
47
+ "@elementor/editor-ui": "3.32.0-24",
48
+ "@elementor/editor-v1-adapters": "3.32.0-24",
49
+ "@elementor/env": "3.32.0-24",
50
+ "@elementor/http-client": "3.32.0-24",
51
51
  "@elementor/icons": "^1.51.1",
52
- "@elementor/locations": "3.32.0-22",
53
- "@elementor/query": "3.32.0-22",
54
- "@elementor/session": "3.32.0-22",
52
+ "@elementor/locations": "3.32.0-24",
53
+ "@elementor/query": "3.32.0-24",
54
+ "@elementor/session": "3.32.0-24",
55
55
  "@elementor/ui": "1.36.2",
56
- "@elementor/utils": "3.32.0-22",
57
- "@elementor/wp-media": "3.32.0-22",
58
- "@wordpress/i18n": "^5.13.0"
56
+ "@elementor/utils": "3.32.0-24",
57
+ "@elementor/wp-media": "3.32.0-24",
58
+ "@wordpress/i18n": "^5.13.0",
59
+ "@monaco-editor/react": "^4.7.0"
59
60
  },
60
61
  "devDependencies": {
62
+ "monaco-types": "^0.1.0",
61
63
  "tsup": "^8.3.5"
62
64
  },
63
65
  "peerDependencies": {
@@ -0,0 +1,45 @@
1
+ import { Box, Button, styled } from '@elementor/ui';
2
+
3
+ export const EditorWrapper = styled( Box )`
4
+ border: 1px solid var( --e-a-border-color );
5
+ border-radius: 8px;
6
+ padding: 10px 12px;
7
+ position: relative;
8
+ height: 200px;
9
+
10
+ .monaco-editor .colorpicker-widget {
11
+ z-index: 99999999 !important;
12
+ }
13
+ `;
14
+
15
+ export const ResizeHandle = styled( Button )`
16
+ position: absolute;
17
+ bottom: 0;
18
+ left: 0;
19
+ right: 0;
20
+ height: 6px;
21
+ cursor: ns-resize;
22
+ background: transparent;
23
+ border: none;
24
+ padding: 0;
25
+
26
+ &:hover {
27
+ background: rgba( 0, 0, 0, 0.05 );
28
+ }
29
+
30
+ &:active {
31
+ background: rgba( 0, 0, 0, 0.1 );
32
+ }
33
+
34
+ &::after {
35
+ content: '';
36
+ position: absolute;
37
+ top: 50%;
38
+ left: 50%;
39
+ transform: translate( -50%, -50% );
40
+ width: 30px;
41
+ height: 2px;
42
+ background: var( --e-a-border-color );
43
+ border-radius: 1px;
44
+ }
45
+ `;
@@ -0,0 +1,169 @@
1
+ import * as React from 'react';
2
+ import type { editor, MonacoEditor } from 'monaco-types';
3
+ import { useActiveBreakpoint } from '@elementor/editor-responsive';
4
+ import { useTheme } from '@elementor/ui';
5
+ import { Editor } from '@monaco-editor/react';
6
+
7
+ import { EditorWrapper } from './css-editor.styles';
8
+ import { setCustomSyntaxRules, validate } from './css-validation';
9
+ import { ResizeHandleComponent } from './resize-handle';
10
+
11
+ type CssEditorProps = {
12
+ value: string;
13
+ onChange: ( value: string ) => void;
14
+ };
15
+
16
+ const setVisualContent = ( value: string ): string => {
17
+ const trimmed = value.trim();
18
+ return `element.style {\n${ trimmed ? ' ' + trimmed.replace( /\n/g, '\n ' ) + '\n' : ' \n' }}`;
19
+ };
20
+
21
+ const getActual = ( value: string ): string => {
22
+ const lines = value.split( '\n' );
23
+ if ( lines.length < 2 ) {
24
+ return '';
25
+ }
26
+ return lines
27
+ .slice( 1, -1 )
28
+ .map( ( line ) => line.replace( /^ {2}/, '' ) )
29
+ .join( '\n' );
30
+ };
31
+
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
+ const createEditorDidMountHandler = (
70
+ editorRef: React.MutableRefObject< editor.IStandaloneCodeEditor | null >,
71
+ monacoRef: React.MutableRefObject< MonacoEditor | null >,
72
+ debounceTimer: React.MutableRefObject< NodeJS.Timeout | null >,
73
+ onChange: ( value: string ) => void
74
+ ) => {
75
+ return ( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ) => {
76
+ editorRef.current = editor;
77
+ monacoRef.current = monaco;
78
+
79
+ preventChangeOnVisualContent( editor, monaco );
80
+
81
+ setCustomSyntaxRules( editor, monaco );
82
+
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;
107
+ } );
108
+ };
109
+ };
110
+
111
+ export const CssEditor = ( { value, onChange }: CssEditorProps ) => {
112
+ const theme = useTheme();
113
+ const containerRef = React.useRef< HTMLDivElement >( null );
114
+ const editorRef = React.useRef< editor.IStandaloneCodeEditor | null >( null );
115
+ const monacoRef = React.useRef< MonacoEditor | null >( null );
116
+ const debounceTimer = React.useRef< NodeJS.Timeout | null >( null );
117
+ const activeBreakpoint = useActiveBreakpoint();
118
+
119
+ const handleResize = React.useCallback( () => {
120
+ editorRef.current?.layout();
121
+ }, [] );
122
+
123
+ const handleHeightChange = React.useCallback( ( height: number ) => {
124
+ if ( containerRef.current ) {
125
+ containerRef.current.style.height = `${ height }px`;
126
+ }
127
+ }, [] );
128
+
129
+ const handleEditorDidMount = createEditorDidMountHandler( editorRef, monacoRef, debounceTimer, onChange );
130
+
131
+ React.useEffect( () => {
132
+ const timerRef = debounceTimer;
133
+ return () => {
134
+ const timer = timerRef.current;
135
+ if ( timer ) {
136
+ clearTimeout( timer );
137
+ }
138
+ };
139
+ }, [] );
140
+
141
+ return (
142
+ <EditorWrapper ref={ containerRef }>
143
+ <Editor
144
+ key={ activeBreakpoint }
145
+ height="100%"
146
+ language="css"
147
+ theme={ theme.palette.mode === 'dark' ? 'vs-dark' : 'vs' }
148
+ defaultValue={ setVisualContent( value ) }
149
+ onMount={ handleEditorDidMount }
150
+ options={ {
151
+ lineNumbers: 'off',
152
+ folding: false,
153
+ showFoldingControls: 'never',
154
+ minimap: { enabled: false },
155
+ fontFamily: 'Roboto, Arial, Helvetica, Verdana, sans-serif',
156
+ fontSize: 12,
157
+ renderLineHighlight: 'none',
158
+ hideCursorInOverviewRuler: true,
159
+ fixedOverflowWidgets: true,
160
+ } }
161
+ />
162
+ <ResizeHandleComponent
163
+ onResize={ handleResize }
164
+ containerRef={ containerRef }
165
+ onHeightChange={ handleHeightChange }
166
+ />
167
+ </EditorWrapper>
168
+ );
169
+ };
@@ -0,0 +1,62 @@
1
+ import type { editor, MonacoEditor } from 'monaco-types';
2
+ import { __ } from '@wordpress/i18n';
3
+
4
+ const forbiddenPatterns = [
5
+ {
6
+ pattern: ':hover',
7
+ message: __(
8
+ 'The use of pseudo-states is not permitted. Instead, switch to the desired pseudo state and add your custom code there.',
9
+ 'elementor'
10
+ ),
11
+ },
12
+ {
13
+ pattern: ':active',
14
+ message: __(
15
+ 'The use of pseudo-states is not permitted. Instead, switch to the desired pseudo state and add your custom code there.',
16
+ 'elementor'
17
+ ),
18
+ },
19
+ {
20
+ pattern: '@media',
21
+ message: __(
22
+ 'The use of @media is not permitted. Instead, switch to the desired breakpoint and add your custom code there.',
23
+ 'elementor'
24
+ ),
25
+ },
26
+ ];
27
+
28
+ export function setCustomSyntaxRules( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): boolean {
29
+ const model = editor.getModel();
30
+ if ( ! model ) {
31
+ return true;
32
+ }
33
+
34
+ const customMarkers: editor.IMarkerData[] = [];
35
+
36
+ forbiddenPatterns.forEach( ( rule ) => {
37
+ const matches = model.findMatches( rule.pattern, true, false, true, null, true );
38
+ matches.forEach( ( match ) => {
39
+ customMarkers.push( {
40
+ severity: monaco.MarkerSeverity.Error,
41
+ message: rule.message,
42
+ startLineNumber: match.range.startLineNumber,
43
+ startColumn: match.range.startColumn,
44
+ endLineNumber: match.range.endLineNumber,
45
+ endColumn: match.range.endColumn,
46
+ source: 'custom-css-rules',
47
+ } );
48
+ } );
49
+ } );
50
+
51
+ monaco.editor.setModelMarkers( model, 'custom-css-rules', customMarkers );
52
+ return customMarkers.length === 0;
53
+ }
54
+
55
+ export function validate( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): boolean {
56
+ const model = editor.getModel();
57
+ if ( ! model ) {
58
+ return true;
59
+ }
60
+ const allMarkers = monaco.editor.getModelMarkers( { resource: model.uri } );
61
+ return allMarkers.filter( ( marker ) => marker.severity === monaco.MarkerSeverity.Error ).length === 0;
62
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+
3
+ import { ResizeHandle } from './css-editor.styles';
4
+
5
+ type ResizeHandleProps = {
6
+ onResize: ( height: number ) => void;
7
+ containerRef: React.RefObject< HTMLDivElement >;
8
+ onHeightChange?: ( height: number ) => void;
9
+ };
10
+
11
+ export const ResizeHandleComponent = ( { onResize, containerRef, onHeightChange }: ResizeHandleProps ) => {
12
+ const handleResizeMove = React.useCallback(
13
+ ( e: MouseEvent ) => {
14
+ const container = containerRef.current;
15
+ if ( ! container ) {
16
+ return;
17
+ }
18
+ const containerRect = container.getBoundingClientRect();
19
+ const newHeight = Math.max( 100, e.clientY - containerRect.top );
20
+ onHeightChange?.( newHeight );
21
+ onResize( newHeight );
22
+ },
23
+ [ containerRef, onResize, onHeightChange ]
24
+ );
25
+
26
+ const handleResizeEnd = React.useCallback( () => {
27
+ document.removeEventListener( 'mousemove', handleResizeMove );
28
+ document.removeEventListener( 'mouseup', handleResizeEnd );
29
+ }, [ handleResizeMove ] );
30
+
31
+ const handleResizeStart = React.useCallback(
32
+ ( e: React.MouseEvent ) => {
33
+ e.preventDefault();
34
+ e.stopPropagation();
35
+ document.addEventListener( 'mousemove', handleResizeMove );
36
+ document.addEventListener( 'mouseup', handleResizeEnd );
37
+ },
38
+ [ handleResizeMove, handleResizeEnd ]
39
+ );
40
+
41
+ React.useEffect( () => {
42
+ return () => {
43
+ document.removeEventListener( 'mousemove', handleResizeMove );
44
+ document.removeEventListener( 'mouseup', handleResizeEnd );
45
+ };
46
+ }, [ handleResizeMove, handleResizeEnd ] );
47
+
48
+ return (
49
+ <ResizeHandle
50
+ onMouseDown={ handleResizeStart }
51
+ aria-label="Resize editor height"
52
+ title="Drag to resize editor height"
53
+ />
54
+ );
55
+ };
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export { transitionProperties, transitionsItemsList } from './controls/transitio
34
34
  // components
35
35
  export { ControlFormLabel } from './components/control-form-label';
36
36
  export { ControlToggleButtonGroup } from './components/control-toggle-button-group';
37
+ export { CssEditor } from './components/css-code-editor/css-editor';
37
38
 
38
39
  // types
39
40
  export type { ControlComponent } from './create-control';