@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/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +177 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -14
- package/src/components/css-code-editor/css-editor.styles.ts +9 -2
- package/src/components/css-code-editor/css-editor.tsx +48 -72
- package/src/components/css-code-editor/css-validation.ts +13 -0
- package/src/components/css-code-editor/visual-content-change-protection.ts +69 -0
- package/src/components/unstable-repeater/context/repeater-context.tsx +16 -1
- package/src/controls/transition-control/trainsition-events.ts +28 -0
- package/src/controls/transition-control/transition-repeater-control.tsx +3 -0
- package/src/services/event-bus.ts +38 -0
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.
|
|
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.
|
|
44
|
-
"@elementor/editor-elements": "3.
|
|
45
|
-
"@elementor/editor-props": "3.
|
|
46
|
-
"@elementor/editor-responsive": "3.
|
|
47
|
-
"@elementor/editor-ui": "3.
|
|
48
|
-
"@elementor/editor-v1-adapters": "3.
|
|
49
|
-
"@elementor/env": "3.
|
|
50
|
-
"@elementor/http-client": "3.
|
|
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.
|
|
53
|
-
"@elementor/query": "3.
|
|
54
|
-
"@elementor/session": "3.
|
|
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.
|
|
57
|
-
"@elementor/wp-media": "3.
|
|
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 .
|
|
11
|
-
|
|
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
|
|
41
|
+
preventChangeOnVisualContent( editor );
|
|
80
42
|
|
|
81
43
|
setCustomSyntaxRules( editor, monaco );
|
|
82
44
|
|
|
83
|
-
editor.
|
|
84
|
-
|
|
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
|
|
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
|
-
|
|
118
|
+
value={ setVisualContent( value ) }
|
|
149
119
|
onMount={ handleEditorDidMount }
|
|
120
|
+
onChange={ handleEditorChange }
|
|
150
121
|
options={ {
|
|
151
|
-
lineNumbers: '
|
|
152
|
-
folding:
|
|
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< {
|
|
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();
|