suneditor 3.0.0-beta.2 → 3.0.0-beta.20

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 (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,1484 +1,1550 @@
1
- /**
2
- * @fileoverview eventManager class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { dom, unicode, numbers, env, converter } from '../../helper';
7
- import { _DragHandle } from '../../modules';
8
-
9
- // event handlers
10
- import { ButtonsHandler, OnClick_menuTray, OnClick_toolbar } from './eventHandlers/handler_toolbar';
11
- import { OnMouseDown_wysiwyg, OnMouseUp_wysiwyg, OnClick_wysiwyg, OnMouseMove_wysiwyg, OnMouseLeave_wysiwyg } from './eventHandlers/handler_ww_mouse';
12
- import { OnInput_wysiwyg, OnKeyDown_wysiwyg, OnKeyUp_wysiwyg } from './eventHandlers/handler_ww_key_input';
13
- import { OnPaste_wysiwyg, OnCopy_wysiwyg, OnCut_wysiwyg } from './eventHandlers/handler_ww_clipboard';
14
- import { OnDragOver_wysiwyg, OnDragEnd_wysiwyg, OnDrop_wysiwyg } from './eventHandlers/handler_ww_dragDrop';
15
-
16
- const { _w, ON_OVER_COMPONENT, isMobile } = env;
17
-
18
- /**
19
- * @typedef {Omit<EventManager & Partial<__se__EditorInjector>, 'eventManager'>} EventManagerThis
20
- */
21
-
22
- /**
23
- * @constructor
24
- * @this {EventManagerThis}
25
- * @description Event manager, editor's all event management class
26
- * @param {__se__EditorCore} editor - The root editor instance
27
- * @property {__se__EditorCore} editor - The root editor instance
28
- */
29
- function EventManager(editor) {
30
- CoreInjector.call(this, editor);
31
-
32
- /**
33
- * @description Old browsers: When there is no 'e.isComposing' in the keyup event
34
- * @type {boolean}
35
- */
36
- this.isComposing = false;
37
-
38
- /** @type {Array<*>} */
39
- this._events = [];
40
- /** @type {RegExp} */
41
- this._onButtonsCheck = new RegExp(`^(${Object.keys(editor.options.get('_defaultStyleTagMap')).join('|')})$`, 'i');
42
- /** @type {boolean} */
43
- this._onShortcutKey = false;
44
- /** @type {number} */
45
- this._balloonDelay = null;
46
- /** @type {ResizeObserver} */
47
- this._wwFrameObserver = null;
48
- /** @type {ResizeObserver} */
49
- this._toolbarObserver = null;
50
- /** @type {Element|null} */
51
- this._lineBreakComp = null;
52
- /** @type {Object<string, *>|null} */
53
- this._formatAttrsTemp = null;
54
- /** @type {number} */
55
- this._resizeClientY = 0;
56
- /** @type {__se__GlobalEventInfo|null} */
57
- this.__resize_editor = null;
58
- /** @type {__se__GlobalEventInfo|null} */
59
- this.__close_move = null;
60
- /** @type {__se__GlobalEventInfo|null} */
61
- this.__geckoActiveEvent = null;
62
- /** @type {Array<Element>} */
63
- this.__scrollparents = [];
64
- /** @type {Array<Node>} */
65
- this.__cacheStyleNodes = [];
66
- /** @type {__se__GlobalEventInfo|null} */
67
- this.__selectionSyncEvent = null;
68
-
69
- // input plugins
70
- /** @type {boolean} */
71
- this._inputFocus = false;
72
- /** @type {Object<string, *>|null} */
73
- this.__inputPlugin = null;
74
- /** @type {?__se__EventInfo=} */
75
- this.__inputBlurEvent = null;
76
- /** @type {?__se__EventInfo=} */
77
- this.__inputKeyEvent = null;
78
-
79
- // viewport
80
- /** @type {HTMLInputElement} */
81
- this.__focusTemp = this.carrierWrapper.querySelector('.__se__focus__temp__');
82
- /** @type {number|void} */
83
- this.__retainTimer = null;
84
- /** @type {Element} */
85
- this.__eventDoc = null;
86
- /** @type {string} */
87
- this.__secopy = null;
88
- }
89
-
90
- EventManager.prototype = {
91
- /**
92
- * @this {EventManagerThis}
93
- * @description Register for an event.
94
- * - Only events registered with this method are unregistered or re-registered when methods such as 'setOptions', 'destroy' are called.
95
- * @param {*} target Target element
96
- * @param {string} type Event type
97
- * @param {(...args: *) => *} listener Event handler
98
- * @param {boolean|AddEventListenerOptions=} useCapture Event useCapture option
99
- * @return {__se__EventInfo|null} Registered event information
100
- */
101
- addEvent(target, type, listener, useCapture) {
102
- if (!target) return null;
103
- if (!numbers.is(target.length) || target.nodeName || (!Array.isArray(target) && target.length < 1)) target = [target];
104
- if (target.length === 0) return null;
105
-
106
- const len = target.length;
107
- for (let i = 0; i < len; i++) {
108
- target[i].addEventListener(type, listener, useCapture);
109
- this._events.push({
110
- target: target[i],
111
- type,
112
- listener,
113
- useCapture
114
- });
115
- }
116
-
117
- return {
118
- target: len > 1 ? target : target[0],
119
- type,
120
- listener,
121
- useCapture
122
- };
123
- },
124
-
125
- /**
126
- * @this {EventManagerThis}
127
- * @description Remove event
128
- * @param {__se__EventInfo} params event info = this.addEvent()
129
- * @returns {undefined|null} Success: null, Not found: undefined
130
- */
131
- removeEvent(params) {
132
- if (!params) return;
133
-
134
- let target = params.target;
135
- const type = params.type;
136
- const listener = params.listener;
137
- const useCapture = params.useCapture;
138
-
139
- if (!target) return;
140
- if (!numbers.is(target.length) || target.nodeName || (!Array.isArray(target) && target.length < 1)) target = /** @type {Array<Element>} */ ([target]);
141
- if (target.length === 0) return;
142
-
143
- for (let i = 0, len = target.length; i < len; i++) {
144
- target[i].removeEventListener(type, listener, useCapture);
145
- }
146
-
147
- return null;
148
- },
149
-
150
- /**
151
- * @this {EventManagerThis}
152
- * @description Add an event to document.
153
- * - When created as an Iframe, the same event is added to the document in the Iframe.
154
- * @param {string} type Event type
155
- * @param {(...args: *) => *} listener Event listener
156
- * @param {boolean|AddEventListenerOptions=} useCapture Use event capture
157
- * @return {__se__GlobalEventInfo} Registered event information
158
- */
159
- addGlobalEvent(type, listener, useCapture) {
160
- if (this.editor.frameOptions.get('iframe')) {
161
- this.editor.frameContext.get('_ww').addEventListener(type, listener, useCapture);
162
- }
163
- this._w.addEventListener(type, listener, useCapture);
164
- return {
165
- type,
166
- listener,
167
- useCapture
168
- };
169
- },
170
-
171
- /**
172
- * @this {EventManagerThis}
173
- * @description Remove events from document.
174
- * - When created as an Iframe, the event of the document inside the Iframe is also removed.
175
- * @param {string|__se__GlobalEventInfo} type Event type or (Event info = this.addGlobalEvent())
176
- * @param {(...args: *) => *=} listener Event listener
177
- * @param {boolean|AddEventListenerOptions=} useCapture Use event capture
178
- * @returns {undefined|null} Success: null, Not found: undefined
179
- */
180
- removeGlobalEvent(type, listener, useCapture) {
181
- if (!type) return;
182
-
183
- if (typeof type === 'object') {
184
- listener = type.listener;
185
- useCapture = type.useCapture;
186
- type = type.type;
187
- }
188
- if (this.editor.frameOptions.get('iframe')) {
189
- this.editor.frameContext.get('_ww').removeEventListener(type, listener, useCapture);
190
- }
191
- this._w.removeEventListener(type, listener, useCapture);
192
-
193
- return null;
194
- },
195
-
196
- /**
197
- * @this {EventManagerThis}
198
- * @description Activates the corresponding button with the tags information of the current cursor position,
199
- * - such as 'bold', 'underline', etc., and executes the 'active' method of the plugins.
200
- * @param {?Node=} selectionNode selectionNode
201
- * @returns {Node|undefined} selectionNode
202
- */
203
- applyTagEffect(selectionNode) {
204
- selectionNode = selectionNode || this.selection.getNode();
205
- if (selectionNode === this.editor.effectNode) return;
206
- this.editor.effectNode = selectionNode;
207
-
208
- const marginDir = this.options.get('_rtl') ? 'marginRight' : 'marginLeft';
209
- const plugins = this.plugins;
210
- const commandTargets = this.editor.commandTargets;
211
- const classOnCheck = this._onButtonsCheck;
212
- const styleCommand = this.options.get('_styleCommandMap');
213
- const commandMapNodes = [];
214
- const currentNodes = [];
215
-
216
- const styleTags = this.options.get('_textStyleTags');
217
- const styleNodes = [];
218
-
219
- const activeCommands = this.editor.activeCommands;
220
- const cLen = activeCommands.length;
221
- let nodeName = '';
222
-
223
- if (this.component.is(selectionNode) && !this.component.__selectionSelected) {
224
- const component = this.component.get(selectionNode);
225
- if (!component) return;
226
- this.component.select(component.target, component.pluginName);
227
- return;
228
- }
229
-
230
- while (selectionNode.firstChild) {
231
- selectionNode = selectionNode.firstChild;
232
- }
233
-
234
- const fc = this.editor.frameContext;
235
- const notReadonly = !fc.get('isReadOnly');
236
- for (let element = selectionNode; !dom.check.isWysiwygFrame(element); element = element.parentElement) {
237
- if (!element) break;
238
- if (element.nodeType !== 1 || dom.check.isBreak(element)) continue;
239
- if (this._isNonFocusNode(element)) {
240
- this.editor.blur();
241
- return;
242
- }
243
-
244
- nodeName = element.nodeName.toLowerCase();
245
- currentNodes.push(nodeName);
246
- if (styleTags.includes(nodeName) && !this.format.isLine(nodeName)) styleNodes.push(element);
247
-
248
- /* Active plugins */
249
- if (notReadonly) {
250
- for (let c = 0, name; c < cLen; c++) {
251
- name = activeCommands[c];
252
- if (
253
- !commandMapNodes.includes(name) &&
254
- commandTargets.get(name) &&
255
- commandTargets.get(name).filter((e) => {
256
- return plugins[name]?.active(element, e);
257
- }).length > 0
258
- ) {
259
- commandMapNodes.push(name);
260
- }
261
- }
262
- }
263
-
264
- /** indent, outdent */
265
- if (this.format.isLine(element)) {
266
- /* Outdent */
267
- if (!commandMapNodes.includes('outdent') && commandTargets.has('outdent') && (dom.check.isListCell(element) || (element.style[marginDir] && numbers.get(element.style[marginDir], 0) > 0))) {
268
- if (
269
- commandTargets.get('outdent').filter((e) => {
270
- if (dom.check.isImportantDisabled(e)) return false;
271
- e.disabled = false;
272
- return true;
273
- }).length > 0
274
- ) {
275
- commandMapNodes.push('outdent');
276
- }
277
- }
278
- /* Indent */
279
- if (!commandMapNodes.includes('indent') && commandTargets.has('indent')) {
280
- const indentDisable = dom.check.isListCell(element) && !element.previousElementSibling;
281
- if (
282
- commandTargets.get('indent').filter((e) => {
283
- if (dom.check.isImportantDisabled(e)) return false;
284
- e.disabled = indentDisable;
285
- return true;
286
- }).length > 0
287
- ) {
288
- commandMapNodes.push('indent');
289
- }
290
- }
291
-
292
- continue;
293
- }
294
-
295
- /** default active buttons [strong, ins, em, del, sub, sup] */
296
- if (classOnCheck.test(nodeName)) {
297
- nodeName = styleCommand[nodeName] || nodeName;
298
- commandMapNodes.push(nodeName);
299
- dom.utils.addClass(commandTargets.get(nodeName), 'active');
300
- }
301
- }
302
-
303
- this._setKeyEffect(commandMapNodes);
304
-
305
- // cache style nodes
306
- this.__cacheStyleNodes = styleNodes.reverse();
307
-
308
- /** save current nodes */
309
- this.status.currentNodes = currentNodes.reverse();
310
- this.status.currentNodesMap = commandMapNodes;
311
-
312
- /** Displays the current node structure to statusbar */
313
- if (this.editor.frameOptions.get('statusbar_showPathLabel') && fc.get('navigation')) {
314
- fc.get('navigation').textContent = this.options.get('_rtl') ? this.status.currentNodes.reverse().join(' < ') : this.status.currentNodes.join(' > ');
315
- }
316
-
317
- return selectionNode;
318
- },
319
-
320
- /**
321
- * @private
322
- * @this {EventManagerThis}
323
- * @description Gives an active effect when the mouse down event is blocked. (Used when "env.isGecko" is true)
324
- * @param {Node} target Target element
325
- * @private
326
- */
327
- _injectActiveEvent(target) {
328
- dom.utils.addClass(target, '__se__active');
329
- this.__geckoActiveEvent = this.addGlobalEvent('mouseup', () => {
330
- dom.utils.removeClass(target, '__se__active');
331
- this.__geckoActiveEvent = this.removeGlobalEvent(this.__geckoActiveEvent);
332
- });
333
- },
334
-
335
- /**
336
- * @private
337
- * @this {EventManagerThis}
338
- * @description remove class, display text.
339
- * @param {Array<string>} ignoredList Igonred button list
340
- * @private
341
- */
342
- _setKeyEffect(ignoredList) {
343
- const activeCommands = this.editor.activeCommands;
344
- const commandTargets = this.editor.commandTargets;
345
- const plugins = this.plugins;
346
- for (let i = 0, len = activeCommands.length, k, c, p; i < len; i++) {
347
- k = activeCommands[i];
348
- if (ignoredList.includes(k) || !(c = commandTargets.get(k))) continue;
349
-
350
- p = plugins[k];
351
- for (let j = 0, jLen = c.length, e; j < jLen; j++) {
352
- e = c[j];
353
- if (!e) continue;
354
- if (p) {
355
- p.active(null, e);
356
- } else if (/^outdent$/i.test(k)) {
357
- if (!dom.check.isImportantDisabled(e)) e.disabled = true;
358
- } else if (/^indent$/i.test(k)) {
359
- if (!dom.check.isImportantDisabled(e)) e.disabled = false;
360
- } else {
361
- dom.utils.removeClass(e, 'active');
362
- }
363
- }
364
- }
365
- },
366
-
367
- /**
368
- * @private
369
- * @this {EventManagerThis}
370
- * @description Show toolbar-balloon with delay.
371
- */
372
- _showToolbarBalloonDelay() {
373
- if (this._balloonDelay) {
374
- _w.clearTimeout(this._balloonDelay);
375
- }
376
-
377
- this._balloonDelay = _w.setTimeout(() => {
378
- _w.clearTimeout(this._balloonDelay);
379
- this._balloonDelay = null;
380
- if (this.editor.isSubBalloon) this.subToolbar._showBalloon();
381
- else this.toolbar._showBalloon();
382
- }, 250);
383
- },
384
-
385
- /**
386
- * @private
387
- * @this {EventManagerThis}
388
- * @description Show or hide the toolbar-balloon.
389
- */
390
- _toggleToolbarBalloon() {
391
- this.selection._init();
392
- const range = this.selection.getRange();
393
- const hasSubMode = this.options.has('_subMode');
394
-
395
- if (!(hasSubMode ? this.editor.isSubBalloonAlways : this.editor.isBalloonAlways) && range.collapsed) {
396
- if (hasSubMode) this._hideToolbar_sub();
397
- else this._hideToolbar();
398
- } else {
399
- if (hasSubMode) this.subToolbar._showBalloon(range);
400
- else this.toolbar._showBalloon(range);
401
- }
402
- },
403
-
404
- /**
405
- * @private
406
- * @this {EventManagerThis}
407
- * @description Hide the toolbar.
408
- */
409
- _hideToolbar() {
410
- if (!this.editor._notHideToolbar && !this.editor.frameContext.get('isFullScreen')) {
411
- this.toolbar.hide();
412
- }
413
- },
414
-
415
- /**
416
- * @private
417
- * @this {EventManagerThis}
418
- * @description Hide the Sub-Toolbar.
419
- */
420
- _hideToolbar_sub() {
421
- if (this.subToolbar && !this.editor._notHideToolbar) {
422
- this.subToolbar.hide();
423
- }
424
- },
425
-
426
- /**
427
- * @private
428
- * @this {EventManagerThis}
429
- * @description Checks if a node is a non-focusable element(.data-se-non-focus). (e.g. fileUpload.component > span)
430
- * @param {Node} node Node to check
431
- * @returns {boolean} True if the node is non-focusable, otherwise false
432
- */
433
- _isNonFocusNode(node) {
434
- return dom.check.isElement(node) && node.getAttribute('data-se-non-focus') === 'true';
435
- },
436
-
437
- /**
438
- * @private
439
- * @this {EventManagerThis}
440
- * @description Determines if the "range" is within an uneditable node.
441
- * @param {Range} range The range object
442
- * @param {boolean} isFront Whether to check the start or end of the range
443
- * @returns {Node|null} The uneditable node if found, otherwise null
444
- */
445
- _isUneditableNode(range, isFront) {
446
- const container = isFront ? range.startContainer : range.endContainer;
447
- const offset = isFront ? range.startOffset : range.endOffset;
448
- const siblingKey = isFront ? 'previousSibling' : 'nextSibling';
449
- const isElement = container.nodeType === 1;
450
-
451
- let siblingNode;
452
- if (isElement) {
453
- siblingNode = /** @type {HTMLElement} */ (this._isUneditableNode_getSibling(container.childNodes[offset], siblingKey, container));
454
- return siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false' ? siblingNode : null;
455
- } else {
456
- siblingNode = /** @type {HTMLElement} */ (this._isUneditableNode_getSibling(container, siblingKey, container));
457
- return dom.check.isEdgePoint(container, offset, isFront ? 'front' : 'end') && siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false' ? siblingNode : null;
458
- }
459
- },
460
-
461
- /**
462
- * @private
463
- * @this {EventManagerThis}
464
- * @description Retrieves the sibling node of a selected node if it is uneditable.
465
- * - Used only in `_isUneditableNode`.
466
- * @param {Node} selectNode The selected node
467
- * @param {string} siblingKey The key to access the sibling (`previousSibling` or `nextSibling`)
468
- * @param {Node} container The parent container node
469
- * @returns {Node|null} The sibling node if found, otherwise null
470
- */
471
- _isUneditableNode_getSibling(selectNode, siblingKey, container) {
472
- if (!selectNode) return null;
473
- let siblingNode = selectNode[siblingKey];
474
-
475
- if (!siblingNode) {
476
- siblingNode = this.format.getLine(container);
477
- siblingNode = siblingNode ? siblingNode[siblingKey] : null;
478
- if (siblingNode && !this.component.is(siblingNode)) siblingNode = siblingKey === 'previousSibling' ? siblingNode.firstChild : siblingNode.lastChild;
479
- else return null;
480
- }
481
-
482
- return siblingNode;
483
- },
484
-
485
- /**
486
- * @private
487
- * @this {EventManagerThis}
488
- * @description Deletes specific elements such as tables in "Firefox" and media elements (image, video, audio) in "Chrome".
489
- * - Handles deletion logic based on selection range and node types.
490
- * @returns {boolean} Returns `true` if an element was deleted and focus was adjusted, otherwise `false`.
491
- */
492
- _hardDelete() {
493
- const range = this.selection.getRange();
494
- const sc = range.startContainer;
495
- const ec = range.endContainer;
496
-
497
- // table
498
- const sCell = this.format.getBlock(sc);
499
- const eCell = this.format.getBlock(ec);
500
- const sIsCell = dom.check.isTableCell(sCell);
501
- const eIsCell = dom.check.isTableCell(eCell);
502
- if (((sIsCell && !sCell.previousElementSibling && !sCell.parentElement.previousElementSibling) || (eIsCell && !eCell.nextElementSibling && !eCell.parentElement.nextElementSibling)) && sCell !== eCell) {
503
- const ancestor = dom.query.getParentElement(range.commonAncestorContainer, dom.check.isFigure) || range.commonAncestorContainer;
504
- if (!sIsCell) {
505
- dom.utils.removeItem(dom.query.getParentElement(eCell, (current) => ancestor === current));
506
- } else if (!eIsCell) {
507
- dom.utils.removeItem(dom.query.getParentElement(sCell, (current) => ancestor === current));
508
- } else {
509
- dom.utils.removeItem(dom.query.getParentElement(sCell, (current) => ancestor === current));
510
- this.editor._nativeFocus();
511
- return true;
512
- }
513
- }
514
-
515
- // component
516
- const sComp = sc.nodeType === 1 ? dom.query.getParentElement(sc, '.se-component') : null;
517
- const eComp = ec.nodeType === 1 ? dom.query.getParentElement(ec, '.se-component') : null;
518
- if (sComp) dom.utils.removeItem(sComp);
519
- if (eComp) dom.utils.removeItem(eComp);
520
-
521
- return false;
522
- },
523
-
524
- /**
525
- * @private
526
- * @this {EventManagerThis}
527
- * @description If there is no default format, add a line and move 'selection'.
528
- * @param {string|null} formatName Format tag name (default: 'P')
529
- */
530
- _setDefaultLine(formatName) {
531
- if (!this.options.get('__lineFormatFilter')) return null;
532
- if (this.editor._fileManager.pluginRegExp.test(this.editor.currentControllerName)) return;
533
-
534
- const range = this.selection.getRange();
535
- const commonCon = /** @type {HTMLElement} */ (range.commonAncestorContainer);
536
- const startCon = range.startContainer;
537
- const rangeEl = this.format.getBlock(commonCon, null);
538
-
539
- /** @type {Node} */
540
- let focusNode;
541
- let offset, format;
542
-
543
- if (rangeEl) {
544
- format = dom.utils.createElement(formatName || this.options.get('defaultLine'));
545
- format.innerHTML = rangeEl.innerHTML;
546
- if (format.childNodes.length === 0) format.innerHTML = unicode.zeroWidthSpace;
547
-
548
- rangeEl.innerHTML = format.outerHTML;
549
- format = rangeEl.firstChild;
550
- focusNode = dom.query.getEdgeChildNodes(format, null).sc;
551
-
552
- if (!focusNode) {
553
- focusNode = dom.utils.createTextNode(unicode.zeroWidthSpace);
554
- format.insertBefore(focusNode, format.firstChild);
555
- }
556
-
557
- offset = focusNode.textContent.length;
558
- this.selection.setRange(focusNode, offset, focusNode, offset);
559
- return;
560
- }
561
-
562
- if (commonCon.nodeType === 3 && this.component.is(commonCon.parentElement)) {
563
- const compInfo = this.component.get(commonCon.parentElement);
564
- const container = compInfo.container;
565
-
566
- if (commonCon.parentElement === container) {
567
- const siblingEl = commonCon.nextElementSibling ? container : container.nextElementSibling;
568
- const el = dom.utils.createElement(this.options.get('defaultLine'), null, commonCon);
569
- container.parentElement.insertBefore(el, siblingEl);
570
- this.editor.focusEdge(el);
571
- return;
572
- }
573
-
574
- this.component.select(compInfo.target, compInfo.pluginName);
575
- return null;
576
- } else if (commonCon.nodeType === 1 && commonCon.getAttribute('data-se-embed') === 'true') {
577
- let el = commonCon.nextElementSibling;
578
- if (!this.format.isLine(el)) el = this.format.addLine(commonCon, this.options.get('defaultLine'));
579
- this.selection.setRange(el.firstChild, 0, el.firstChild, 0);
580
- return;
581
- }
582
-
583
- if ((this.format.isBlock(startCon) || dom.check.isWysiwygFrame(startCon)) && (this.component.is(startCon.children[range.startOffset]) || this.component.is(startCon.children[range.startOffset - 1]))) return;
584
- if (dom.query.getParentElement(commonCon, dom.check.isExcludeFormat)) return null;
585
-
586
- if (this.format.isBlock(commonCon) && commonCon.childNodes.length <= 1) {
587
- let br = null;
588
- if (commonCon.childNodes.length === 1 && dom.check.isBreak(commonCon.firstChild)) {
589
- br = commonCon.firstChild;
590
- } else {
591
- br = dom.utils.createTextNode(unicode.zeroWidthSpace);
592
- commonCon.appendChild(br);
593
- }
594
-
595
- this.selection.setRange(br, 1, br, 1);
596
- return;
597
- }
598
-
599
- /* eslint-disable @typescript-eslint/no-unused-vars */
600
- try {
601
- if (commonCon.nodeType === 3) {
602
- format = dom.utils.createElement(formatName || this.options.get('defaultLine'));
603
- commonCon.parentNode.insertBefore(format, commonCon);
604
- format.appendChild(commonCon);
605
- }
606
-
607
- if (dom.check.isBreak(format.nextSibling)) dom.utils.removeItem(format.nextSibling);
608
- if (dom.check.isBreak(format.previousSibling)) dom.utils.removeItem(format.previousSibling);
609
- if (dom.check.isBreak(focusNode)) {
610
- const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
611
- focusNode.parentNode.insertBefore(zeroWidth, focusNode);
612
- focusNode = zeroWidth;
613
- }
614
- } catch (e) {
615
- this.editor.execCommand('formatBlock', false, formatName || this.options.get('defaultLine'));
616
- this.selection.removeRange();
617
- this.selection._init();
618
- this.editor.effectNode = null;
619
- return;
620
- }
621
- /* eslint-disable @typescript-eslint/no-unused-vars */
622
-
623
- if (format) {
624
- if (dom.check.isBreak(format.nextSibling)) dom.utils.removeItem(format.nextSibling);
625
- if (dom.check.isBreak(format.previousSibling)) dom.utils.removeItem(format.previousSibling);
626
- if (dom.check.isBreak(focusNode)) {
627
- const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
628
- focusNode.parentNode.insertBefore(zeroWidth, focusNode);
629
- focusNode = zeroWidth;
630
- }
631
- }
632
-
633
- this.editor.effectNode = null;
634
- if (startCon) {
635
- this.selection.setRange(startCon, 1, startCon, 1);
636
- } else {
637
- this.editor._nativeFocus();
638
- }
639
- },
640
-
641
- /**
642
- * @private
643
- * @this {EventManagerThis}
644
- * @description Handles data transfer actions for paste and drop events.
645
- * - It processes clipboard data, triggers relevant events, and inserts cleaned data into the editor.
646
- * @param {"paste"|"drop"} type The type of event
647
- * @param {Event} e The original event object
648
- * @param {DataTransfer} clipboardData The clipboard data object
649
- * @param {__se__FrameContext} frameContext The frame context
650
- * @returns {Promise<boolean>} Resolves to `false` if processing is complete, otherwise allows default behavior
651
- */
652
- async _dataTransferAction(type, e, clipboardData, frameContext) {
653
- try {
654
- this.ui.showLoading();
655
- await this._setClipboardData(type, e, clipboardData, frameContext);
656
- e.preventDefault();
657
- e.stopPropagation();
658
- return false;
659
- } catch (err) {
660
- console.warn('[SUNEDITOR.paste.error]', err);
661
- } finally {
662
- this.ui.hideLoading();
663
- }
664
- },
665
-
666
- /**
667
- * @private
668
- * @this {EventManagerThis}
669
- * @description Processes clipboard data for paste and drop events, handling text and HTML cleanup.
670
- * - Supports specific handling for content from Microsoft Office applications.
671
- * @param {"paste"|"drop"} type The type of event
672
- * @param {Event} e The original event object
673
- * @param {DataTransfer} clipboardData The clipboard data object
674
- * @param {__se__FrameContext} frameContext The frame context
675
- * @returns {Promise<boolean>} Resolves to `false` if processing is complete, otherwise allows default behavior
676
- */
677
- async _setClipboardData(type, e, clipboardData, frameContext) {
678
- e.preventDefault();
679
- e.stopPropagation();
680
-
681
- let plainText = clipboardData.getData('text/plain');
682
- let cleanData = clipboardData.getData('text/html');
683
- const onlyText = !cleanData;
684
-
685
- // SE copy data
686
- const SEData = this.__secopy === plainText;
687
- // MS word, OneNode, Excel
688
- const MSData = /class=["']*Mso(Normal|List)/i.test(cleanData) || /content=["']*Word.Document/i.test(cleanData) || /content=["']*OneNote.File/i.test(cleanData) || /content=["']*Excel.Sheet/i.test(cleanData);
689
- // from
690
- const from = SEData ? 'SE' : MSData ? 'MS' : '';
691
-
692
- if (onlyText) {
693
- cleanData = converter.htmlToEntity(plainText).replace(/\n/g, '<br>');
694
- } else {
695
- cleanData = cleanData.replace(/^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g, '');
696
- if (MSData) {
697
- cleanData = cleanData.replace(/\n/g, ' ');
698
- plainText = plainText.replace(/\n/g, ' ');
699
- }
700
- }
701
-
702
- if (!SEData) {
703
- const autoLinkify = this.options.get('autoLinkify');
704
- if (autoLinkify) {
705
- const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
706
- dom.query.getListChildNodes(domParser.body, converter.textToAnchor);
707
- cleanData = domParser.body.innerHTML;
708
- }
709
- }
710
-
711
- if (!onlyText) {
712
- cleanData = this.html.clean(cleanData, { forceFormat: false, whitelist: null, blacklist: null });
713
- }
714
-
715
- const maxCharCount = this.char.test(this.editor.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText, false);
716
- // user event - paste
717
- if (type === 'paste') {
718
- const value = await this.triggerEvent('onPaste', { frameContext, event: e, data: cleanData, maxCharCount, from });
719
- if (value === false) {
720
- return false;
721
- } else if (typeof value === 'string') {
722
- if (!value) return false;
723
- cleanData = value;
724
- }
725
- }
726
- // user event - drop
727
- if (type === 'drop') {
728
- const value = await this.triggerEvent('onDrop', { frameContext, event: e, data: cleanData, maxCharCount, from });
729
- if (value === false) {
730
- return false;
731
- } else if (typeof value === 'string') {
732
- if (!value) return false;
733
- cleanData = value;
734
- }
735
- }
736
-
737
- // files
738
- const files = clipboardData.files;
739
- if (files.length > 0 && !MSData) {
740
- for (let i = 0, len = files.length; i < len; i++) {
741
- this._callPluginEvent('onFilePasteAndDrop', { frameContext, event: e, file: files[i] });
742
- }
743
-
744
- return false;
745
- }
746
-
747
- if (!maxCharCount) {
748
- return false;
749
- }
750
-
751
- if (cleanData) {
752
- const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
753
- if (this._callPluginEvent('onPaste', { frameContext, event: e, data: cleanData, doc: domParser }) !== true) {
754
- this.html.insert(cleanData, { selectInserted: false, skipCharCount: true, skipCleaning: true });
755
- }
756
-
757
- // document type
758
- if (frameContext.has('documentType-use-header')) {
759
- frameContext.get('documentType').reHeader();
760
- }
761
- return false;
762
- }
763
- },
764
-
765
- /**
766
- * @private
767
- * @this {EventManagerThis}
768
- * @description Registers common UI events such as toolbar and menu interactions.
769
- * - Adds event listeners for various UI elements, sets up observers, and configures window events.
770
- */
771
- _addCommonEvents() {
772
- const buttonsHandler = ButtonsHandler.bind(this);
773
- const toolbarHandler = OnClick_toolbar.bind(this);
774
-
775
- /** menu event */
776
- this.addEvent(this.context.get('menuTray'), 'mousedown', buttonsHandler, false);
777
- this.addEvent(this.context.get('menuTray'), 'click', OnClick_menuTray.bind(this), true);
778
-
779
- /** toolbar event */
780
- this.addEvent(this.context.get('toolbar.main'), 'mousedown', buttonsHandler, false);
781
- this.addEvent(this.context.get('toolbar.main'), 'click', toolbarHandler, false);
782
- // subToolbar
783
- if (this.options.has('_subMode')) {
784
- this.addEvent(this.context.get('toolbar.sub.main'), 'mousedown', buttonsHandler, false);
785
- this.addEvent(this.context.get('toolbar.sub.main'), 'click', toolbarHandler, false);
786
- }
787
-
788
- /** set response toolbar */
789
- this.toolbar._setResponsive();
790
-
791
- /** observer */
792
- if (env.isResizeObserverSupported) {
793
- this._toolbarObserver = new ResizeObserver(() => {
794
- _w.setTimeout(() => {
795
- this.toolbar.resetResponsiveToolbar();
796
- }, 0);
797
- });
798
- this._wwFrameObserver = new ResizeObserver((entries) => {
799
- _w.setTimeout(() => {
800
- entries.forEach((e) => {
801
- this.editor.__callResizeFunction(this.frameRoots.get(e.target.getAttribute('data-root-key')), -1, e);
802
- });
803
- }, 0);
804
- });
805
- }
806
-
807
- /** modal outside click */
808
- if (this.options.get('closeModalOutsideClick')) {
809
- this.addEvent(
810
- this.carrierWrapper.querySelector('.se-modal .se-modal-inner'),
811
- 'click',
812
- (e) => {
813
- if (e.target === this.carrierWrapper.querySelector('.se-modal .se-modal-inner')) {
814
- this.ui._offCurrentModal();
815
- }
816
- },
817
- false
818
- );
819
- }
820
-
821
- /** window event */
822
- this.addEvent(_w, 'resize', OnResize_window.bind(this), false);
823
- this.addEvent(_w, 'scroll', OnScroll_window.bind(this), false);
824
- if (env.isMobile) {
825
- this.addEvent(_w.visualViewport, 'resize', OnChange_viewport.bind(this), false);
826
- this.addEvent(_w.visualViewport, 'scroll', OnChange_viewport.bind(this), false);
827
- }
828
- },
829
-
830
- /**
831
- * @private
832
- * @this {EventManagerThis}
833
- * @description Registers event listeners for the editor's frame, including text input, selection, and UI interactions.
834
- * - Handles events inside an iframe or within the standard wysiwyg editor.
835
- * @param {__se__FrameContext} fc The frame context object
836
- */
837
- _addFrameEvents(fc) {
838
- const isIframe = fc.get('options').get('iframe');
839
- const eventWysiwyg = isIframe ? fc.get('_ww') : fc.get('wysiwyg');
840
- fc.set('eventWysiwyg', eventWysiwyg);
841
- const codeArea = fc.get('code');
842
- const dragCursor = this.editor.carrierWrapper.querySelector('.se-drag-cursor');
843
-
844
- /** editor area */
845
- const wwMouseMove = OnMouseMove_wysiwyg.bind(this, fc);
846
- this.addEvent(eventWysiwyg, 'mousemove', wwMouseMove, false);
847
- this.addEvent(eventWysiwyg, 'mouseleave', OnMouseLeave_wysiwyg.bind(this, fc), false);
848
- this.addEvent(eventWysiwyg, 'mousedown', OnMouseDown_wysiwyg.bind(this, fc), false);
849
- this.addEvent(eventWysiwyg, 'mouseup', OnMouseUp_wysiwyg.bind(this, fc), false);
850
- this.addEvent(eventWysiwyg, 'click', OnClick_wysiwyg.bind(this, fc), false);
851
- this.addEvent(eventWysiwyg, 'input', OnInput_wysiwyg.bind(this, fc), false);
852
- this.addEvent(eventWysiwyg, 'keydown', OnKeyDown_wysiwyg.bind(this, fc), false);
853
- this.addEvent(eventWysiwyg, 'keyup', OnKeyUp_wysiwyg.bind(this, fc), false);
854
- this.addEvent(eventWysiwyg, 'paste', OnPaste_wysiwyg.bind(this, fc), false);
855
- this.addEvent(eventWysiwyg, 'copy', OnCopy_wysiwyg.bind(this, fc), false);
856
- this.addEvent(eventWysiwyg, 'cut', OnCut_wysiwyg.bind(this, fc), false);
857
- this.addEvent(
858
- eventWysiwyg,
859
- 'dragover',
860
- OnDragOver_wysiwyg.bind(this, fc, dragCursor, isIframe ? this.editor.frameContext.get('topArea') : null, !this.options.get('toolbar_container') && !this.editor.isBalloon && !this.editor.isInline),
861
- false
862
- );
863
- this.addEvent(eventWysiwyg, 'dragend', OnDragEnd_wysiwyg.bind(this, dragCursor), false);
864
- this.addEvent(eventWysiwyg, 'drop', OnDrop_wysiwyg.bind(this, fc, dragCursor), false);
865
- this.addEvent(eventWysiwyg, 'scroll', OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), { passive: true, capture: false });
866
- this.addEvent(eventWysiwyg, 'focus', OnFocus_wysiwyg.bind(this, fc), false);
867
- this.addEvent(eventWysiwyg, 'blur', OnBlur_wysiwyg.bind(this, fc), false);
868
- this.addEvent(codeArea, 'mousedown', OnFocus_code.bind(this, fc), false);
869
-
870
- /** drag handle */
871
- const dragHandle = fc.get('wrapper').querySelector('.se-drag-handle');
872
- this.addEvent(
873
- dragHandle,
874
- 'wheel',
875
- (event) => {
876
- event.preventDefault();
877
- this.component.deselect();
878
- },
879
- false
880
- );
881
-
882
- /** line breaker */
883
- const lineBreakEventName = isMobile ? 'touchstart' : 'mousedown';
884
- this.addEvent(
885
- [fc.get('lineBreaker_t'), fc.get('lineBreaker_b')],
886
- lineBreakEventName,
887
- (e) => {
888
- e.preventDefault();
889
- },
890
- false
891
- );
892
- this.addEvent(fc.get('lineBreaker_t'), lineBreakEventName, DisplayLineBreak.bind(this, 't'), false);
893
- this.addEvent(fc.get('lineBreaker_b'), lineBreakEventName, DisplayLineBreak.bind(this, 'b'), false);
894
-
895
- /** Events are registered mobile. */
896
- if (isMobile) {
897
- this.addEvent(eventWysiwyg, 'touchstart', wwMouseMove, {
898
- passive: true,
899
- capture: false
900
- });
901
- }
902
-
903
- /** code view area auto line */
904
- if (!this.options.get('hasCodeMirror')) {
905
- const codeNumbers = fc.get('codeNumbers');
906
- const cvAuthHeight = this.viewer._codeViewAutoHeight.bind(this.viewer, fc.get('code'), codeNumbers, this.editor.frameOptions.get('height') === 'auto');
907
-
908
- this.addEvent(codeArea, 'keydown', cvAuthHeight, false);
909
- this.addEvent(codeArea, 'keyup', cvAuthHeight, false);
910
- this.addEvent(codeArea, 'paste', cvAuthHeight, false);
911
-
912
- /** code view numbers */
913
- if (codeNumbers) this.addEvent(codeArea, 'scroll', this.viewer._scrollLineNumbers.bind(codeArea, codeNumbers), false);
914
- }
915
-
916
- if (fc.has('statusbar')) this.__addStatusbarEvent(fc, fc.get('options'));
917
-
918
- const OnScrollAbs = OnScroll_Abs.bind(this);
919
- let scrollParent = fc.get('originElement');
920
- while ((scrollParent = dom.query.getScrollParent(scrollParent.parentElement))) {
921
- this.__scrollparents.push(scrollParent);
922
- this.addEvent(scrollParent, 'scroll', OnScrollAbs, false);
923
- }
924
-
925
- /** focus temp (mobile) */
926
- this.addEvent(this.__focusTemp, 'focus', (e) => e.preventDefault(), false);
927
-
928
- /** document event */
929
- if (this.__eventDoc !== fc.get('_wd')) {
930
- this.__eventDoc = fc.get('_wd');
931
- this.addEvent(this.__eventDoc, 'selectionchange', OnSelectionchange_document.bind(this, this.__eventDoc), false);
932
- }
933
- },
934
-
935
- /**
936
- * @private
937
- * @this {EventManagerThis}
938
- * @description Adds event listeners for resizing the status bar if resizing is enabled.
939
- * - If resizing is not enabled, applies a non-resizable class.
940
- * @param {__se__FrameContext} fc The frame context object
941
- * @param {__se__FrameOptions} fo The frame options object
942
- */
943
- __addStatusbarEvent(fc, fo) {
944
- if (/\d+/.test(fo.get('height')) && fo.get('statusbar_resizeEnable')) {
945
- fo.set('__statusbarEvent', this.addEvent(fc.get('statusbar'), 'mousedown', OnMouseDown_statusbar.bind(this), false));
946
- } else {
947
- dom.utils.addClass(fc.get('statusbar'), 'se-resizing-none');
948
- }
949
- },
950
-
951
- /**
952
- * @private
953
- * @this {EventManagerThis}
954
- * @description Removes all registered event listeners from the editor.
955
- * - Disconnects observers and clears stored event references.
956
- */
957
- _removeAllEvents() {
958
- for (let i = 0, len = this._events.length, e; i < len; i++) {
959
- e = this._events[i];
960
- e.target.removeEventListener(e.type, e.listener, e.useCapture);
961
- }
962
-
963
- this._events = [];
964
-
965
- if (this._wwFrameObserver) {
966
- this._wwFrameObserver.disconnect();
967
- this._wwFrameObserver = null;
968
- }
969
-
970
- if (this._toolbarObserver) {
971
- this._toolbarObserver.disconnect();
972
- this._toolbarObserver = null;
973
- }
974
- },
975
-
976
- /**
977
- * @private
978
- * @this {EventManagerThis}
979
- * @description Adjusts the position of the editor's toolbar, controllers, and other floating elements based on scroll position.
980
- * - Ensures UI elements maintain their intended relative positions when scrolling.
981
- * @param {Element} eventWysiwyg The wysiwyg event object containing scroll data
982
- */
983
- _moveContainer(eventWysiwyg) {
984
- const y = eventWysiwyg.scrollTop || 0;
985
- const x = eventWysiwyg.scrollLeft || 0;
986
-
987
- if (this.editor.isBalloon) {
988
- this.context.get('toolbar.main').style.top = this.toolbar._balloonOffset.top - y + 'px';
989
- this.context.get('toolbar.main').style.left = this.toolbar._balloonOffset.left - x + 'px';
990
- } else if (this.editor.isSubBalloon) {
991
- this.context.get('toolbar.sub.main').style.top = this.subToolbar._balloonOffset.top - y + 'px';
992
- this.context.get('toolbar.sub.main').style.left = this.subToolbar._balloonOffset.left - x + 'px';
993
- }
994
-
995
- if (this.editor._controllerTargetContext !== this.editor.frameContext.get('topArea')) {
996
- this.ui._offCurrentController();
997
- }
998
-
999
- if (this.editor._lineBreaker_t) {
1000
- const t_style = this.editor._lineBreaker_t.style;
1001
- if (t_style.display !== 'none') {
1002
- const t_offset = (this.editor._lineBreaker_t.getAttribute('data-offset') || ',').split(',');
1003
- t_style.top = numbers.get(t_style.top, 0) - (y - numbers.get(t_offset[0], 0)) + 'px';
1004
- t_style.left = numbers.get(t_style.left, 0) - (x - numbers.get(t_offset[1], 0)) + 'px';
1005
- this.editor._lineBreaker_t.setAttribute('data-offset', y + ',' + x);
1006
- }
1007
- }
1008
-
1009
- if (this.editor._lineBreaker_b) {
1010
- const b_style = this.editor._lineBreaker_b.style;
1011
- if (b_style.display !== 'none') {
1012
- const b_offset = (this.editor._lineBreaker_b.getAttribute('data-offset') || ',').split(',');
1013
- b_style.top = numbers.get(b_style.top, 0) - (y - numbers.get(b_offset[0], 0)) + 'px';
1014
- b_style[b_offset[1]] = numbers.get(b_style[b_offset[1]], 0) - (x - numbers.get(b_offset[2], 0)) + 'px';
1015
- this.editor._lineBreaker_b.setAttribute('data-offset', y + ',' + b_offset[1] + ',' + x);
1016
- }
1017
- }
1018
-
1019
- const openCont = this.editor.opendControllers;
1020
- for (let i = 0; i < openCont.length; i++) {
1021
- if (!openCont[i].notInCarrier) continue;
1022
- openCont[i].form.style.top = openCont[i].inst.__offset.top - y + 'px';
1023
- openCont[i].form.style.left = openCont[i].inst.__offset.left - x + 'px';
1024
- }
1025
- },
1026
-
1027
- /**
1028
- * @private
1029
- * @this {EventManagerThis}
1030
- * @description Handles the scrolling of the editor container.
1031
- * - Repositions open controllers if necessary.
1032
- */
1033
- _scrollContainer() {
1034
- const openCont = this.editor.opendControllers;
1035
- if (!openCont.length) return;
1036
-
1037
- this.__rePositionController(openCont);
1038
- },
1039
-
1040
- /**
1041
- * @private
1042
- * @this {EventManagerThis}
1043
- * @description Repositions the currently open controllers within the editor.
1044
- * - Ensures elements are displayed in their correct positions after scrolling.
1045
- * @param {Array<object>} cont List of controllers to reposition
1046
- */
1047
- __rePositionController(cont) {
1048
- if (_DragHandle.get('__dragMove')) _DragHandle.get('__dragMove')();
1049
- for (let i = 0; i < cont.length; i++) {
1050
- if (cont[i].notInCarrier) continue;
1051
- cont[i].inst?.show();
1052
- }
1053
- },
1054
-
1055
- /**
1056
- * @private
1057
- * @this {EventManagerThis}
1058
- * @description Resets the frame status, adjusting toolbar and UI elements based on the current state.
1059
- * - Handles inline editor adjustments, fullscreen mode, and responsive toolbar updates.
1060
- */
1061
- _resetFrameStatus() {
1062
- if (!env.isResizeObserverSupported) {
1063
- this.toolbar.resetResponsiveToolbar();
1064
- if (this.options.get('_subMode')) this.subToolbar.resetResponsiveToolbar();
1065
- }
1066
-
1067
- const toolbar = this.context.get('toolbar.main');
1068
- const isToolbarHidden = toolbar.style.display === 'none' || (this.editor.isInline && !this.toolbar._inlineToolbarAttr.isShow);
1069
- if (toolbar.offsetWidth === 0 && !isToolbarHidden) return;
1070
-
1071
- const opendBrowser = this.editor.opendBrowser;
1072
- if (opendBrowser && opendBrowser.area.style.display === 'block') {
1073
- opendBrowser.body.style.maxHeight = dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
1074
- }
1075
-
1076
- if (this.menu.currentDropdownActiveButton && this.menu.currentDropdown) {
1077
- this.menu._setMenuPosition(this.menu.currentDropdownActiveButton, this.menu.currentDropdown);
1078
- }
1079
-
1080
- if (this.viewer._resetFullScreenHeight()) return;
1081
-
1082
- const fc = this.editor.frameContext;
1083
- if (fc.get('isCodeView') && this.editor.isInline) {
1084
- this.toolbar._showInline();
1085
- return;
1086
- }
1087
-
1088
- this.editor._iframeAutoHeight(fc);
1089
-
1090
- if (this.toolbar._sticky) {
1091
- this.context.get('toolbar.main').style.width = fc.get('topArea').offsetWidth - 2 + 'px';
1092
- this.toolbar._resetSticky();
1093
- }
1094
- },
1095
-
1096
- /**
1097
- * @private
1098
- * @this {EventManagerThis}
1099
- * @description Synchronizes the selection state by resetting it on mouseup.
1100
- * - Ensures selection updates correctly across different interactions.
1101
- */
1102
- _setSelectionSync() {
1103
- this.removeGlobalEvent(this.__selectionSyncEvent);
1104
- this.__selectionSyncEvent = this.addGlobalEvent('mouseup', () => {
1105
- this.selection._init();
1106
- this.removeGlobalEvent(this.__selectionSyncEvent);
1107
- });
1108
- },
1109
-
1110
- /**
1111
- * @private
1112
- * @this {EventManagerThis}
1113
- * @description Retains the style nodes for formatting consistency when applying styles.
1114
- * - Preserves nested styling by cloning and restructuring the style nodes.
1115
- * @param {HTMLElement} formatEl The format element where styles should be retained
1116
- * @param {Array<Node>} _styleNodes The list of style nodes to retain
1117
- */
1118
- _retainStyleNodes(formatEl, _styleNodes) {
1119
- const el = _styleNodes[0].cloneNode(false);
1120
- let n = el;
1121
- for (let i = 1, len = _styleNodes.length, t; i < len; i++) {
1122
- t = _styleNodes[i].cloneNode(false);
1123
- n.appendChild(t);
1124
- n = t;
1125
- }
1126
-
1127
- const { parent, inner } = this.nodeTransform.createNestedNode(_styleNodes, null);
1128
- const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
1129
- inner.appendChild(zeroWidth);
1130
-
1131
- formatEl.innerHTML = '';
1132
- formatEl.appendChild(parent);
1133
-
1134
- this.selection.setRange(zeroWidth, 1, zeroWidth, 1);
1135
- },
1136
-
1137
- /**
1138
- * @private
1139
- * @this {EventManagerThis}
1140
- * @description Clears retained style nodes by replacing content with a single line break.
1141
- * - Resets the selection to the start of the cleared element.
1142
- * @param {HTMLElement} formatEl The format element where styles should be cleared
1143
- */
1144
- _clearRetainStyleNodes(formatEl) {
1145
- formatEl.innerHTML = '<br>';
1146
- this.selection.setRange(formatEl, 0, formatEl, 0);
1147
- },
1148
-
1149
- /**
1150
- * @private
1151
- * @this {EventManagerThis}
1152
- * @description Calls a registered plugin event and executes associated handlers.
1153
- * - If any handler returns `false`, the event propagation stops.
1154
- * @param {string} name The name of the plugin event
1155
- * @param {{ frameContext: __se__FrameContext, event: Event, data?: string, line?: Node, range?: Range, file?: File, doc?: Document }} e The event object passed to the plugin event handler
1156
- * @returns {boolean|undefined} Returns `false` if any handler stops the event, otherwise `undefined`
1157
- */
1158
- _callPluginEvent(name, e) {
1159
- const eventPlugins = this.editor._onPluginEvents.get(name);
1160
- for (let i = 0, r; i < eventPlugins.length; i++) {
1161
- r = eventPlugins[i](e);
1162
- if (typeof r === 'boolean') return r;
1163
- }
1164
- },
1165
-
1166
- /**
1167
- * @private
1168
- * @this {EventManagerThis}
1169
- * @description Handles the selection of a component when hovering over it.
1170
- * - If the target is a component, it ensures that the component is selected properly.
1171
- * @param {Element} target The element being hovered over
1172
- */
1173
- _overComponentSelect(target) {
1174
- const figure = dom.query.getParentElement(target, dom.check.isFigure);
1175
- let info = this.component.get(target);
1176
- if (info || figure) {
1177
- if (!info) info = this.component.get(figure);
1178
- if (info && !dom.utils.hasClass(info.container, 'se-component-selected')) {
1179
- this.ui._offCurrentController();
1180
- _DragHandle.set('__overInfo', ON_OVER_COMPONENT);
1181
- this.component.select(info.target, info.pluginName);
1182
- }
1183
- } else if (_DragHandle.get('__overInfo') !== null && !dom.utils.hasClass(target, 'se-drag-handle')) {
1184
- this.component.__deselect();
1185
- _DragHandle.set('__overInfo', null);
1186
- }
1187
- },
1188
-
1189
- /**
1190
- * @private
1191
- * @this {EventManagerThis}
1192
- * @description Removes input event listeners and resets input-related properties.
1193
- */
1194
- __removeInput() {
1195
- this._inputFocus = this.editor._preventBlur = false;
1196
- this.__inputBlurEvent = this.removeEvent(this.__inputBlurEvent);
1197
- this.__inputKeyEvent = this.removeEvent(this.__inputKeyEvent);
1198
- this.__inputPlugin = null;
1199
- },
1200
-
1201
- /**
1202
- * @private
1203
- * @this {EventManagerThis}
1204
- * @description Prevents the default behavior of the Enter key and refocuses the editor.
1205
- * @param {Event} e The keyboard event
1206
- */
1207
- __enterPrevent(e) {
1208
- e.preventDefault();
1209
- if (!isMobile) return;
1210
-
1211
- this.__focusTemp.focus();
1212
- this.editor.frameContext.get('wysiwyg').focus();
1213
- },
1214
-
1215
- /**
1216
- * @private
1217
- * @description Scrolls the editor view to the caret position after pressing Enter. (Ignored on mobile devices)
1218
- * @this {EventManagerThis}
1219
- * @param {*} range Range object
1220
- */
1221
- __enterScrollTo(range) {
1222
- if (!env.isMobile) this.editor.selection.scrollTo(range);
1223
- },
1224
-
1225
- constructor: EventManager
1226
- };
1227
-
1228
- /**
1229
- * @this {EventManagerThis}
1230
- * @param {__se__FrameContext} frameContext - frame context object
1231
- * @param {Element} eventWysiwyg - wysiwyg event object
1232
- * @param {Event} e - Event object
1233
- */
1234
- function OnScroll_wysiwyg(frameContext, eventWysiwyg, e) {
1235
- this._moveContainer(eventWysiwyg);
1236
- this._scrollContainer();
1237
-
1238
- // plugin event
1239
- this._callPluginEvent('onScroll', { frameContext, event: e });
1240
-
1241
- // document type page
1242
- if (frameContext.has('documentType-use-page')) {
1243
- frameContext.get('documentType').scrollPage();
1244
- }
1245
-
1246
- // user event
1247
- this.triggerEvent('onScroll', { frameContext, event: e });
1248
- }
1249
-
1250
- /**
1251
- * @this {EventManagerThis}
1252
- * @param {__se__FrameContext} frameContext - frame context object
1253
- * @param {Event} e - Event object
1254
- */
1255
- function OnFocus_wysiwyg(frameContext, e) {
1256
- if (this.selection.__iframeFocus || frameContext.get('isReadOnly') || frameContext.get('isDisabled')) {
1257
- e.preventDefault();
1258
- return false;
1259
- }
1260
-
1261
- const rootKey = frameContext.get('key');
1262
-
1263
- if (this._inputFocus) {
1264
- if (this.editor.isInline) {
1265
- this._w.setTimeout(() => {
1266
- this.toolbar._showInline();
1267
- }, 0);
1268
- }
1269
- return;
1270
- }
1271
-
1272
- if (this.status.rootKey === rootKey && this.editor._preventBlur) return;
1273
-
1274
- const onSelected = this.editor.status.onSelected || this.editor.opendModal;
1275
- this.ui._offCurrentController();
1276
- this.status.hasFocus = true;
1277
-
1278
- dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
1279
- dom.utils.setDisabled(this.editor._codeViewDisabledButtons, false);
1280
-
1281
- this.editor.changeFrameContext(rootKey);
1282
- this.history.resetButtons(rootKey, null);
1283
-
1284
- if (!onSelected) {
1285
- this.applyTagEffect();
1286
- }
1287
-
1288
- this._w.setTimeout(() => {
1289
- if (this.editor.isInline) this.toolbar._showInline();
1290
-
1291
- // user event
1292
- this.triggerEvent('onFocus', { frameContext, event: e });
1293
- // plugin event
1294
- this._callPluginEvent('onFocus', { frameContext, event: e });
1295
- }, 0);
1296
- }
1297
-
1298
- /**
1299
- * @this {EventManagerThis}
1300
- * @param {__se__FrameContext} frameContext - frame context object
1301
- * @param {Event} e - Event object
1302
- */
1303
- function OnBlur_wysiwyg(frameContext, e) {
1304
- if (this._inputFocus || this.editor._preventBlur || frameContext.get('isCodeView') || frameContext.get('isReadOnly') || frameContext.get('isDisabled')) return;
1305
-
1306
- this.status.hasFocus = false;
1307
- this.editor.effectNode = null;
1308
- if (this.editor.isInline || this.editor.isBalloon) this._hideToolbar();
1309
- if (this.editor.isSubBalloon) this._hideToolbar_sub();
1310
-
1311
- this._setKeyEffect([]);
1312
-
1313
- this.status.currentNodes = [];
1314
- this.status.currentNodesMap = [];
1315
-
1316
- this.editor.applyFrameRoots((root) => {
1317
- if (root.get('navigation')) root.get('navigation').textContent = '';
1318
- });
1319
-
1320
- this.history.check(frameContext.get('key'), this.status._range);
1321
-
1322
- // user event
1323
- this.triggerEvent('onBlur', { frameContext, event: e });
1324
- // plugin event
1325
- this._callPluginEvent('onBlur', { frameContext, event: e });
1326
- }
1327
-
1328
- /**
1329
- * @this {EventManagerThis}
1330
- * @param {MouseEvent} e - Event object
1331
- */
1332
- function OnMouseDown_statusbar(e) {
1333
- e.stopPropagation();
1334
- this._resizeClientY = e.clientY;
1335
- this.ui.enableBackWrapper('ns-resize');
1336
- this.__resize_editor = this.addGlobalEvent('mousemove', __resizeEditor.bind(this));
1337
- this.__close_move = this.addGlobalEvent('mouseup', __closeMove.bind(this));
1338
- }
1339
-
1340
- /**
1341
- * @this {EventManagerThis}
1342
- * @param {MouseEvent} e - Event object
1343
- */
1344
- function __resizeEditor(e) {
1345
- const fc = this.editor.frameContext;
1346
- const resizeInterval = fc.get('wrapper').offsetHeight + (e.clientY - this._resizeClientY);
1347
- const h = resizeInterval < fc.get('_minHeight') ? fc.get('_minHeight') : resizeInterval;
1348
- fc.get('wysiwygFrame').style.height = fc.get('code').style.height = h + 'px';
1349
- this._resizeClientY = e.clientY;
1350
- if (!env.isResizeObserverSupported) this.editor.__callResizeFunction(fc, h, null);
1351
- }
1352
-
1353
- /**
1354
- * @this {EventManagerThis}
1355
- */
1356
- function __closeMove() {
1357
- this.ui.disableBackWrapper();
1358
- if (this.__resize_editor) this.__resize_editor = this.removeGlobalEvent(this.__resize_editor);
1359
- if (this.__close_move) this.__close_move = this.removeGlobalEvent(this.__close_move);
1360
- }
1361
-
1362
- /**
1363
- * @this {EventManagerThis}
1364
- * @param {"t"|"b"} dir - Direction
1365
- * @param {Event} e - Event object
1366
- */
1367
- function DisplayLineBreak(dir, e) {
1368
- e.preventDefault();
1369
-
1370
- const component = this._lineBreakComp;
1371
- if (!component) return;
1372
-
1373
- const isList = dom.check.isListCell(component.parentElement);
1374
- const format = dom.utils.createElement(isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.options.get('defaultLine'));
1375
- if (!isList) format.innerHTML = '<br>';
1376
-
1377
- if (this.editor.frameOptions.get('charCounter_type') === 'byte-html' && !this.char.check(format.outerHTML)) return;
1378
-
1379
- component.parentNode.insertBefore(format, dir === 't' ? component : component.nextSibling);
1380
- this.component.deselect();
1381
-
1382
- try {
1383
- this.editor._preventBlur = true;
1384
- const focusEl = isList ? format : format.firstChild;
1385
- this.selection.setRange(focusEl, 1, focusEl, 1);
1386
- this.history.push(false);
1387
- } finally {
1388
- this.editor._preventBlur = false;
1389
- }
1390
- }
1391
-
1392
- /**
1393
- * @this {EventManagerThis}
1394
- */
1395
- function OnResize_window() {
1396
- if (isMobile) {
1397
- this._scrollContainer();
1398
- } else {
1399
- this.ui._offCurrentController();
1400
- }
1401
-
1402
- if (this.editor.isBalloon) this.toolbar.hide();
1403
- else if (this.editor.isSubBalloon) this.subToolbar.hide();
1404
-
1405
- this._resetFrameStatus();
1406
- }
1407
-
1408
- /**
1409
- * @this {EventManagerThis}
1410
- */
1411
- function OnScroll_window() {
1412
- if (this.options.get('toolbar_sticky') > -1) {
1413
- this.toolbar._resetSticky();
1414
- }
1415
-
1416
- if (this.editor.isBalloon && this.context.get('toolbar.main').style.display === 'block') {
1417
- this.toolbar._setBalloonOffset(this.toolbar._balloonOffset.position === 'top');
1418
- } else if (this.editor.isSubBalloon && this.context.get('toolbar.sub.main').style.display === 'block') {
1419
- this.subToolbar._setBalloonOffset(this.subToolbar._balloonOffset.position === 'top');
1420
- }
1421
-
1422
- this._scrollContainer();
1423
-
1424
- // document type page
1425
- if (this.editor.frameContext.has('documentType-use-page')) {
1426
- this.editor.frameContext.get('documentType').scrollWindow();
1427
- }
1428
- }
1429
-
1430
- /**
1431
- * @this {EventManagerThis}
1432
- */
1433
- function OnChange_viewport() {
1434
- if (this.options.get('toolbar_sticky') > -1) {
1435
- this.toolbar._resetSticky();
1436
- this.editor.menu._restoreMenuPosition();
1437
- }
1438
- }
1439
-
1440
- /**
1441
- * @this {EventManagerThis}
1442
- * @param {Document} _wd - Wysiwyg document
1443
- */
1444
- function OnSelectionchange_document(_wd) {
1445
- if (this.editor._preventSelection) return;
1446
-
1447
- const selection = _wd.getSelection();
1448
- let anchorNode = selection.anchorNode;
1449
-
1450
- this.editor.applyFrameRoots((root) => {
1451
- if (anchorNode && root.get('wysiwyg').contains(anchorNode)) {
1452
- if (root.get('isReadOnly') || root.get('isDisabled')) return;
1453
-
1454
- anchorNode = null;
1455
- this.selection._init();
1456
- this.applyTagEffect();
1457
-
1458
- // document type
1459
- if (root.has('documentType-use-header')) {
1460
- const el = dom.query.getParentElement(this.selection.selectionNode, this.format.isLine.bind(this.format));
1461
- root.get('documentType').on(el);
1462
- }
1463
- }
1464
- });
1465
- }
1466
-
1467
- /**
1468
- * @this {EventManagerThis}
1469
- */
1470
- function OnScroll_Abs() {
1471
- this._scrollContainer();
1472
- }
1473
-
1474
- /**
1475
- * @this {EventManagerThis}
1476
- * @param {__se__FrameContext} frameContext - frame context object
1477
- */
1478
- function OnFocus_code(frameContext) {
1479
- this.editor.changeFrameContext(frameContext.get('key'));
1480
- dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
1481
- dom.utils.setDisabled(this.editor._codeViewDisabledButtons, true);
1482
- }
1483
-
1484
- export default EventManager;
1
+ /**
2
+ * @fileoverview eventManager class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { dom, unicode, numbers, env, converter } from '../../helper';
7
+ import { _DragHandle } from '../../modules';
8
+
9
+ // event handlers
10
+ import { ButtonsHandler, OnClick_menuTray, OnClick_toolbar } from './eventHandlers/handler_toolbar';
11
+ import { OnMouseDown_wysiwyg, OnMouseUp_wysiwyg, OnClick_wysiwyg, OnMouseMove_wysiwyg, OnMouseLeave_wysiwyg } from './eventHandlers/handler_ww_mouse';
12
+ import { OnInput_wysiwyg, OnKeyDown_wysiwyg, OnKeyUp_wysiwyg } from './eventHandlers/handler_ww_key_input';
13
+ import { OnPaste_wysiwyg, OnCopy_wysiwyg, OnCut_wysiwyg } from './eventHandlers/handler_ww_clipboard';
14
+ import { OnDragOver_wysiwyg, OnDragEnd_wysiwyg, OnDrop_wysiwyg } from './eventHandlers/handler_ww_dragDrop';
15
+
16
+ const { _w, _d, ON_OVER_COMPONENT, isMobile, isTouchDevice } = env;
17
+
18
+ /**
19
+ * @typedef {Omit<EventManager & Partial<__se__EditorInjector>, 'eventManager'>} EventManagerThis
20
+ */
21
+
22
+ /**
23
+ * @constructor
24
+ * @this {EventManagerThis}
25
+ * @description Event manager, editor's all event management class
26
+ * @param {__se__EditorCore} editor - The root editor instance
27
+ * @property {__se__EditorCore} editor - The root editor instance
28
+ */
29
+ function EventManager(editor) {
30
+ CoreInjector.call(this, editor);
31
+
32
+ /**
33
+ * @description Old browsers: When there is no 'e.isComposing' in the keyup event
34
+ * @type {boolean}
35
+ */
36
+ this.isComposing = false;
37
+
38
+ /**
39
+ * @description An array of parent containers that can be scrolled (in descending order)
40
+ * @type {Array<Element>}
41
+ */
42
+ this.scrollparents = [];
43
+
44
+ /** @type {Array<*>} */
45
+ this._events = [];
46
+ /** @type {RegExp} */
47
+ this._onButtonsCheck = new RegExp(`^(${Object.keys(editor.options.get('_defaultStyleTagMap')).join('|')})$`, 'i');
48
+ /** @type {boolean} */
49
+ this._onShortcutKey = false;
50
+ /** @type {number} */
51
+ this._balloonDelay = null;
52
+ /** @type {ResizeObserver} */
53
+ this._wwFrameObserver = null;
54
+ /** @type {ResizeObserver} */
55
+ this._toolbarObserver = null;
56
+ /** @type {Element|null} */
57
+ this._lineBreakComp = null;
58
+ /** @type {Object<string, *>|null} */
59
+ this._formatAttrsTemp = null;
60
+ /** @type {number} */
61
+ this._resizeClientY = 0;
62
+ /** @type {__se__GlobalEventInfo|null} */
63
+ this.__resize_editor = null;
64
+ /** @type {__se__GlobalEventInfo|null} */
65
+ this.__close_move = null;
66
+ /** @type {__se__GlobalEventInfo|null} */
67
+ this.__geckoActiveEvent = null;
68
+ /** @type {Array<Node>} */
69
+ this.__cacheStyleNodes = [];
70
+ /** @type {__se__GlobalEventInfo|null} */
71
+ this.__selectionSyncEvent = null;
72
+
73
+ // input plugins
74
+ /** @type {boolean} */
75
+ this._inputFocus = false;
76
+ /** @type {Object<string, *>|null} */
77
+ this.__inputPlugin = null;
78
+ /** @type {?__se__EventInfo=} */
79
+ this.__inputBlurEvent = null;
80
+ /** @type {?__se__EventInfo=} */
81
+ this.__inputKeyEvent = null;
82
+
83
+ // viewport
84
+ /** @type {HTMLInputElement} */
85
+ this.__focusTemp = this.carrierWrapper.querySelector('.__se__focus__temp__');
86
+ /** @type {number|void} */
87
+ this.__retainTimer = null;
88
+ /** @type {Element} */
89
+ this.__eventDoc = null;
90
+ /** @type {string} */
91
+ this.__secopy = null;
92
+ }
93
+
94
+ EventManager.prototype = {
95
+ /**
96
+ * @this {EventManagerThis}
97
+ * @description Register for an event.
98
+ * - Only events registered with this method are unregistered or re-registered when methods such as 'setOptions', 'destroy' are called.
99
+ * @param {*} target Target element
100
+ * @param {string} type Event type
101
+ * @param {(...args: *) => *} listener Event handler
102
+ * @param {boolean|AddEventListenerOptions=} useCapture Event useCapture option
103
+ * @return {__se__EventInfo|null} Registered event information
104
+ */
105
+ addEvent(target, type, listener, useCapture) {
106
+ if (!target) return null;
107
+ if (!numbers.is(target.length) || target.nodeName || (!Array.isArray(target) && target.length < 1)) target = [target];
108
+ if (target.length === 0) return null;
109
+
110
+ const len = target.length;
111
+ for (let i = 0; i < len; i++) {
112
+ target[i].addEventListener(type, listener, useCapture);
113
+ this._events.push({
114
+ target: target[i],
115
+ type,
116
+ listener,
117
+ useCapture
118
+ });
119
+ }
120
+
121
+ return {
122
+ target: len > 1 ? target : target[0],
123
+ type,
124
+ listener,
125
+ useCapture
126
+ };
127
+ },
128
+
129
+ /**
130
+ * @this {EventManagerThis}
131
+ * @description Remove event
132
+ * @param {__se__EventInfo} params event info = this.addEvent()
133
+ * @returns {undefined|null} Success: null, Not found: undefined
134
+ */
135
+ removeEvent(params) {
136
+ if (!params) return;
137
+
138
+ let target = params.target;
139
+ const type = params.type;
140
+ const listener = params.listener;
141
+ const useCapture = params.useCapture;
142
+
143
+ if (!target) return;
144
+ if (!numbers.is(target.length) || target.nodeName || (!Array.isArray(target) && target.length < 1)) target = /** @type {Array<Element>} */ ([target]);
145
+ if (target.length === 0) return;
146
+
147
+ for (let i = 0, len = target.length; i < len; i++) {
148
+ target[i].removeEventListener(type, listener, useCapture);
149
+ }
150
+
151
+ return null;
152
+ },
153
+
154
+ /**
155
+ * @this {EventManagerThis}
156
+ * @description Add an event to document.
157
+ * - When created as an Iframe, the same event is added to the document in the Iframe.
158
+ * @param {string} type Event type
159
+ * @param {(...args: *) => *} listener Event listener
160
+ * @param {boolean|AddEventListenerOptions=} useCapture Use event capture
161
+ * @return {__se__GlobalEventInfo} Registered event information
162
+ */
163
+ addGlobalEvent(type, listener, useCapture) {
164
+ if (this.editor.frameOptions.get('iframe')) {
165
+ this.editor.frameContext.get('_ww').addEventListener(type, listener, useCapture);
166
+ }
167
+ this._w.addEventListener(type, listener, useCapture);
168
+ return {
169
+ type,
170
+ listener,
171
+ useCapture
172
+ };
173
+ },
174
+
175
+ /**
176
+ * @this {EventManagerThis}
177
+ * @description Remove events from document.
178
+ * - When created as an Iframe, the event of the document inside the Iframe is also removed.
179
+ * @param {string|__se__GlobalEventInfo} type Event type or (Event info = this.addGlobalEvent())
180
+ * @param {(...args: *) => *=} listener Event listener
181
+ * @param {boolean|AddEventListenerOptions=} useCapture Use event capture
182
+ * @returns {undefined|null} Success: null, Not found: undefined
183
+ */
184
+ removeGlobalEvent(type, listener, useCapture) {
185
+ if (!type) return;
186
+
187
+ if (typeof type === 'object') {
188
+ listener = type.listener;
189
+ useCapture = type.useCapture;
190
+ type = type.type;
191
+ }
192
+ if (this.editor.frameOptions.get('iframe')) {
193
+ this.editor.frameContext.get('_ww').removeEventListener(type, listener, useCapture);
194
+ }
195
+ this._w.removeEventListener(type, listener, useCapture);
196
+
197
+ return null;
198
+ },
199
+
200
+ /**
201
+ * @this {EventManagerThis}
202
+ * @description Activates the corresponding button with the tags information of the current cursor position,
203
+ * - such as 'bold', 'underline', etc., and executes the 'active' method of the plugins.
204
+ * @param {?Node=} selectionNode selectionNode
205
+ * @returns {Node|undefined} selectionNode
206
+ */
207
+ applyTagEffect(selectionNode) {
208
+ selectionNode = selectionNode || this.selection.getNode();
209
+ if (selectionNode === this.editor.effectNode) return;
210
+ this.editor.effectNode = selectionNode;
211
+
212
+ const marginDir = this.options.get('_rtl') ? 'marginRight' : 'marginLeft';
213
+ const plugins = this.plugins;
214
+ const commandTargets = this.editor.commandTargets;
215
+ const classOnCheck = this._onButtonsCheck;
216
+ const styleCommand = this.options.get('_styleCommandMap');
217
+ const commandMapNodes = [];
218
+ const currentNodes = [];
219
+
220
+ const styleTags = this.options.get('_textStyleTags');
221
+ const styleNodes = [];
222
+
223
+ const ignoreCommands = [];
224
+ const activeCommands = this.editor.activeCommands;
225
+ const cLen = activeCommands.length;
226
+ let nodeName = '';
227
+
228
+ if (this.component.is(selectionNode) && !this.component.__selectionSelected) {
229
+ const component = this.component.get(selectionNode);
230
+ if (!component) return;
231
+ this.component.select(component.target, component.pluginName);
232
+ return;
233
+ }
234
+
235
+ while (selectionNode.firstChild) {
236
+ selectionNode = selectionNode.firstChild;
237
+ }
238
+
239
+ const fc = this.editor.frameContext;
240
+ const notReadonly = !fc.get('isReadOnly');
241
+ for (let element = selectionNode; !dom.check.isWysiwygFrame(element); element = element.parentElement) {
242
+ if (!element) break;
243
+ if (element.nodeType !== 1 || dom.check.isBreak(element)) continue;
244
+ if (this._isNonFocusNode(element)) {
245
+ this.editor.blur();
246
+ return;
247
+ }
248
+
249
+ nodeName = element.nodeName.toLowerCase();
250
+ currentNodes.push(nodeName);
251
+ if (styleTags.includes(nodeName) && !this.format.isLine(nodeName)) styleNodes.push(element);
252
+
253
+ /* Active plugins */
254
+ if (notReadonly) {
255
+ for (let c = 0, name; c < cLen; c++) {
256
+ name = activeCommands[c];
257
+ if (
258
+ !commandMapNodes.includes(name) &&
259
+ !ignoreCommands.includes(name) &&
260
+ commandTargets.get(name) &&
261
+ commandTargets.get(name).filter((e) => {
262
+ const r = plugins[name]?.active(element, e);
263
+ if (r === undefined) {
264
+ ignoreCommands.push(name);
265
+ }
266
+ return r;
267
+ }).length > 0
268
+ ) {
269
+ commandMapNodes.push(name);
270
+ }
271
+ }
272
+ }
273
+
274
+ /** indent, outdent */
275
+ if (this.format.isLine(element)) {
276
+ /* Outdent */
277
+ if (!commandMapNodes.includes('outdent') && commandTargets.has('outdent') && (dom.check.isListCell(element) || (element.style[marginDir] && numbers.get(element.style[marginDir], 0) > 0))) {
278
+ if (
279
+ commandTargets.get('outdent').filter((e) => {
280
+ if (dom.check.isImportantDisabled(e)) return false;
281
+ e.disabled = false;
282
+ return true;
283
+ }).length > 0
284
+ ) {
285
+ commandMapNodes.push('outdent');
286
+ }
287
+ }
288
+ /* Indent */
289
+ if (!commandMapNodes.includes('indent') && commandTargets.has('indent')) {
290
+ const indentDisable = dom.check.isListCell(element) && !element.previousElementSibling;
291
+ if (
292
+ commandTargets.get('indent').filter((e) => {
293
+ if (dom.check.isImportantDisabled(e)) return false;
294
+ e.disabled = indentDisable;
295
+ return true;
296
+ }).length > 0
297
+ ) {
298
+ commandMapNodes.push('indent');
299
+ }
300
+ }
301
+
302
+ continue;
303
+ }
304
+
305
+ /** default active buttons [strong, ins, em, del, sub, sup] */
306
+ if (classOnCheck.test(nodeName)) {
307
+ nodeName = styleCommand[nodeName] || nodeName;
308
+ commandMapNodes.push(nodeName);
309
+ dom.utils.addClass(commandTargets.get(nodeName), 'active');
310
+ }
311
+ }
312
+
313
+ this._setKeyEffect(commandMapNodes);
314
+
315
+ // cache style nodes
316
+ this.__cacheStyleNodes = styleNodes.reverse();
317
+
318
+ /** save current nodes */
319
+ this.status.currentNodes = currentNodes.reverse();
320
+ this.status.currentNodesMap = commandMapNodes;
321
+
322
+ /** Displays the current node structure to statusbar */
323
+ if (this.editor.frameOptions.get('statusbar_showPathLabel') && fc.get('navigation')) {
324
+ fc.get('navigation').textContent = this.options.get('_rtl') ? this.status.currentNodes.reverse().join(' < ') : this.status.currentNodes.join(' > ');
325
+ }
326
+
327
+ return selectionNode;
328
+ },
329
+
330
+ /**
331
+ * @private
332
+ * @this {EventManagerThis}
333
+ * @description Gives an active effect when the mouse down event is blocked. (Used when "env.isGecko" is true)
334
+ * @param {Node} target Target element
335
+ * @private
336
+ */
337
+ _injectActiveEvent(target) {
338
+ dom.utils.addClass(target, '__se__active');
339
+ this.__geckoActiveEvent = this.addGlobalEvent('mouseup', () => {
340
+ dom.utils.removeClass(target, '__se__active');
341
+ this.__geckoActiveEvent = this.removeGlobalEvent(this.__geckoActiveEvent);
342
+ });
343
+ },
344
+
345
+ /**
346
+ * @private
347
+ * @this {EventManagerThis}
348
+ * @description remove class, display text.
349
+ * @param {Array<string>} ignoredList Igonred button list
350
+ * @private
351
+ */
352
+ _setKeyEffect(ignoredList) {
353
+ const activeCommands = this.editor.activeCommands;
354
+ const commandTargets = this.editor.commandTargets;
355
+ const plugins = this.plugins;
356
+ for (let i = 0, len = activeCommands.length, k, c, p; i < len; i++) {
357
+ k = activeCommands[i];
358
+ if (ignoredList.includes(k) || !(c = commandTargets.get(k))) continue;
359
+
360
+ p = plugins[k];
361
+ for (let j = 0, jLen = c.length, e; j < jLen; j++) {
362
+ e = c[j];
363
+ if (!e) continue;
364
+ if (p) {
365
+ p.active(null, e);
366
+ } else if (/^outdent$/i.test(k)) {
367
+ if (!dom.check.isImportantDisabled(e)) e.disabled = true;
368
+ } else if (/^indent$/i.test(k)) {
369
+ if (!dom.check.isImportantDisabled(e)) e.disabled = false;
370
+ } else {
371
+ dom.utils.removeClass(e, 'active');
372
+ }
373
+ }
374
+ }
375
+ },
376
+
377
+ /**
378
+ * @private
379
+ * @this {EventManagerThis}
380
+ * @description Show toolbar-balloon with delay.
381
+ */
382
+ _showToolbarBalloonDelay() {
383
+ if (this._balloonDelay) {
384
+ _w.clearTimeout(this._balloonDelay);
385
+ }
386
+
387
+ this._balloonDelay = _w.setTimeout(() => {
388
+ _w.clearTimeout(this._balloonDelay);
389
+ this._balloonDelay = null;
390
+ if (this.editor.isSubBalloon) this.subToolbar._showBalloon();
391
+ else this.toolbar._showBalloon();
392
+ }, 250);
393
+ },
394
+
395
+ /**
396
+ * @private
397
+ * @this {EventManagerThis}
398
+ * @description Show or hide the toolbar-balloon.
399
+ */
400
+ _toggleToolbarBalloon() {
401
+ this.selection._init();
402
+ const range = this.selection.getRange();
403
+ const hasSubMode = this.options.has('_subMode');
404
+
405
+ if (!(hasSubMode ? this.editor.isSubBalloonAlways : this.editor.isBalloonAlways) && range.collapsed) {
406
+ if (hasSubMode) this._hideToolbar_sub();
407
+ else this._hideToolbar();
408
+ } else {
409
+ if (hasSubMode) this.subToolbar._showBalloon(range);
410
+ else this.toolbar._showBalloon(range);
411
+ }
412
+ },
413
+
414
+ /**
415
+ * @private
416
+ * @this {EventManagerThis}
417
+ * @description Hide the toolbar.
418
+ */
419
+ _hideToolbar() {
420
+ if (!this.editor._notHideToolbar && !this.editor.frameContext.get('isFullScreen')) {
421
+ this.toolbar.hide();
422
+ }
423
+ },
424
+
425
+ /**
426
+ * @private
427
+ * @this {EventManagerThis}
428
+ * @description Hide the Sub-Toolbar.
429
+ */
430
+ _hideToolbar_sub() {
431
+ if (this.subToolbar && !this.editor._notHideToolbar) {
432
+ this.subToolbar.hide();
433
+ }
434
+ },
435
+
436
+ /**
437
+ * @private
438
+ * @this {EventManagerThis}
439
+ * @description Checks if a node is a non-focusable element(.data-se-non-focus). (e.g. fileUpload.component > span)
440
+ * @param {Node} node Node to check
441
+ * @returns {boolean} True if the node is non-focusable, otherwise false
442
+ */
443
+ _isNonFocusNode(node) {
444
+ return dom.check.isElement(node) && node.getAttribute('data-se-non-focus') === 'true';
445
+ },
446
+
447
+ /**
448
+ * @private
449
+ * @this {EventManagerThis}
450
+ * @description Determines if the "range" is within an uneditable node.
451
+ * @param {Range} range The range object
452
+ * @param {boolean} isFront Whether to check the start or end of the range
453
+ * @returns {Node|null} The uneditable node if found, otherwise null
454
+ */
455
+ _isUneditableNode(range, isFront) {
456
+ const container = isFront ? range.startContainer : range.endContainer;
457
+ const offset = isFront ? range.startOffset : range.endOffset;
458
+ const siblingKey = isFront ? 'previousSibling' : 'nextSibling';
459
+ const isElement = container.nodeType === 1;
460
+
461
+ let siblingNode;
462
+ if (isElement) {
463
+ siblingNode = /** @type {HTMLElement} */ (this._isUneditableNode_getSibling(container.childNodes[offset], siblingKey, container));
464
+ return siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false' ? siblingNode : null;
465
+ } else {
466
+ siblingNode = /** @type {HTMLElement} */ (this._isUneditableNode_getSibling(container, siblingKey, container));
467
+ return dom.check.isEdgePoint(container, offset, isFront ? 'front' : 'end') && siblingNode && siblingNode.nodeType === 1 && siblingNode.getAttribute('contenteditable') === 'false' ? siblingNode : null;
468
+ }
469
+ },
470
+
471
+ /**
472
+ * @private
473
+ * @this {EventManagerThis}
474
+ * @description Retrieves the sibling node of a selected node if it is uneditable.
475
+ * - Used only in `_isUneditableNode`.
476
+ * @param {Node} selectNode The selected node
477
+ * @param {string} siblingKey The key to access the sibling (`previousSibling` or `nextSibling`)
478
+ * @param {Node} container The parent container node
479
+ * @returns {Node|null} The sibling node if found, otherwise null
480
+ */
481
+ _isUneditableNode_getSibling(selectNode, siblingKey, container) {
482
+ if (!selectNode) return null;
483
+ let siblingNode = selectNode[siblingKey];
484
+
485
+ if (!siblingNode) {
486
+ siblingNode = this.format.getLine(container);
487
+ siblingNode = siblingNode ? siblingNode[siblingKey] : null;
488
+ if (siblingNode && !this.component.is(siblingNode)) siblingNode = siblingKey === 'previousSibling' ? siblingNode.firstChild : siblingNode.lastChild;
489
+ else return null;
490
+ }
491
+
492
+ return siblingNode;
493
+ },
494
+
495
+ /**
496
+ * @private
497
+ * @this {EventManagerThis}
498
+ * @description Deletes specific elements such as tables in "Firefox" and media elements (image, video, audio) in "Chrome".
499
+ * - Handles deletion logic based on selection range and node types.
500
+ * @returns {boolean} Returns `true` if an element was deleted and focus was adjusted, otherwise `false`.
501
+ */
502
+ _hardDelete() {
503
+ const range = this.selection.getRange();
504
+ const sc = range.startContainer;
505
+ const ec = range.endContainer;
506
+
507
+ // table
508
+ const sCell = this.format.getBlock(sc);
509
+ const eCell = this.format.getBlock(ec);
510
+ const sIsCell = dom.check.isTableCell(sCell);
511
+ const eIsCell = dom.check.isTableCell(eCell);
512
+ if (((sIsCell && !sCell.previousElementSibling && !sCell.parentElement.previousElementSibling) || (eIsCell && !eCell.nextElementSibling && !eCell.parentElement.nextElementSibling)) && sCell !== eCell) {
513
+ const ancestor = dom.query.getParentElement(range.commonAncestorContainer, dom.check.isFigure)?.parentElement || range.commonAncestorContainer;
514
+ if (!sIsCell) {
515
+ dom.utils.removeItem(dom.query.getParentElement(eCell, (current) => ancestor === current.parentNode));
516
+ } else if (!eIsCell) {
517
+ dom.utils.removeItem(dom.query.getParentElement(sCell, (current) => ancestor === current.parentNode));
518
+ } else {
519
+ dom.utils.removeItem(dom.query.getParentElement(sCell, (current) => ancestor === current.parentNode));
520
+ this.editor._nativeFocus();
521
+ return true;
522
+ }
523
+ }
524
+
525
+ // component
526
+ const sComp = sc.nodeType === 1 ? dom.query.getParentElement(sc, '.se-component') : null;
527
+ const eComp = ec.nodeType === 1 ? dom.query.getParentElement(ec, '.se-component') : null;
528
+ if (sComp) dom.utils.removeItem(sComp);
529
+ if (eComp) dom.utils.removeItem(eComp);
530
+
531
+ return false;
532
+ },
533
+
534
+ /**
535
+ * @private
536
+ * @this {EventManagerThis}
537
+ * @description If there is no default format, add a line and move 'selection'.
538
+ * @param {string|null} formatName Format tag name (default: 'P')
539
+ */
540
+ _setDefaultLine(formatName) {
541
+ if (!this.options.get('__lineFormatFilter')) return null;
542
+ if (this.editor._fileManager.pluginRegExp.test(this.editor.currentControllerName)) return;
543
+
544
+ const range = this.selection.getRange();
545
+ const commonCon = /** @type {HTMLElement} */ (range.commonAncestorContainer);
546
+ const startCon = range.startContainer;
547
+ const rangeEl = this.format.getBlock(commonCon, null);
548
+
549
+ /** @type {Node} */
550
+ let focusNode;
551
+ let offset, format;
552
+
553
+ if (rangeEl) {
554
+ format = dom.utils.createElement(formatName || this.options.get('defaultLine'));
555
+ format.innerHTML = rangeEl.innerHTML;
556
+ if (format.childNodes.length === 0) format.innerHTML = unicode.zeroWidthSpace;
557
+
558
+ rangeEl.innerHTML = format.outerHTML;
559
+ format = rangeEl.firstChild;
560
+ focusNode = dom.query.getEdgeChildNodes(format, null).sc;
561
+
562
+ if (!focusNode) {
563
+ focusNode = dom.utils.createTextNode(unicode.zeroWidthSpace);
564
+ format.insertBefore(focusNode, format.firstChild);
565
+ }
566
+
567
+ offset = focusNode.textContent.length;
568
+ this.selection.setRange(focusNode, offset, focusNode, offset);
569
+ return;
570
+ }
571
+
572
+ if (commonCon.nodeType === 3 && this.component.is(commonCon.parentElement)) {
573
+ const compInfo = this.component.get(commonCon.parentElement);
574
+ if (!compInfo) return;
575
+
576
+ const container = compInfo.container;
577
+
578
+ if (commonCon.parentElement === container) {
579
+ const siblingEl = commonCon.nextElementSibling ? container : container.nextElementSibling;
580
+ const el = dom.utils.createElement(this.options.get('defaultLine'), null, commonCon);
581
+ container.parentElement.insertBefore(el, siblingEl);
582
+ this.editor.focusEdge(el);
583
+ return;
584
+ }
585
+
586
+ this.component.select(compInfo.target, compInfo.pluginName);
587
+ return null;
588
+ } else if (commonCon.nodeType === 1 && commonCon.getAttribute('data-se-embed') === 'true') {
589
+ let el = commonCon.nextElementSibling;
590
+ if (!this.format.isLine(el)) el = this.format.addLine(commonCon, this.options.get('defaultLine'));
591
+ this.selection.setRange(el.firstChild, 0, el.firstChild, 0);
592
+ return;
593
+ }
594
+
595
+ if ((this.format.isBlock(startCon) || dom.check.isWysiwygFrame(startCon)) && (this.component.is(startCon.children[range.startOffset]) || this.component.is(startCon.children[range.startOffset - 1]))) return;
596
+ if (dom.query.getParentElement(commonCon, dom.check.isExcludeFormat)) return null;
597
+
598
+ if (this.format.isBlock(commonCon) && commonCon.childNodes.length <= 1) {
599
+ let br = null;
600
+ if (commonCon.childNodes.length === 1 && dom.check.isBreak(commonCon.firstChild)) {
601
+ br = commonCon.firstChild;
602
+ } else {
603
+ br = dom.utils.createTextNode(unicode.zeroWidthSpace);
604
+ commonCon.appendChild(br);
605
+ }
606
+
607
+ this.selection.setRange(br, 1, br, 1);
608
+ return;
609
+ }
610
+
611
+ /* eslint-disable @typescript-eslint/no-unused-vars */
612
+ try {
613
+ if (commonCon.nodeType === 3) {
614
+ format = dom.utils.createElement(formatName || this.options.get('defaultLine'));
615
+ commonCon.parentNode.insertBefore(format, commonCon);
616
+ format.appendChild(commonCon);
617
+ }
618
+
619
+ if (dom.check.isBreak(format.nextSibling)) dom.utils.removeItem(format.nextSibling);
620
+ if (dom.check.isBreak(format.previousSibling)) dom.utils.removeItem(format.previousSibling);
621
+ if (dom.check.isBreak(focusNode)) {
622
+ const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
623
+ focusNode.parentNode.insertBefore(zeroWidth, focusNode);
624
+ focusNode = zeroWidth;
625
+ }
626
+ } catch (e) {
627
+ this.editor.execCommand('formatBlock', false, formatName || this.options.get('defaultLine'));
628
+ this.selection.removeRange();
629
+ this.selection._init();
630
+ this.editor.effectNode = null;
631
+ return;
632
+ }
633
+ /* eslint-disable @typescript-eslint/no-unused-vars */
634
+
635
+ if (format) {
636
+ if (dom.check.isBreak(format.nextSibling)) dom.utils.removeItem(format.nextSibling);
637
+ if (dom.check.isBreak(format.previousSibling)) dom.utils.removeItem(format.previousSibling);
638
+ if (dom.check.isBreak(focusNode)) {
639
+ const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
640
+ focusNode.parentNode.insertBefore(zeroWidth, focusNode);
641
+ focusNode = zeroWidth;
642
+ }
643
+ }
644
+
645
+ this.editor.effectNode = null;
646
+ if (startCon) {
647
+ this.selection.setRange(startCon, 1, startCon, 1);
648
+ } else {
649
+ this.editor._nativeFocus();
650
+ }
651
+ },
652
+
653
+ /**
654
+ * @private
655
+ * @this {EventManagerThis}
656
+ * @description Handles data transfer actions for paste and drop events.
657
+ * - It processes clipboard data, triggers relevant events, and inserts cleaned data into the editor.
658
+ * @param {"paste"|"drop"} type The type of event
659
+ * @param {Event} e The original event object
660
+ * @param {DataTransfer} clipboardData The clipboard data object
661
+ * @param {__se__FrameContext} frameContext The frame context
662
+ * @returns {Promise<boolean>} Resolves to `false` if processing is complete, otherwise allows default behavior
663
+ */
664
+ async _dataTransferAction(type, e, clipboardData, frameContext) {
665
+ try {
666
+ this.ui.showLoading();
667
+ await this._setClipboardData(type, e, clipboardData, frameContext);
668
+ e.preventDefault();
669
+ e.stopPropagation();
670
+ return false;
671
+ } catch (err) {
672
+ console.warn('[SUNEDITOR.paste.error]', err);
673
+ } finally {
674
+ this.ui.hideLoading();
675
+ }
676
+ },
677
+
678
+ /**
679
+ * @private
680
+ * @this {EventManagerThis}
681
+ * @description Processes clipboard data for paste and drop events, handling text and HTML cleanup.
682
+ * - Supports specific handling for content from Microsoft Office applications.
683
+ * @param {"paste"|"drop"} type The type of event
684
+ * @param {Event} e The original event object
685
+ * @param {DataTransfer} clipboardData The clipboard data object
686
+ * @param {__se__FrameContext} frameContext The frame context
687
+ * @returns {Promise<boolean>} Resolves to `false` if processing is complete, otherwise allows default behavior
688
+ */
689
+ async _setClipboardData(type, e, clipboardData, frameContext) {
690
+ e.preventDefault();
691
+ e.stopPropagation();
692
+
693
+ let plainText = clipboardData.getData('text/plain');
694
+ let cleanData = clipboardData.getData('text/html');
695
+ const onlyText = !cleanData;
696
+
697
+ // SE copy data
698
+ const SEData = this.__secopy === plainText;
699
+ // MS word, OneNode, Excel
700
+ const MSData = /class=["']*Mso(Normal|List)/i.test(cleanData) || /content=["']*Word.Document/i.test(cleanData) || /content=["']*OneNote.File/i.test(cleanData) || /content=["']*Excel.Sheet/i.test(cleanData);
701
+ // from
702
+ const from = SEData ? 'SE' : MSData ? 'MS' : '';
703
+
704
+ if (onlyText) {
705
+ cleanData = converter.htmlToEntity(plainText).replace(/\n/g, '<br>');
706
+ } else {
707
+ cleanData = cleanData.replace(/^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g, '');
708
+ if (MSData) {
709
+ cleanData = cleanData.replace(/\n/g, ' ');
710
+ plainText = plainText.replace(/\n/g, ' ');
711
+ }
712
+ }
713
+
714
+ if (!SEData) {
715
+ const autoLinkify = this.options.get('autoLinkify');
716
+ if (autoLinkify) {
717
+ const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
718
+ dom.query.getListChildNodes(domParser.body, converter.textToAnchor);
719
+ cleanData = domParser.body.innerHTML;
720
+ }
721
+ }
722
+
723
+ if (!onlyText) {
724
+ cleanData = this.html.clean(cleanData, { forceFormat: false, whitelist: null, blacklist: null });
725
+ }
726
+
727
+ const maxCharCount = this.char.test(this.editor.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText, false);
728
+ // user event - paste
729
+ if (type === 'paste') {
730
+ const value = await this.triggerEvent('onPaste', { frameContext, event: e, data: cleanData, maxCharCount, from });
731
+ if (value === false) {
732
+ return false;
733
+ } else if (typeof value === 'string') {
734
+ if (!value) return false;
735
+ cleanData = value;
736
+ }
737
+ }
738
+ // user event - drop
739
+ if (type === 'drop') {
740
+ const value = await this.triggerEvent('onDrop', { frameContext, event: e, data: cleanData, maxCharCount, from });
741
+ if (value === false) {
742
+ return false;
743
+ } else if (typeof value === 'string') {
744
+ if (!value) return false;
745
+ cleanData = value;
746
+ }
747
+ }
748
+
749
+ // files
750
+ const files = clipboardData.files;
751
+ if (files.length > 0 && !MSData) {
752
+ for (let i = 0, len = files.length; i < len; i++) {
753
+ this._callPluginEvent('onFilePasteAndDrop', { frameContext, event: e, file: files[i] });
754
+ }
755
+
756
+ return false;
757
+ }
758
+
759
+ if (!maxCharCount) {
760
+ return false;
761
+ }
762
+
763
+ if (cleanData) {
764
+ const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
765
+ if (this._callPluginEvent('onPaste', { frameContext, event: e, data: cleanData, doc: domParser }) !== true) {
766
+ this.html.insert(cleanData, { selectInserted: false, skipCharCount: true, skipCleaning: true });
767
+ }
768
+
769
+ // document type
770
+ if (frameContext.has('documentType-use-header')) {
771
+ frameContext.get('documentType').reHeader();
772
+ }
773
+ return false;
774
+ }
775
+ },
776
+
777
+ /**
778
+ * @private
779
+ * @this {EventManagerThis}
780
+ * @description Registers common UI events such as toolbar and menu interactions.
781
+ * - Adds event listeners for various UI elements, sets up observers, and configures window events.
782
+ */
783
+ _addCommonEvents() {
784
+ const buttonsHandler = ButtonsHandler.bind(this);
785
+ const toolbarHandler = OnClick_toolbar.bind(this);
786
+
787
+ /** menu event */
788
+ this.addEvent(this.context.get('menuTray'), 'mousedown', buttonsHandler, false);
789
+ this.addEvent(this.context.get('menuTray'), 'click', OnClick_menuTray.bind(this), true);
790
+
791
+ /** toolbar event */
792
+ this.addEvent(this.context.get('toolbar.main'), 'mousedown', buttonsHandler, false);
793
+ this.addEvent(this.context.get('toolbar.main'), 'click', toolbarHandler, false);
794
+ // subToolbar
795
+ if (this.options.has('_subMode')) {
796
+ this.addEvent(this.context.get('toolbar.sub.main'), 'mousedown', buttonsHandler, false);
797
+ this.addEvent(this.context.get('toolbar.sub.main'), 'click', toolbarHandler, false);
798
+ }
799
+
800
+ /** set response toolbar */
801
+ this.toolbar._setResponsive();
802
+
803
+ /** observer */
804
+ if (env.isResizeObserverSupported) {
805
+ this._toolbarObserver = new ResizeObserver(() => {
806
+ _w.setTimeout(() => {
807
+ this.toolbar.resetResponsiveToolbar();
808
+ }, 0);
809
+ });
810
+ this._wwFrameObserver = new ResizeObserver((entries) => {
811
+ _w.setTimeout(() => {
812
+ entries.forEach((e) => {
813
+ this.editor.__callResizeFunction(this.frameRoots.get(e.target.getAttribute('data-root-key')), -1, e);
814
+ });
815
+ }, 0);
816
+ });
817
+ }
818
+
819
+ /** modal outside click */
820
+ if (this.options.get('closeModalOutsideClick')) {
821
+ this.addEvent(
822
+ this.carrierWrapper.querySelector('.se-modal .se-modal-inner'),
823
+ 'click',
824
+ (e) => {
825
+ if (e.target === this.carrierWrapper.querySelector('.se-modal .se-modal-inner')) {
826
+ this.ui._offCurrentModal();
827
+ }
828
+ },
829
+ false
830
+ );
831
+ }
832
+
833
+ /** global event */
834
+ this.addEvent(_w, 'resize', OnResize_window.bind(this), false);
835
+ this.addEvent(_w.visualViewport, 'resize', OnResize_viewport.bind(this), false);
836
+ this.addEvent(_w, 'scroll', OnScroll_window.bind(this), false);
837
+ if (isTouchDevice) {
838
+ this.addEvent(_w.visualViewport, 'scroll', OnMobileScroll_viewport.bind(this), false);
839
+ }
840
+ },
841
+
842
+ /**
843
+ * @private
844
+ * @this {EventManagerThis}
845
+ * @description Registers event listeners for the editor's frame, including text input, selection, and UI interactions.
846
+ * - Handles events inside an iframe or within the standard wysiwyg editor.
847
+ * @param {__se__FrameContext} fc The frame context object
848
+ */
849
+ _addFrameEvents(fc) {
850
+ const isIframe = fc.get('options').get('iframe');
851
+ const eventWysiwyg = isIframe ? fc.get('_ww') : fc.get('wysiwyg');
852
+ fc.set('eventWysiwyg', eventWysiwyg);
853
+ const codeArea = fc.get('code');
854
+ const dragCursor = this.editor.carrierWrapper.querySelector('.se-drag-cursor');
855
+
856
+ /** editor area */
857
+ const wwMouseMove = OnMouseMove_wysiwyg.bind(this, fc);
858
+ this.addEvent(eventWysiwyg, 'mousemove', wwMouseMove, false);
859
+ this.addEvent(eventWysiwyg, 'mouseleave', OnMouseLeave_wysiwyg.bind(this, fc), false);
860
+ this.addEvent(eventWysiwyg, 'mousedown', OnMouseDown_wysiwyg.bind(this, fc), false);
861
+ this.addEvent(eventWysiwyg, 'mouseup', OnMouseUp_wysiwyg.bind(this, fc), false);
862
+ this.addEvent(eventWysiwyg, 'click', OnClick_wysiwyg.bind(this, fc), false);
863
+ this.addEvent(eventWysiwyg, 'input', OnInput_wysiwyg.bind(this, fc), false);
864
+ this.addEvent(eventWysiwyg, 'keydown', OnKeyDown_wysiwyg.bind(this, fc), false);
865
+ this.addEvent(eventWysiwyg, 'keyup', OnKeyUp_wysiwyg.bind(this, fc), false);
866
+ this.addEvent(eventWysiwyg, 'paste', OnPaste_wysiwyg.bind(this, fc), false);
867
+ this.addEvent(eventWysiwyg, 'copy', OnCopy_wysiwyg.bind(this, fc), false);
868
+ this.addEvent(eventWysiwyg, 'cut', OnCut_wysiwyg.bind(this, fc), false);
869
+ this.addEvent(
870
+ eventWysiwyg,
871
+ 'dragover',
872
+ OnDragOver_wysiwyg.bind(this, fc, dragCursor, isIframe ? this.editor.frameContext.get('topArea') : null, !this.options.get('toolbar_container') && !this.editor.isBalloon && !this.editor.isInline),
873
+ false
874
+ );
875
+ this.addEvent(eventWysiwyg, 'dragend', OnDragEnd_wysiwyg.bind(this, dragCursor), false);
876
+ this.addEvent(eventWysiwyg, 'drop', OnDrop_wysiwyg.bind(this, fc, dragCursor), false);
877
+ this.addEvent(eventWysiwyg, 'scroll', OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), { passive: true, capture: false });
878
+ this.addEvent(eventWysiwyg, 'focus', OnFocus_wysiwyg.bind(this, fc), false);
879
+ this.addEvent(eventWysiwyg, 'blur', OnBlur_wysiwyg.bind(this, fc), false);
880
+ this.addEvent(codeArea, 'mousedown', OnFocus_code.bind(this, fc), false);
881
+
882
+ /** drag handle */
883
+ const dragHandle = fc.get('wrapper').querySelector('.se-drag-handle');
884
+ this.addEvent(
885
+ dragHandle,
886
+ 'wheel',
887
+ (event) => {
888
+ event.preventDefault();
889
+ this.component.deselect();
890
+ },
891
+ false
892
+ );
893
+
894
+ /** line breaker */
895
+ this.addEvent(fc.get('lineBreaker_t'), 'pointerdown', DisplayLineBreak.bind(this, 't'), false);
896
+ this.addEvent(fc.get('lineBreaker_b'), 'pointerdown', DisplayLineBreak.bind(this, 'b'), false);
897
+
898
+ /** Events are registered mobile. */
899
+ if (isTouchDevice) {
900
+ this.addEvent(eventWysiwyg, 'touchstart', wwMouseMove, {
901
+ passive: true,
902
+ capture: false
903
+ });
904
+ }
905
+
906
+ /** code view area auto line */
907
+ if (!this.options.get('hasCodeMirror')) {
908
+ const codeNumbers = fc.get('codeNumbers');
909
+ const cvAuthHeight = this.viewer._codeViewAutoHeight.bind(this.viewer, fc.get('code'), codeNumbers, this.editor.frameOptions.get('height') === 'auto');
910
+
911
+ this.addEvent(codeArea, 'keydown', cvAuthHeight, false);
912
+ this.addEvent(codeArea, 'keyup', cvAuthHeight, false);
913
+ this.addEvent(codeArea, 'paste', cvAuthHeight, false);
914
+
915
+ /** code view numbers */
916
+ if (codeNumbers) this.addEvent(codeArea, 'scroll', this.viewer._scrollLineNumbers.bind(codeArea, codeNumbers), false);
917
+ }
918
+
919
+ if (fc.has('statusbar')) this.__addStatusbarEvent(fc, fc.get('options'));
920
+
921
+ const OnScrollAbs = OnScroll_Abs.bind(this);
922
+ const scrollParents = dom.query.getScrollParents(fc.get('originElement'));
923
+ for (const parent of scrollParents) {
924
+ this.scrollparents.push(parent);
925
+ this.addEvent(parent, 'scroll', OnScrollAbs, false);
926
+ }
927
+
928
+ /** focus temp (mobile) */
929
+ this.addEvent(this.__focusTemp, 'focus', (e) => e.preventDefault(), false);
930
+
931
+ /** document event */
932
+ if (this.__eventDoc !== fc.get('_wd')) {
933
+ this.__eventDoc = fc.get('_wd');
934
+ this.addEvent(this.__eventDoc, 'selectionchange', OnSelectionchange_document.bind(this, this.__eventDoc), false);
935
+ }
936
+ },
937
+
938
+ /**
939
+ * @private
940
+ * @this {EventManagerThis}
941
+ * @description Adds event listeners for resizing the status bar if resizing is enabled.
942
+ * - If resizing is not enabled, applies a non-resizable class.
943
+ * @param {__se__FrameContext} fc The frame context object
944
+ * @param {__se__FrameOptions} fo The frame options object
945
+ */
946
+ __addStatusbarEvent(fc, fo) {
947
+ if (/\d+/.test(fo.get('height')) && fo.get('statusbar_resizeEnable')) {
948
+ fo.set('__statusbarEvent', this.addEvent(fc.get('statusbar'), 'mousedown', OnMouseDown_statusbar.bind(this), false));
949
+ } else {
950
+ dom.utils.addClass(fc.get('statusbar'), 'se-resizing-none');
951
+ }
952
+ },
953
+
954
+ /**
955
+ * @private
956
+ * @this {EventManagerThis}
957
+ * @description Removes all registered event listeners from the editor.
958
+ * - Disconnects observers and clears stored event references.
959
+ */
960
+ _removeAllEvents() {
961
+ for (let i = 0, len = this._events.length, e; i < len; i++) {
962
+ e = this._events[i];
963
+ e.target.removeEventListener(e.type, e.listener, e.useCapture);
964
+ }
965
+
966
+ this._events = [];
967
+
968
+ if (this._wwFrameObserver) {
969
+ this._wwFrameObserver.disconnect();
970
+ this._wwFrameObserver = null;
971
+ }
972
+
973
+ if (this._toolbarObserver) {
974
+ this._toolbarObserver.disconnect();
975
+ this._toolbarObserver = null;
976
+ }
977
+ },
978
+
979
+ /**
980
+ * @private
981
+ * @this {EventManagerThis}
982
+ * @description Adjusts the position of the editor's toolbar, controllers, and other floating elements based on scroll position.
983
+ * - Ensures UI elements maintain their intended relative positions when scrolling.
984
+ * @param {*} eventWysiwyg The wysiwyg event object containing scroll data (Window or element)
985
+ */
986
+ _moveContainer(eventWysiwyg) {
987
+ const y = eventWysiwyg.scrollTop || eventWysiwyg.scrollY || 0;
988
+ const x = eventWysiwyg.scrollLeft || eventWysiwyg.scrollX || 0;
989
+
990
+ if (this.editor.isBalloon) {
991
+ this.context.get('toolbar.main').style.top = this.toolbar._balloonOffset.top - y + 'px';
992
+ this.context.get('toolbar.main').style.left = this.toolbar._balloonOffset.left - x + 'px';
993
+ } else if (this.editor.isSubBalloon) {
994
+ this.context.get('toolbar.sub.main').style.top = this.subToolbar._balloonOffset.top - y + 'px';
995
+ this.context.get('toolbar.sub.main').style.left = this.subToolbar._balloonOffset.left - x + 'px';
996
+ }
997
+
998
+ if (this.editor._controllerTargetContext !== this.editor.frameContext.get('topArea')) {
999
+ this.ui._offCurrentController();
1000
+ }
1001
+
1002
+ if (this.editor._lineBreaker_t) {
1003
+ const t_style = this.editor._lineBreaker_t.style;
1004
+ if (t_style.display !== 'none') {
1005
+ const t_offset = (this.editor._lineBreaker_t.getAttribute('data-offset') || ',').split(',');
1006
+ t_style.top = numbers.get(t_style.top, 0) - (y - numbers.get(t_offset[0], 0)) + 'px';
1007
+ t_style.left = numbers.get(t_style.left, 0) - (x - numbers.get(t_offset[1], 0)) + 'px';
1008
+ this.editor._lineBreaker_t.setAttribute('data-offset', y + ',' + x);
1009
+ }
1010
+ }
1011
+
1012
+ if (this.editor._lineBreaker_b) {
1013
+ const b_style = this.editor._lineBreaker_b.style;
1014
+ if (b_style.display !== 'none') {
1015
+ const b_offset = (this.editor._lineBreaker_b.getAttribute('data-offset') || ',').split(',');
1016
+ b_style.top = numbers.get(b_style.top, 0) - (y - numbers.get(b_offset[0], 0)) + 'px';
1017
+ b_style[b_offset[1]] = numbers.get(b_style[b_offset[1]], 0) - (x - numbers.get(b_offset[2], 0)) + 'px';
1018
+ this.editor._lineBreaker_b.setAttribute('data-offset', y + ',' + b_offset[1] + ',' + x);
1019
+ }
1020
+ }
1021
+
1022
+ const openCont = this.editor.opendControllers;
1023
+ for (let i = 0; i < openCont.length; i++) {
1024
+ if (!openCont[i].notInCarrier) continue;
1025
+ openCont[i].form.style.top = openCont[i].inst.__offset.top - y + 'px';
1026
+ openCont[i].form.style.left = openCont[i].inst.__offset.left - x + 'px';
1027
+ }
1028
+ },
1029
+
1030
+ /**
1031
+ * @private
1032
+ * @this {EventManagerThis}
1033
+ * @description Handles the scrolling of the editor container.
1034
+ * - Repositions open controllers if necessary.
1035
+ */
1036
+ _scrollContainer() {
1037
+ if (this.menu.currentDropdownActiveButton && this.menu.currentDropdown) {
1038
+ this.menu._resetMenuPosition(this.menu.currentDropdownActiveButton, this.menu.currentDropdown);
1039
+ }
1040
+
1041
+ const openCont = this.editor.opendControllers;
1042
+ if (!openCont.length) return;
1043
+
1044
+ this.__rePositionController(openCont);
1045
+ },
1046
+
1047
+ /**
1048
+ * @private
1049
+ * @this {EventManagerThis}
1050
+ * @description Repositions the currently open controllers within the editor.
1051
+ * - Ensures elements are displayed in their correct positions after scrolling.
1052
+ * @param {Array<object>} cont List of controllers to reposition
1053
+ */
1054
+ __rePositionController(cont) {
1055
+ if (_DragHandle.get('__dragMove')) _DragHandle.get('__dragMove')();
1056
+ for (let i = 0; i < cont.length; i++) {
1057
+ if (cont[i].notInCarrier) continue;
1058
+ cont[i].inst?.show();
1059
+ }
1060
+ },
1061
+
1062
+ /**
1063
+ * @private
1064
+ * @this {EventManagerThis}
1065
+ * @description Resets the frame status, adjusting toolbar and UI elements based on the current state.
1066
+ * - Handles inline editor adjustments, fullscreen mode, and responsive toolbar updates.
1067
+ */
1068
+ _resetFrameStatus() {
1069
+ if (!env.isResizeObserverSupported) {
1070
+ this.toolbar.resetResponsiveToolbar();
1071
+ if (this.options.get('_subMode')) this.subToolbar.resetResponsiveToolbar();
1072
+ }
1073
+
1074
+ const toolbar = this.context.get('toolbar.main');
1075
+ const isToolbarHidden = toolbar.style.display === 'none' || (this.editor.isInline && !this.toolbar._inlineToolbarAttr.isShow);
1076
+ if (toolbar.offsetWidth === 0 && !isToolbarHidden) return;
1077
+
1078
+ const opendBrowser = this.editor.opendBrowser;
1079
+ if (opendBrowser && opendBrowser.area.style.display === 'block') {
1080
+ opendBrowser.body.style.maxHeight = dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
1081
+ }
1082
+
1083
+ if (this.menu.currentDropdownActiveButton && this.menu.currentDropdown) {
1084
+ this.menu._resetMenuPosition(this.menu.currentDropdownActiveButton, this.menu.currentDropdown);
1085
+ }
1086
+
1087
+ if (this.viewer._resetFullScreenHeight()) return;
1088
+
1089
+ const fc = this.editor.frameContext;
1090
+ if (fc.get('isCodeView') && this.editor.isInline) {
1091
+ this.toolbar._showInline();
1092
+ return;
1093
+ }
1094
+
1095
+ this.editor._iframeAutoHeight(fc);
1096
+
1097
+ if (this.toolbar._sticky) {
1098
+ this.context.get('toolbar.main').style.width = fc.get('topArea').offsetWidth - 2 + 'px';
1099
+ this.toolbar._resetSticky();
1100
+ }
1101
+ },
1102
+
1103
+ /**
1104
+ * @private
1105
+ * @this {EventManagerThis}
1106
+ * @description Synchronizes the selection state by resetting it on mouseup.
1107
+ * - Ensures selection updates correctly across different interactions.
1108
+ */
1109
+ _setSelectionSync() {
1110
+ this.removeGlobalEvent(this.__selectionSyncEvent);
1111
+ this.__selectionSyncEvent = this.addGlobalEvent('mouseup', () => {
1112
+ this.selection._init();
1113
+ this.removeGlobalEvent(this.__selectionSyncEvent);
1114
+ });
1115
+ },
1116
+
1117
+ /**
1118
+ * @private
1119
+ * @this {EventManagerThis}
1120
+ * @description Retains the style nodes for formatting consistency when applying styles.
1121
+ * - Preserves nested styling by cloning and restructuring the style nodes.
1122
+ * @param {HTMLElement} formatEl The format element where styles should be retained
1123
+ * @param {Array<Node>} _styleNodes The list of style nodes to retain
1124
+ */
1125
+ _retainStyleNodes(formatEl, _styleNodes) {
1126
+ const el = _styleNodes[0].cloneNode(false);
1127
+ let n = el;
1128
+ for (let i = 1, len = _styleNodes.length, t; i < len; i++) {
1129
+ t = _styleNodes[i].cloneNode(false);
1130
+ n.appendChild(t);
1131
+ n = t;
1132
+ }
1133
+
1134
+ const { parent, inner } = this.nodeTransform.createNestedNode(_styleNodes, null);
1135
+ const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
1136
+ inner.appendChild(zeroWidth);
1137
+
1138
+ formatEl.innerHTML = '';
1139
+ formatEl.appendChild(parent);
1140
+
1141
+ this.selection.setRange(zeroWidth, 1, zeroWidth, 1);
1142
+ },
1143
+
1144
+ /**
1145
+ * @private
1146
+ * @this {EventManagerThis}
1147
+ * @description Clears retained style nodes by replacing content with a single line break.
1148
+ * - Resets the selection to the start of the cleared element.
1149
+ * @param {HTMLElement} formatEl The format element where styles should be cleared
1150
+ */
1151
+ _clearRetainStyleNodes(formatEl) {
1152
+ formatEl.innerHTML = '<br>';
1153
+ this.selection.setRange(formatEl, 0, formatEl, 0);
1154
+ },
1155
+
1156
+ /**
1157
+ * @private
1158
+ * @this {EventManagerThis}
1159
+ * @description Calls a registered plugin event and executes associated handlers.
1160
+ * - If any handler returns `false`, the event propagation stops.
1161
+ * @param {string} name The name of the plugin event
1162
+ * @param {{ frameContext: __se__FrameContext, event: Event, data?: string, line?: Node, range?: Range, file?: File, doc?: Document }} e The event object passed to the plugin event handler
1163
+ * @returns {boolean|undefined} Returns `false` if any handler stops the event, otherwise `undefined`
1164
+ */
1165
+ _callPluginEvent(name, e) {
1166
+ const eventPlugins = this.editor._onPluginEvents.get(name);
1167
+ for (let i = 0, r; i < eventPlugins.length; i++) {
1168
+ r = eventPlugins[i](e);
1169
+ if (typeof r === 'boolean') return r;
1170
+ }
1171
+ },
1172
+
1173
+ /**
1174
+ * @private
1175
+ * @this {EventManagerThis}
1176
+ * @description Handles the selection of a component when hovering over it.
1177
+ * - If the target is a component, it ensures that the component is selected properly.
1178
+ * @param {Element} target The element being hovered over
1179
+ */
1180
+ _overComponentSelect(target) {
1181
+ const figure = dom.query.getParentElement(target, dom.check.isFigure);
1182
+ let info = this.component.get(target);
1183
+ if (info || figure) {
1184
+ if (!info) info = this.component.get(figure);
1185
+ if (info && !dom.utils.hasClass(info.container, 'se-component-selected')) {
1186
+ this.ui._offCurrentController();
1187
+ _DragHandle.set('__overInfo', ON_OVER_COMPONENT);
1188
+ this.component.select(info.target, info.pluginName);
1189
+ }
1190
+ } else if (_DragHandle.get('__overInfo') !== null && !dom.utils.hasClass(target, 'se-drag-handle')) {
1191
+ this.component.__deselect();
1192
+ _DragHandle.set('__overInfo', null);
1193
+ }
1194
+ },
1195
+
1196
+ /**
1197
+ * @private
1198
+ * @this {EventManagerThis}
1199
+ * @description Removes input event listeners and resets input-related properties.
1200
+ */
1201
+ __removeInput() {
1202
+ this._inputFocus = this.editor._preventBlur = false;
1203
+ this.__inputBlurEvent = this.removeEvent(this.__inputBlurEvent);
1204
+ this.__inputKeyEvent = this.removeEvent(this.__inputKeyEvent);
1205
+ this.__inputPlugin = null;
1206
+ },
1207
+
1208
+ /**
1209
+ * @private
1210
+ * @this {EventManagerThis}
1211
+ * @description Prevents the default behavior of the Enter key and refocuses the editor.
1212
+ * @param {Event} e The keyboard event
1213
+ */
1214
+ __enterPrevent(e) {
1215
+ e.preventDefault();
1216
+ if (!isMobile) return;
1217
+
1218
+ this.__focusTemp.focus({ preventScroll: true });
1219
+ this.editor.frameContext.get('wysiwyg').focus({ preventScroll: true });
1220
+ },
1221
+
1222
+ /**
1223
+ * @private
1224
+ * @description Scrolls the editor view to the caret position after pressing Enter. (Ignored on mobile devices)
1225
+ * @this {EventManagerThis}
1226
+ * @param {*} range Range object
1227
+ */
1228
+ __enterScrollTo(range) {
1229
+ this.editor._iframeAutoHeight(this.editor.frameContext);
1230
+
1231
+ // scroll to
1232
+ if (env.isMobile && this.scrollparents.length > 0) return;
1233
+ this.editor.selection.scrollTo(range, { behavior: 'auto', block: 'nearest', inline: 'nearest' });
1234
+ },
1235
+
1236
+ /**
1237
+ * @private
1238
+ * @description Focus Event Postprocessing
1239
+ * @this {EventManagerThis}
1240
+ * @param {__se__FrameContext} frameContext - frame context object
1241
+ * @param {Event} event - Event object
1242
+ */
1243
+ __postFocusEvent(frameContext, event) {
1244
+ if (this.editor.isInline || this.editor.isBalloonAlways) this.toolbar.show();
1245
+ if (this.editor.isSubBalloonAlways) this.subToolbar.show();
1246
+
1247
+ // user event
1248
+ this.triggerEvent('onFocus', { frameContext, event });
1249
+ // plugin event
1250
+ this._callPluginEvent('onFocus', { frameContext, event });
1251
+ },
1252
+
1253
+ /**
1254
+ * @private
1255
+ * @description Blur Event Postprocessing
1256
+ * @this {EventManagerThis}
1257
+ * @param {__se__FrameContext} frameContext - frame context object
1258
+ * @param {Event} event - Event object
1259
+ */
1260
+ __postBlurEvent(frameContext, event) {
1261
+ if (this.editor.isInline || this.editor.isBalloon) this._hideToolbar();
1262
+ if (this.editor.isSubBalloon) this._hideToolbar_sub();
1263
+
1264
+ // user event
1265
+ this.triggerEvent('onBlur', { frameContext, event });
1266
+ // plugin event
1267
+ this._callPluginEvent('onBlur', { frameContext, event });
1268
+ },
1269
+
1270
+ /**
1271
+ * @private
1272
+ * @description Records the current viewport size.
1273
+ * @this {EventManagerThis}
1274
+ */
1275
+ __setViewportSize() {
1276
+ const currentVisibleHeight = (this.status.currentViewportHeight = numbers.get(_w.visualViewport.height, 0));
1277
+ this.editor.setRootCssVar('--se-var-viewport-height', `${currentVisibleHeight}px`);
1278
+ },
1279
+
1280
+ constructor: EventManager
1281
+ };
1282
+
1283
+ /**
1284
+ * @this {EventManagerThis}
1285
+ * @param {__se__FrameContext} frameContext - frame context object
1286
+ * @param {Element|Window} eventWysiwyg - wysiwyg event object
1287
+ * @param {Event} e - Event object
1288
+ */
1289
+ function OnScroll_wysiwyg(frameContext, eventWysiwyg, e) {
1290
+ this._moveContainer(eventWysiwyg);
1291
+ this._scrollContainer();
1292
+
1293
+ // plugin event
1294
+ this._callPluginEvent('onScroll', { frameContext, event: e });
1295
+
1296
+ // document type page
1297
+ if (frameContext.has('documentType-use-page')) {
1298
+ frameContext.get('documentType').scrollPage();
1299
+ }
1300
+
1301
+ // user event
1302
+ this.triggerEvent('onScroll', { frameContext, event: e });
1303
+ }
1304
+
1305
+ /**
1306
+ * @this {EventManagerThis}
1307
+ * @param {__se__FrameContext} frameContext - frame context object
1308
+ * @param {Event} e - Event object
1309
+ */
1310
+ function OnFocus_wysiwyg(frameContext, e) {
1311
+ if (this.selection.__iframeFocus || frameContext.get('isReadOnly') || frameContext.get('isDisabled')) {
1312
+ e.preventDefault();
1313
+ return false;
1314
+ }
1315
+
1316
+ this.status.hasFocus = true;
1317
+ this.component.__prevent = false;
1318
+ this.triggerEvent('onNativeFocus', { frameContext, event: e });
1319
+
1320
+ const rootKey = frameContext.get('key');
1321
+
1322
+ if (this._inputFocus) {
1323
+ if (this.editor.isInline) {
1324
+ this._w.setTimeout(() => {
1325
+ this.toolbar._showInline();
1326
+ }, 0);
1327
+ }
1328
+ return;
1329
+ }
1330
+
1331
+ if ((this.status.rootKey === rootKey && this.editor._preventBlur) || this.editor._preventFocus) return;
1332
+ this.editor._preventFocus = true;
1333
+
1334
+ const onSelected = this.editor.status.onSelected || this.editor.opendModal;
1335
+ this.ui._offCurrentController();
1336
+
1337
+ dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
1338
+ dom.utils.setDisabled(this.editor._codeViewDisabledButtons, false);
1339
+
1340
+ this.editor.changeFrameContext(rootKey);
1341
+ this.history.resetButtons(rootKey, null);
1342
+
1343
+ if (!onSelected) {
1344
+ this.applyTagEffect();
1345
+ }
1346
+
1347
+ this._w.setTimeout(() => {
1348
+ this.__postFocusEvent(frameContext, e);
1349
+ }, 0);
1350
+ }
1351
+
1352
+ /**
1353
+ * @this {EventManagerThis}
1354
+ * @param {__se__FrameContext} frameContext - frame context object
1355
+ * @param {Event} e - Event object
1356
+ */
1357
+ function OnBlur_wysiwyg(frameContext, e) {
1358
+ if (frameContext.get('isCodeView') || frameContext.get('isReadOnly') || frameContext.get('isDisabled')) return;
1359
+
1360
+ this.status.hasFocus = false;
1361
+ this.editor.effectNode = null;
1362
+ this.triggerEvent('onNativeBlur', { frameContext, event: e });
1363
+
1364
+ if (this._inputFocus || this.editor._preventBlur) return;
1365
+ this.editor._preventFocus = false;
1366
+
1367
+ this._setKeyEffect([]);
1368
+
1369
+ this.status.currentNodes = [];
1370
+ this.status.currentNodesMap = [];
1371
+
1372
+ this.editor.applyFrameRoots((root) => {
1373
+ if (root.get('navigation')) root.get('navigation').textContent = '';
1374
+ });
1375
+
1376
+ this.history.check(frameContext.get('key'), this.status._range);
1377
+
1378
+ this.__postBlurEvent(frameContext, e);
1379
+ }
1380
+
1381
+ /**
1382
+ * @this {EventManagerThis}
1383
+ * @param {MouseEvent} e - Event object
1384
+ */
1385
+ function OnMouseDown_statusbar(e) {
1386
+ e.stopPropagation();
1387
+ this._resizeClientY = e.clientY;
1388
+ this.ui.enableBackWrapper('ns-resize');
1389
+ this.__resize_editor = this.addGlobalEvent('mousemove', __resizeEditor.bind(this));
1390
+ this.__close_move = this.addGlobalEvent('mouseup', __closeMove.bind(this));
1391
+ }
1392
+
1393
+ /**
1394
+ * @this {EventManagerThis}
1395
+ * @param {MouseEvent} e - Event object
1396
+ */
1397
+ function __resizeEditor(e) {
1398
+ const fc = this.editor.frameContext;
1399
+ const resizeInterval = fc.get('wrapper').offsetHeight + (e.clientY - this._resizeClientY);
1400
+ const h = resizeInterval < fc.get('_minHeight') ? fc.get('_minHeight') : resizeInterval;
1401
+ fc.get('wysiwygFrame').style.height = fc.get('code').style.height = h + 'px';
1402
+ this._resizeClientY = e.clientY;
1403
+ if (!env.isResizeObserverSupported) this.editor.__callResizeFunction(fc, h, null);
1404
+ }
1405
+
1406
+ /**
1407
+ * @this {EventManagerThis}
1408
+ */
1409
+ function __closeMove() {
1410
+ this.ui.disableBackWrapper();
1411
+ if (this.__resize_editor) this.__resize_editor = this.removeGlobalEvent(this.__resize_editor);
1412
+ if (this.__close_move) this.__close_move = this.removeGlobalEvent(this.__close_move);
1413
+ }
1414
+
1415
+ /**
1416
+ * @this {EventManagerThis}
1417
+ * @param {"t"|"b"} dir - Direction
1418
+ * @param {Event} e - Event object
1419
+ */
1420
+ function DisplayLineBreak(dir, e) {
1421
+ e.preventDefault();
1422
+
1423
+ const component = this._lineBreakComp;
1424
+ if (!component) return;
1425
+
1426
+ const isList = dom.check.isListCell(component.parentElement);
1427
+ const format = dom.utils.createElement(isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.options.get('defaultLine'));
1428
+ if (!isList) format.innerHTML = '<br>';
1429
+
1430
+ if (this.editor.frameOptions.get('charCounter_type') === 'byte-html' && !this.char.check(format.outerHTML)) return;
1431
+
1432
+ component.parentNode.insertBefore(format, dir === 't' ? component : component.nextSibling);
1433
+ this.component.deselect();
1434
+
1435
+ try {
1436
+ const focusEl = isList ? format : format.firstChild;
1437
+ this.selection.setRange(focusEl, 1, focusEl, 1);
1438
+ this.history.push(false);
1439
+ } catch (err) {
1440
+ console.warn('[SUNEDITOR.lineBreaker.error]', err);
1441
+ }
1442
+ }
1443
+
1444
+ /**
1445
+ * @this {EventManagerThis}
1446
+ */
1447
+ function OnResize_window() {
1448
+ this.status.initViewportHeight = _w.visualViewport.height;
1449
+
1450
+ if (!isMobile) {
1451
+ this.ui._offCurrentController();
1452
+ }
1453
+
1454
+ if (this.editor.isBalloon) this.toolbar.hide();
1455
+ else if (this.editor.isSubBalloon) this.subToolbar.hide();
1456
+
1457
+ this._resetFrameStatus();
1458
+ }
1459
+
1460
+ /**
1461
+ * @this {EventManagerThis}
1462
+ */
1463
+ function OnResize_viewport() {
1464
+ if (isMobile && this.options.get('toolbar_sticky') > -1) {
1465
+ this.toolbar._resetSticky();
1466
+ this.editor.menu._restoreMenuPosition();
1467
+ }
1468
+
1469
+ this._scrollContainer();
1470
+ this.__setViewportSize();
1471
+ }
1472
+
1473
+ /**
1474
+ * @this {EventManagerThis}
1475
+ */
1476
+ function OnScroll_window() {
1477
+ if (this.options.get('toolbar_sticky') > -1) {
1478
+ this.toolbar._resetSticky();
1479
+ }
1480
+
1481
+ if (this.editor.isBalloon && this.context.get('toolbar.main').style.display === 'block') {
1482
+ this.toolbar._setBalloonOffset(this.toolbar._balloonOffset.position === 'top');
1483
+ } else if (this.editor.isSubBalloon && this.context.get('toolbar.sub.main').style.display === 'block') {
1484
+ this.subToolbar._setBalloonOffset(this.subToolbar._balloonOffset.position === 'top');
1485
+ }
1486
+
1487
+ this._scrollContainer();
1488
+
1489
+ // document type page
1490
+ if (this.editor.frameContext.has('documentType-use-page')) {
1491
+ this.editor.frameContext.get('documentType').scrollWindow();
1492
+ }
1493
+ }
1494
+
1495
+ /**
1496
+ * @this {EventManagerThis}
1497
+ */
1498
+ function OnMobileScroll_viewport() {
1499
+ if (this.options.get('toolbar_sticky') > -1) {
1500
+ this.toolbar._resetSticky();
1501
+ this.editor.menu._restoreMenuPosition();
1502
+ }
1503
+ }
1504
+
1505
+ /**
1506
+ * @this {EventManagerThis}
1507
+ * @param {Document} _wd - Wysiwyg document
1508
+ */
1509
+ function OnSelectionchange_document(_wd) {
1510
+ if (this.editor._preventSelection) return;
1511
+
1512
+ const selection = _wd.getSelection();
1513
+ let anchorNode = selection.anchorNode;
1514
+
1515
+ this.editor.applyFrameRoots((root) => {
1516
+ if (anchorNode && root.get('wysiwyg').contains(anchorNode)) {
1517
+ if (root.get('isReadOnly') || root.get('isDisabled')) return;
1518
+
1519
+ anchorNode = null;
1520
+ this.selection._init();
1521
+ this.applyTagEffect();
1522
+
1523
+ // document type
1524
+ if (root.has('documentType-use-header')) {
1525
+ const el = dom.query.getParentElement(this.selection.selectionNode, this.format.isLine.bind(this.format));
1526
+ root.get('documentType').on(el);
1527
+ }
1528
+ }
1529
+ });
1530
+ }
1531
+
1532
+ /**
1533
+ * @this {EventManagerThis}
1534
+ */
1535
+ function OnScroll_Abs() {
1536
+ this.menu.dropdownOff();
1537
+ this._scrollContainer();
1538
+ }
1539
+
1540
+ /**
1541
+ * @this {EventManagerThis}
1542
+ * @param {__se__FrameContext} frameContext - frame context object
1543
+ */
1544
+ function OnFocus_code(frameContext) {
1545
+ this.editor.changeFrameContext(frameContext.get('key'));
1546
+ dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
1547
+ dom.utils.setDisabled(this.editor._codeViewDisabledButtons, true);
1548
+ }
1549
+
1550
+ export default EventManager;