suneditor 3.1.4 → 3.2.0

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 (216) hide show
  1. package/dist/suneditor-contents.min.css +1 -1
  2. package/dist/suneditor.min.css +28 -1
  3. package/dist/suneditor.min.js +1 -1
  4. package/package.json +2 -1
  5. package/src/assets/design/color.css +12 -0
  6. package/src/assets/design/size.css +9 -2
  7. package/src/assets/icons/defaultIcons.js +96 -47
  8. package/src/assets/suneditor-contents.css +3 -2
  9. package/src/assets/suneditor.css +480 -31
  10. package/src/core/config/contextProvider.js +2 -1
  11. package/src/core/config/eventManager.js +41 -2
  12. package/src/core/config/optionProvider.js +23 -2
  13. package/src/core/editor.js +32 -14
  14. package/src/core/event/actions/index.js +76 -9
  15. package/src/core/event/effects/common.registry.js +2 -1
  16. package/src/core/event/effects/keydown.registry.js +169 -33
  17. package/src/core/event/effects/ruleHelpers.js +20 -6
  18. package/src/core/event/eventOrchestrator.js +136 -23
  19. package/src/core/event/handlers/handler_toolbar.js +4 -2
  20. package/src/core/event/handlers/handler_ww_dragDrop.js +10 -0
  21. package/src/core/event/handlers/handler_ww_input.js +9 -2
  22. package/src/core/event/handlers/handler_ww_key.js +86 -17
  23. package/src/core/event/handlers/handler_ww_mouse.js +45 -8
  24. package/src/core/event/ports.js +23 -5
  25. package/src/core/event/reducers/keydown.reducer.js +15 -2
  26. package/src/core/event/rules/keydown.rule.arrow.js +12 -2
  27. package/src/core/event/rules/keydown.rule.backspace.js +110 -15
  28. package/src/core/event/rules/keydown.rule.delete.js +62 -7
  29. package/src/core/event/rules/keydown.rule.enter.js +62 -31
  30. package/src/core/event/support/defaultLineManager.js +9 -2
  31. package/src/core/event/support/selectionState.js +13 -3
  32. package/src/core/kernel/coreKernel.js +1 -0
  33. package/src/core/logic/dom/char.js +13 -3
  34. package/src/core/logic/dom/format.js +171 -39
  35. package/src/core/logic/dom/html.js +350 -72
  36. package/src/core/logic/dom/inline.js +229 -34
  37. package/src/core/logic/dom/listFormat.js +111 -20
  38. package/src/core/logic/dom/nodeTransform.js +36 -11
  39. package/src/core/logic/dom/offset.js +116 -31
  40. package/src/core/logic/dom/selection.js +147 -29
  41. package/src/core/logic/panel/blockHandle.js +785 -0
  42. package/src/core/logic/panel/blockResolver.js +278 -0
  43. package/src/core/logic/panel/finder.js +16 -6
  44. package/src/core/logic/panel/menu.js +119 -18
  45. package/src/core/logic/panel/toolbar.js +75 -19
  46. package/src/core/logic/panel/viewer.js +76 -20
  47. package/src/core/logic/shell/_commandExecutor.js +44 -9
  48. package/src/core/logic/shell/commandDispatcher.js +46 -4
  49. package/src/core/logic/shell/component.js +102 -25
  50. package/src/core/logic/shell/focusManager.js +13 -3
  51. package/src/core/logic/shell/history.js +21 -6
  52. package/src/core/logic/shell/pluginManager.js +21 -13
  53. package/src/core/logic/shell/shortcuts.js +9 -1
  54. package/src/core/logic/shell/ui.js +139 -37
  55. package/src/core/schema/frameContext.js +34 -3
  56. package/src/core/schema/options.js +46 -4
  57. package/src/core/section/constructor.js +537 -93
  58. package/src/core/section/documentType.js +35 -6
  59. package/src/helper/converter.js +24 -7
  60. package/src/helper/dom/domCheck.js +25 -5
  61. package/src/helper/dom/domQuery.js +2 -1
  62. package/src/helper/dom/domUtils.js +17 -4
  63. package/src/helper/env.js +11 -2
  64. package/src/helper/keyCodeMap.js +6 -2
  65. package/src/helper/markdown.js +24 -5
  66. package/src/helper/msOffice.js +9 -2
  67. package/src/interfaces/plugins.js +1 -1
  68. package/src/langs/ckb.js +0 -1
  69. package/src/langs/cs.js +0 -1
  70. package/src/langs/da.js +0 -1
  71. package/src/langs/de.js +0 -1
  72. package/src/langs/en.js +0 -1
  73. package/src/langs/es.js +0 -1
  74. package/src/langs/fa.js +0 -1
  75. package/src/langs/fr.js +0 -1
  76. package/src/langs/he.js +0 -1
  77. package/src/langs/hu.js +0 -1
  78. package/src/langs/index.js +27 -1
  79. package/src/langs/it.js +0 -1
  80. package/src/langs/ja.js +0 -1
  81. package/src/langs/km.js +0 -1
  82. package/src/langs/ko.js +0 -1
  83. package/src/langs/lv.js +0 -1
  84. package/src/langs/nl.js +0 -1
  85. package/src/langs/pl.js +0 -1
  86. package/src/langs/pt_br.js +0 -1
  87. package/src/langs/ro.js +0 -1
  88. package/src/langs/ru.js +0 -1
  89. package/src/langs/se.js +0 -1
  90. package/src/langs/tr.js +0 -1
  91. package/src/langs/uk.js +0 -1
  92. package/src/langs/ur.js +0 -1
  93. package/src/langs/zh_cn.js +0 -1
  94. package/src/modules/contract/Browser.js +58 -14
  95. package/src/modules/contract/ColorPicker.js +12 -4
  96. package/src/modules/contract/Controller.js +37 -7
  97. package/src/modules/contract/Figure.js +156 -36
  98. package/src/modules/contract/HueSlider.js +27 -4
  99. package/src/modules/contract/Modal.js +50 -10
  100. package/src/modules/manager/FileManager.js +50 -8
  101. package/src/modules/ui/CommandMenu.js +597 -0
  102. package/src/modules/ui/ModalAnchorEditor.js +33 -6
  103. package/src/modules/ui/SelectMenu.js +481 -36
  104. package/src/modules/ui/index.js +1 -0
  105. package/src/plugins/browser/audioGallery.js +2 -1
  106. package/src/plugins/browser/fileBrowser.js +9 -2
  107. package/src/plugins/browser/fileGallery.js +2 -1
  108. package/src/plugins/browser/imageGallery.js +18 -3
  109. package/src/plugins/browser/videoGallery.js +13 -4
  110. package/src/plugins/command/blockquote.js +6 -1
  111. package/src/plugins/command/codeBlock.js +43 -5
  112. package/src/plugins/command/exportPDF.js +20 -4
  113. package/src/plugins/command/fileUpload.js +46 -11
  114. package/src/plugins/command/list_bulleted.js +6 -1
  115. package/src/plugins/command/list_numbered.js +6 -1
  116. package/src/plugins/dropdown/align.js +21 -33
  117. package/src/plugins/dropdown/backgroundColor.js +10 -2
  118. package/src/plugins/dropdown/blockStyle.js +17 -22
  119. package/src/plugins/dropdown/font.js +34 -34
  120. package/src/plugins/dropdown/hr.js +15 -43
  121. package/src/plugins/dropdown/layout.js +10 -27
  122. package/src/plugins/dropdown/lineHeight.js +10 -33
  123. package/src/plugins/dropdown/list.js +19 -25
  124. package/src/plugins/dropdown/paragraphStyle.js +16 -24
  125. package/src/plugins/dropdown/table/index.js +72 -17
  126. package/src/plugins/dropdown/table/render/table.html.js +3 -1
  127. package/src/plugins/dropdown/table/services/table.cell.js +33 -7
  128. package/src/plugins/dropdown/table/services/table.clipboard.js +5 -1
  129. package/src/plugins/dropdown/table/services/table.grid.js +40 -9
  130. package/src/plugins/dropdown/table/services/table.resize.js +55 -11
  131. package/src/plugins/dropdown/table/services/table.selection.js +27 -7
  132. package/src/plugins/dropdown/table/services/table.style.js +120 -20
  133. package/src/plugins/dropdown/template.js +13 -29
  134. package/src/plugins/dropdown/textStyle.js +22 -35
  135. package/src/plugins/field/autocomplete.js +8 -4
  136. package/src/plugins/field/slashCommand.js +297 -0
  137. package/src/plugins/index.js +3 -0
  138. package/src/plugins/input/fontSize.js +37 -8
  139. package/src/plugins/input/pageNavigator.js +7 -1
  140. package/src/plugins/modal/audio.js +71 -17
  141. package/src/plugins/modal/drawing.js +41 -11
  142. package/src/plugins/modal/embed.js +92 -22
  143. package/src/plugins/modal/image/index.js +114 -28
  144. package/src/plugins/modal/image/services/image.size.js +9 -2
  145. package/src/plugins/modal/image/services/image.upload.js +38 -4
  146. package/src/plugins/modal/link.js +9 -2
  147. package/src/plugins/modal/math.js +10 -2
  148. package/src/plugins/modal/video/index.js +112 -26
  149. package/src/plugins/modal/video/render/video.html.js +4 -1
  150. package/src/plugins/modal/video/services/video.size.js +15 -4
  151. package/src/plugins/modal/video/services/video.upload.js +10 -2
  152. package/src/plugins/popup/anchor.js +14 -2
  153. package/src/suneditor.js +8 -2
  154. package/src/themes/cobalt.css +12 -0
  155. package/src/themes/cream.css +12 -0
  156. package/src/themes/dark.css +12 -0
  157. package/src/themes/midnight.css +12 -0
  158. package/src/typedef.js +32 -21
  159. package/types/assets/icons/defaultIcons.d.ts +1 -0
  160. package/types/core/config/contextProvider.d.ts +7 -1
  161. package/types/core/config/eventManager.d.ts +37 -4
  162. package/types/core/config/optionProvider.d.ts +5 -1
  163. package/types/core/event/actions/index.d.ts +44 -6
  164. package/types/core/event/effects/keydown.registry.d.ts +37 -7
  165. package/types/core/event/effects/ruleHelpers.d.ts +6 -1
  166. package/types/core/event/eventOrchestrator.d.ts +6 -1
  167. package/types/core/event/handlers/handler_ww_clipboard.d.ts +15 -3
  168. package/types/core/event/handlers/handler_ww_dragDrop.d.ts +14 -2
  169. package/types/core/event/handlers/handler_ww_input.d.ts +10 -2
  170. package/types/core/event/handlers/handler_ww_key.d.ts +10 -2
  171. package/types/core/event/handlers/handler_ww_mouse.d.ts +25 -5
  172. package/types/core/event/ports.d.ts +7 -2
  173. package/types/core/event/rules/keydown.rule.backspace.d.ts +6 -1
  174. package/types/core/kernel/store.d.ts +4 -1
  175. package/types/core/logic/dom/format.d.ts +32 -14
  176. package/types/core/logic/dom/html.d.ts +11 -3
  177. package/types/core/logic/dom/nodeTransform.d.ts +6 -6
  178. package/types/core/logic/panel/blockHandle.d.ts +64 -0
  179. package/types/core/logic/panel/blockResolver.d.ts +50 -0
  180. package/types/core/logic/panel/menu.d.ts +72 -3
  181. package/types/core/logic/shell/commandDispatcher.d.ts +8 -0
  182. package/types/core/logic/shell/component.d.ts +5 -3
  183. package/types/core/logic/shell/shortcuts.d.ts +10 -1
  184. package/types/core/logic/shell/ui.d.ts +7 -1
  185. package/types/core/schema/context.d.ts +7 -1
  186. package/types/core/schema/frameContext.d.ts +36 -4
  187. package/types/core/schema/options.d.ts +83 -2
  188. package/types/core/section/constructor.d.ts +38 -1
  189. package/types/events.d.ts +208 -32
  190. package/types/helper/converter.d.ts +4 -2
  191. package/types/helper/dom/domCheck.d.ts +12 -2
  192. package/types/helper/dom/domQuery.d.ts +35 -7
  193. package/types/helper/dom/domUtils.d.ts +4 -1
  194. package/types/interfaces/plugins.d.ts +2 -2
  195. package/types/langs/_Lang.d.ts +0 -1
  196. package/types/modules/contract/ColorPicker.d.ts +2 -2
  197. package/types/modules/contract/Controller.d.ts +7 -0
  198. package/types/modules/contract/Figure.d.ts +31 -4
  199. package/types/modules/ui/CommandMenu.d.ts +262 -0
  200. package/types/modules/ui/SelectMenu.d.ts +56 -3
  201. package/types/modules/ui/index.d.ts +1 -0
  202. package/types/plugins/browser/audioGallery.d.ts +1 -1
  203. package/types/plugins/browser/fileBrowser.d.ts +1 -1
  204. package/types/plugins/browser/fileGallery.d.ts +1 -1
  205. package/types/plugins/browser/imageGallery.d.ts +1 -1
  206. package/types/plugins/browser/videoGallery.d.ts +1 -1
  207. package/types/plugins/dropdown/align.d.ts +1 -1
  208. package/types/plugins/dropdown/blockStyle.d.ts +3 -1
  209. package/types/plugins/dropdown/table/index.d.ts +4 -1
  210. package/types/plugins/dropdown/table/services/table.clipboard.d.ts +5 -1
  211. package/types/plugins/dropdown/table/services/table.grid.d.ts +10 -2
  212. package/types/plugins/field/autocomplete.d.ts +0 -1
  213. package/types/plugins/field/slashCommand.d.ts +165 -0
  214. package/types/plugins/index.d.ts +3 -0
  215. package/types/plugins/modal/link.d.ts +4 -1
  216. package/types/typedef.d.ts +48 -23
@@ -4,7 +4,13 @@ import { _DragHandle } from '../../modules/ui';
4
4
 
5
5
  // event handlers
6
6
  import { ButtonsHandler, OnClick_menuTray, OnClick_toolbar } from '../event/handlers/handler_toolbar';
7
- import { OnMouseDown_wysiwyg, OnMouseUp_wysiwyg, OnClick_wysiwyg, OnMouseMove_wysiwyg, OnMouseLeave_wysiwyg } from '../event/handlers/handler_ww_mouse';
7
+ import {
8
+ OnMouseDown_wysiwyg,
9
+ OnMouseUp_wysiwyg,
10
+ OnClick_wysiwyg,
11
+ OnMouseMove_wysiwyg,
12
+ OnMouseLeave_wysiwyg,
13
+ } from '../event/handlers/handler_ww_mouse';
8
14
  import { OnBeforeInput_wysiwyg, OnInput_wysiwyg } from '../event/handlers/handler_ww_input';
9
15
  import { OnKeyDown_wysiwyg, OnKeyUp_wysiwyg } from '../event/handlers/handler_ww_key';
10
16
  import { OnPaste_wysiwyg, OnCopy_wysiwyg, OnCut_wysiwyg } from '../event/handlers/handler_ww_clipboard';
@@ -245,14 +251,21 @@ class EventOrchestrator extends KernelInjector {
245
251
  // SE copy data
246
252
  const SEData = this.__secopy === plainText;
247
253
  // MS word, OneNode, Excel
248
- 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);
254
+ const MSData =
255
+ /class=["']*Mso(Normal|List)/i.test(cleanData) ||
256
+ /content=["']*Word.Document/i.test(cleanData) ||
257
+ /content=["']*OneNote.File/i.test(cleanData) ||
258
+ /content=["']*Excel.Sheet/i.test(cleanData);
249
259
  // from
250
260
  const from = SEData ? 'SE' : MSData ? 'MS' : '';
251
261
 
252
262
  if (onlyText) {
253
263
  cleanData = converter.htmlToEntity(plainText).replace(/\n/g, '<br>');
254
264
  } else {
255
- cleanData = cleanData.replace(/^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g, '');
265
+ cleanData = cleanData.replace(
266
+ /^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g,
267
+ '',
268
+ );
256
269
  if (MSData) {
257
270
  cleanData = cleanData.replace(/\n/g, ' ');
258
271
  plainText = plainText.replace(/\n/g, ' ');
@@ -273,10 +286,19 @@ class EventOrchestrator extends KernelInjector {
273
286
  cleanData = this.$.html.clean(cleanData, { forceFormat: false, whitelist: null, blacklist: null });
274
287
  }
275
288
 
276
- const maxCharCount = this.$.char.test(this.$.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText, false);
289
+ const maxCharCount = this.$.char.test(
290
+ this.$.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText,
291
+ false,
292
+ );
277
293
  // user event - paste
278
294
  if (type === 'paste') {
279
- const value = await this.#eventManager.triggerEvent('onPaste', { frameContext, event: e, data: cleanData, maxCharCount, from });
295
+ const value = await this.#eventManager.triggerEvent('onPaste', {
296
+ frameContext,
297
+ event: e,
298
+ data: cleanData,
299
+ maxCharCount,
300
+ from,
301
+ });
280
302
  if (value === false) {
281
303
  return false;
282
304
  } else if (typeof value === 'string') {
@@ -286,7 +308,13 @@ class EventOrchestrator extends KernelInjector {
286
308
  }
287
309
  // user event - drop
288
310
  if (type === 'drop') {
289
- const value = await this.#eventManager.triggerEvent('onDrop', { frameContext, event: e, data: cleanData, maxCharCount, from });
311
+ const value = await this.#eventManager.triggerEvent('onDrop', {
312
+ frameContext,
313
+ event: e,
314
+ data: cleanData,
315
+ maxCharCount,
316
+ from,
317
+ });
290
318
  if (value === false) {
291
319
  return false;
292
320
  } else if (typeof value === 'string') {
@@ -311,7 +339,14 @@ class EventOrchestrator extends KernelInjector {
311
339
 
312
340
  if (cleanData) {
313
341
  const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
314
- if ((await this._callPluginEventAsync('onPaste', { frameContext, event: e, data: cleanData, doc: domParser })) !== false) {
342
+ if (
343
+ (await this._callPluginEventAsync('onPaste', {
344
+ frameContext,
345
+ event: e,
346
+ data: cleanData,
347
+ doc: domParser,
348
+ })) !== false
349
+ ) {
315
350
  this.$.html.insert(cleanData, { selectInserted: false, skipCharCount: true, skipCleaning: true });
316
351
  }
317
352
 
@@ -422,12 +457,21 @@ class EventOrchestrator extends KernelInjector {
422
457
  this.#eventManager.addEvent(
423
458
  eventWysiwyg,
424
459
  'dragover',
425
- OnDragOver_wysiwyg.bind(this, fc, dragCursor, isIframe ? this.$.frameContext.get('topArea') : null, !this.#options.get('toolbar_container') && !this.#store.mode.isBalloon && !this.#store.mode.isInline),
460
+ OnDragOver_wysiwyg.bind(
461
+ this,
462
+ fc,
463
+ dragCursor,
464
+ isIframe ? this.$.frameContext.get('topArea') : null,
465
+ !this.#options.get('toolbar_container') && !this.#store.mode.isBalloon && !this.#store.mode.isInline,
466
+ ),
426
467
  false,
427
468
  );
428
469
  this.#eventManager.addEvent(eventWysiwyg, 'dragend', OnDragEnd_wysiwyg.bind(this, dragCursor), false);
429
470
  this.#eventManager.addEvent(eventWysiwyg, 'drop', OnDrop_wysiwyg.bind(this, fc, dragCursor), false);
430
- this.#eventManager.addEvent(eventWysiwyg, 'scroll', this.#OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), { passive: true, capture: false });
471
+ this.#eventManager.addEvent(eventWysiwyg, 'scroll', this.#OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), {
472
+ passive: true,
473
+ capture: false,
474
+ });
431
475
  this.#eventManager.addEvent(eventWysiwyg, 'focus', this.#OnFocus_wysiwyg.bind(this, fc), false);
432
476
  this.#eventManager.addEvent(eventWysiwyg, 'blur', this.#OnBlur_wysiwyg.bind(this, fc), false);
433
477
  this.#eventManager.addEvent(codeArea, 'mousedown', this.#OnFocus_code.bind(this, fc), false);
@@ -445,8 +489,18 @@ class EventOrchestrator extends KernelInjector {
445
489
  );
446
490
 
447
491
  /** line breaker */
448
- this.#eventManager.addEvent(fc.get('lineBreaker_t'), 'pointerdown', this.#DisplayLineBreak.bind(this, 't'), false);
449
- this.#eventManager.addEvent(fc.get('lineBreaker_b'), 'pointerdown', this.#DisplayLineBreak.bind(this, 'b'), false);
492
+ this.#eventManager.addEvent(
493
+ fc.get('lineBreaker_t'),
494
+ 'pointerdown',
495
+ this.#DisplayLineBreak.bind(this, 't'),
496
+ false,
497
+ );
498
+ this.#eventManager.addEvent(
499
+ fc.get('lineBreaker_b'),
500
+ 'pointerdown',
501
+ this.#DisplayLineBreak.bind(this, 'b'),
502
+ false,
503
+ );
450
504
 
451
505
  /** Events are registered mobile. */
452
506
  if (isTouchDevice) {
@@ -459,7 +513,12 @@ class EventOrchestrator extends KernelInjector {
459
513
  /** code view area auto line */
460
514
  if (!this.#options.get('hasCodeMirror')) {
461
515
  const codeNumbers = fc.get('codeNumbers');
462
- const cvAuthHeight = this.$.viewer._codeViewAutoHeight.bind(this.$.viewer, fc.get('code'), codeNumbers, this.$.frameOptions.get('height') === 'auto');
516
+ const cvAuthHeight = this.$.viewer._codeViewAutoHeight.bind(
517
+ this.$.viewer,
518
+ fc.get('code'),
519
+ codeNumbers,
520
+ this.$.frameOptions.get('height') === 'auto',
521
+ );
463
522
 
464
523
  this.#eventManager.addEvent(codeArea, 'keydown', cvAuthHeight, false);
465
524
  this.#eventManager.addEvent(codeArea, 'keyup', cvAuthHeight, false);
@@ -471,7 +530,13 @@ class EventOrchestrator extends KernelInjector {
471
530
  }
472
531
 
473
532
  /** code view numbers */
474
- if (codeNumbers) this.#eventManager.addEvent(codeArea, 'scroll', this.$.viewer._scrollLineNumbers.bind(codeArea, codeNumbers), false);
533
+ if (codeNumbers)
534
+ this.#eventManager.addEvent(
535
+ codeArea,
536
+ 'scroll',
537
+ this.$.viewer._scrollLineNumbers.bind(codeArea, codeNumbers),
538
+ false,
539
+ );
475
540
  }
476
541
 
477
542
  /** markdown view area */
@@ -480,7 +545,12 @@ class EventOrchestrator extends KernelInjector {
480
545
  this.#eventManager.addEvent(markdownArea, 'mousedown', this.#OnFocus_markdown.bind(this, fc), false);
481
546
 
482
547
  const mdNumbers = fc.get('markdownNumbers');
483
- const mdAutoHeight = this.$.viewer._markdownViewAutoHeight.bind(this.$.viewer, markdownArea, mdNumbers, this.$.frameOptions.get('height') === 'auto');
548
+ const mdAutoHeight = this.$.viewer._markdownViewAutoHeight.bind(
549
+ this.$.viewer,
550
+ markdownArea,
551
+ mdNumbers,
552
+ this.$.frameOptions.get('height') === 'auto',
553
+ );
484
554
 
485
555
  this.#eventManager.addEvent(markdownArea, 'keydown', mdAutoHeight, false);
486
556
  this.#eventManager.addEvent(markdownArea, 'keyup', mdAutoHeight, false);
@@ -491,7 +561,13 @@ class EventOrchestrator extends KernelInjector {
491
561
  this.#eventManager.addEvent(markdownArea, 'keydown', InsertTab, false);
492
562
  }
493
563
 
494
- if (mdNumbers) this.#eventManager.addEvent(markdownArea, 'scroll', this.$.viewer._scrollMarkdownLineNumbers.bind(markdownArea, mdNumbers), false);
564
+ if (mdNumbers)
565
+ this.#eventManager.addEvent(
566
+ markdownArea,
567
+ 'scroll',
568
+ this.$.viewer._scrollMarkdownLineNumbers.bind(markdownArea, mdNumbers),
569
+ false,
570
+ );
495
571
  }
496
572
 
497
573
  if (fc.has('statusbar')) this.__addStatusbarEvent(fc, fc.get('options'));
@@ -509,7 +585,12 @@ class EventOrchestrator extends KernelInjector {
509
585
  /** document event */
510
586
  if (this.__eventDoc !== fc.get('_wd')) {
511
587
  this.__eventDoc = fc.get('_wd');
512
- this.#eventManager.addEvent(this.__eventDoc, 'selectionchange', this.#OnSelectionchange_document.bind(this, this.__eventDoc), false);
588
+ this.#eventManager.addEvent(
589
+ this.__eventDoc,
590
+ 'selectionchange',
591
+ this.#OnSelectionchange_document.bind(this, this.__eventDoc),
592
+ false,
593
+ );
513
594
  }
514
595
  }
515
596
 
@@ -522,7 +603,15 @@ class EventOrchestrator extends KernelInjector {
522
603
  */
523
604
  __addStatusbarEvent(fc, fo) {
524
605
  if (/\d+/.test(fo.get('height')) && fo.get('statusbar_resizeEnable')) {
525
- fo.set('__statusbarEvent', this.#eventManager.addEvent(fc.get('statusbar'), 'mousedown', this.#OnMouseDown_statusbar.bind(this), false));
606
+ fo.set(
607
+ '__statusbarEvent',
608
+ this.#eventManager.addEvent(
609
+ fc.get('statusbar'),
610
+ 'mousedown',
611
+ this.#OnMouseDown_statusbar.bind(this),
612
+ false,
613
+ ),
614
+ );
526
615
  } else {
527
616
  dom.utils.addClass(fc.get('statusbar'), 'se-resizing-none');
528
617
  }
@@ -673,6 +762,9 @@ class EventOrchestrator extends KernelInjector {
673
762
  // sticky
674
763
  this.#toolbar._resetSticky();
675
764
 
765
+ // placeholder
766
+ this.#ui._updatePlaceholder(frameContext);
767
+
676
768
  // user event
677
769
  this.#eventManager.triggerEvent('onFocus', { frameContext, event });
678
770
  // plugin event
@@ -735,12 +827,14 @@ class EventOrchestrator extends KernelInjector {
735
827
  }
736
828
 
737
829
  const toolbar = this.#context.get('toolbar_main');
738
- const isToolbarHidden = toolbar.style.display === 'none' || (this.#store.mode.isInline && !this.#toolbar.inlineToolbarAttr.isShow);
830
+ const isToolbarHidden =
831
+ toolbar.style.display === 'none' || (this.#store.mode.isInline && !this.#toolbar.inlineToolbarAttr.isShow);
739
832
  if (toolbar.offsetWidth === 0 && !isToolbarHidden) return;
740
833
 
741
834
  const opendBrowser = this.#ui.opendBrowser;
742
835
  if (opendBrowser && opendBrowser.area.style.display === 'block') {
743
- opendBrowser.body.style.maxHeight = dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
836
+ opendBrowser.body.style.maxHeight =
837
+ dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
744
838
  }
745
839
 
746
840
  if (this.#menu.currentDropdownActiveButton && this.#menu.currentDropdown) {
@@ -812,7 +906,11 @@ class EventOrchestrator extends KernelInjector {
812
906
  return;
813
907
  }
814
908
 
815
- if ((this.#store.get('rootKey') === rootKey && this.#store.get('_preventBlur')) || this.#store.get('_preventFocus')) return;
909
+ if (
910
+ (this.#store.get('rootKey') === rootKey && this.#store.get('_preventBlur')) ||
911
+ this.#store.get('_preventFocus')
912
+ )
913
+ return;
816
914
  this.#store.set('_preventFocus', true);
817
915
 
818
916
  dom.utils.removeClass(this.$.commandDispatcher.targets.get('codeView'), 'active');
@@ -838,6 +936,9 @@ class EventOrchestrator extends KernelInjector {
838
936
  this.#store.set('_lastSelectionNode', null);
839
937
  this.#eventManager.triggerEvent('onNativeBlur', { frameContext, event: e });
840
938
 
939
+ // Drop the per-line placeholder on blur; the empty-editor placeholder takes over if applicable.
940
+ this.#ui._updatePlaceholder(frameContext);
941
+
841
942
  if (this._inputFocus || this.#store.get('_preventBlur')) return;
842
943
  this.#store.set('_preventFocus', false);
843
944
 
@@ -897,7 +998,9 @@ class EventOrchestrator extends KernelInjector {
897
998
  if (!component) return;
898
999
 
899
1000
  const isList = dom.check.isListCell(component.parentElement);
900
- const format = dom.utils.createElement(isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.#options.get('defaultLine'));
1001
+ const format = dom.utils.createElement(
1002
+ isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.#options.get('defaultLine'),
1003
+ );
901
1004
  if (!isList) format.innerHTML = '<br>';
902
1005
 
903
1006
  if (this.$.frameOptions.get('charCounter_type') === 'byte-html' && !this.$.char.check(format.outerHTML)) return;
@@ -939,7 +1042,11 @@ class EventOrchestrator extends KernelInjector {
939
1042
  this.__setViewportSize();
940
1043
 
941
1044
  if (isMobile && prevHeight > 0 && prevHeight - _w.visualViewport.height > 100 && this.#store.get('hasFocus')) {
942
- this.$.selection.scrollTo(this.$.selection.getRange(), { behavior: 'auto', block: 'nearest', inline: 'nearest' });
1045
+ this.$.selection.scrollTo(this.$.selection.getRange(), {
1046
+ behavior: 'auto',
1047
+ block: 'nearest',
1048
+ inline: 'nearest',
1049
+ });
943
1050
  }
944
1051
  }
945
1052
 
@@ -984,6 +1091,9 @@ class EventOrchestrator extends KernelInjector {
984
1091
  this.$.selection.init();
985
1092
  this.applyTagEffect();
986
1093
 
1094
+ // Notion-style per-line placeholder follows the caret's line.
1095
+ this.#ui._updatePlaceholder(root);
1096
+
987
1097
  // balloon toolbar - touch devices
988
1098
  if (isTouchDevice && (this.#store.mode.isBalloon || this.#store.mode.isSubBalloon)) {
989
1099
  this.#toggleToolbarBalloonDelay();
@@ -991,7 +1101,10 @@ class EventOrchestrator extends KernelInjector {
991
1101
 
992
1102
  // document type
993
1103
  if (root.has('documentType_use_header')) {
994
- const el = dom.query.getParentElement(this.$.selection.selectionNode, this.$.format.isLine.bind(this.$.format));
1104
+ const el = dom.query.getParentElement(
1105
+ this.$.selection.selectionNode,
1106
+ this.$.format.isLine.bind(this.$.format),
1107
+ );
995
1108
  root.get('documentType').on(el);
996
1109
  }
997
1110
  }
@@ -53,13 +53,15 @@ export function ButtonsHandler(e) {
53
53
  if (this.__inputBlurEvent) this.__removeInput();
54
54
 
55
55
  // blur event
56
- if (typeof plugin.toolbarInputChange === 'function') this.__inputPlugin = { obj: plugin, target: eventTarget, value: eventTarget.value };
56
+ if (typeof plugin.toolbarInputChange === 'function')
57
+ this.__inputPlugin = { obj: plugin, target: eventTarget, value: eventTarget.value };
57
58
  this.__inputBlurEvent = this.$.eventManager.addEvent(eventTarget, 'blur', (ev) => {
58
59
  if (plugin.isInputActive) return;
59
60
 
60
61
  try {
61
62
  const value = eventTarget.value.trim();
62
- if (typeof plugin.toolbarInputChange === 'function' && value !== this.__inputPlugin.value) plugin.toolbarInputChange({ target: eventTarget, value, event: ev });
63
+ if (typeof plugin.toolbarInputChange === 'function' && value !== this.__inputPlugin.value)
64
+ plugin.toolbarInputChange({ target: eventTarget, value, event: ev });
63
65
  } finally {
64
66
  // Defer flag reset — wysiwyg focus event fires synchronously during blur and checks this flag
65
67
  _w.setTimeout(() => (this._inputFocus = false), 0);
@@ -16,6 +16,11 @@ const { _w } = env;
16
16
  * @param {DragEvent} e - Event object
17
17
  */
18
18
  export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar, e) {
19
+ // Block-handle drag uses its own indicator + drop logic — skip the component
20
+ if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('application/x-suneditor-block-drag')) {
21
+ return;
22
+ }
23
+
19
24
  const { sc, so, ec, eo } = this.$.selection.getDragEventLocationRange(e);
20
25
  if (!sc) return;
21
26
 
@@ -91,6 +96,11 @@ export function OnDrop_wysiwyg(fc, dragCursor, e) {
91
96
  const dataTransfer = e.dataTransfer;
92
97
  if (!dataTransfer) return true;
93
98
 
99
+ // Block-handle drag handles its own drop — skip the text-insertion path.
100
+ if (Array.from(dataTransfer.types || []).includes('application/x-suneditor-block-drag')) {
101
+ return;
102
+ }
103
+
94
104
  const { sc, so, ec, eo } = this.$.selection.getDragEventLocationRange(e);
95
105
  if (!sc) return;
96
106
 
@@ -29,7 +29,8 @@ export async function OnBeforeInput_wysiwyg(fc, e) {
29
29
  }
30
30
 
31
31
  // user event
32
- if ((await this.$.eventManager.triggerEvent('onBeforeInput', { frameContext: fc, event: e, data })) === false) return;
32
+ if ((await this.$.eventManager.triggerEvent('onBeforeInput', { frameContext: fc, event: e, data })) === false)
33
+ return;
33
34
  // plugin event
34
35
  await this._callPluginEventAsync('onBeforeInput', { frameContext: fc, event: e, data });
35
36
  }
@@ -49,7 +50,13 @@ export async function OnInput_wysiwyg(fc, e) {
49
50
  const range = this.$.selection.getRange();
50
51
  const selectionNode = this.$.selection.getNode();
51
52
  const formatEl = this.$.format.getLine(selectionNode, null);
52
- if (!this.$.format.isNormalLine(formatEl) && !this.$.format.isBrLine(formatEl) && range.collapsed && !this.$.component.is(selectionNode) && !dom.check.isList(selectionNode)) {
53
+ if (
54
+ !this.$.format.isNormalLine(formatEl) &&
55
+ !this.$.format.isBrLine(formatEl) &&
56
+ range.collapsed &&
57
+ !this.$.component.is(selectionNode) &&
58
+ !dom.check.isList(selectionNode)
59
+ ) {
53
60
  const rangeEl = this.$.format.getBlock(selectionNode, null);
54
61
  this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine'));
55
62
  }
@@ -47,12 +47,22 @@ export async function OnKeyDown_wysiwyg(fc, e) {
47
47
  if (!this.$.store.mode.isSubBalloonAlways) this._hideToolbar_sub();
48
48
  }
49
49
 
50
- // user event
51
- if ((await this.$.eventManager.triggerEvent('onKeyDown', { frameContext: fc, event: e })) === false) return;
52
-
53
- /** default key action */
50
+ /** default key action — normalize the Enter range before the reducer reads it */
54
51
  if (keyCodeMap.isEnter(keyCode) && this.$.format.isLine(this.$.selection.getRange()?.startContainer)) {
55
52
  this.$.selection.resetRangeToTextNode();
53
+
54
+ const r = this.$.selection.getRange();
55
+ if (
56
+ r.startContainer === r.endContainer &&
57
+ r.startOffset !== r.endOffset &&
58
+ r.startContainer.nodeType === 3 &&
59
+ dom.check.isZeroWidth(r.startContainer)
60
+ ) {
61
+ const br = r.startContainer.nextSibling;
62
+ if (br && dom.check.isBreak(br)) this.$.selection.setRange(br, 0, br, 0);
63
+ else this.$.selection.setRange(r.endContainer, r.endOffset, r.endContainer, r.endOffset);
64
+ }
65
+
56
66
  selectionNode = this.$.selection.getNode();
57
67
  }
58
68
 
@@ -60,12 +70,22 @@ export async function OnKeyDown_wysiwyg(fc, e) {
60
70
  const formatEl = /** @type {HTMLElement} */ (this.$.format.getLine(selectionNode, null) || selectionNode);
61
71
 
62
72
  /** Shortcuts */
63
- if (ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.shortcuts.command(e, ctrl, shift, keyCode, '', false, null, null)) {
73
+ if (
74
+ ctrl &&
75
+ !keyCodeMap.isNonTextKey(keyCode) &&
76
+ this.$.shortcuts.command(e, ctrl, shift, keyCode, '', false, null, null)
77
+ ) {
64
78
  this._onShortcutKey = true;
65
79
  e.preventDefault();
66
80
  e.stopPropagation();
67
81
  return false;
68
- } else if (!ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.format.isLine(formatEl) && range.collapsed && dom.check.isEdgePoint(range.startContainer, 0, 'front')) {
82
+ } else if (
83
+ !ctrl &&
84
+ !keyCodeMap.isNonTextKey(keyCode) &&
85
+ this.$.format.isLine(formatEl) &&
86
+ range.collapsed &&
87
+ dom.check.isEdgePoint(range.startContainer, 0, 'front')
88
+ ) {
69
89
  const keyword = /** @type {Text} */ (range.startContainer).substringData?.(0, range.startOffset);
70
90
  if (keyword && this.$.shortcuts.command(e, false, shift, keyCode, keyword, true, formatEl, range)) {
71
91
  this._onShortcutKey = true;
@@ -77,16 +97,36 @@ export async function OnKeyDown_wysiwyg(fc, e) {
77
97
  this._onShortcutKey = false;
78
98
  }
79
99
 
80
- // plugin event
81
- if ((await this._callPluginEventAsync('onKeyDown', { frameContext: fc, event: e, range, line: formatEl })) === false) return;
82
-
83
100
  // reducer / actions
84
101
  /** @type {import('../reducers/keydown.reducer').KeydownReducerCtx} */
85
- const ctx = { e, fc, store: this.$.store, options: this.$.options, frameOptions: this.$.frameOptions, range, selectionNode, formatEl, keyCode, ctrl, alt, shift };
102
+ const ctx = {
103
+ e,
104
+ fc,
105
+ store: this.$.store,
106
+ options: this.$.options,
107
+ frameOptions: this.$.frameOptions,
108
+ range,
109
+ selectionNode,
110
+ formatEl,
111
+ keyCode,
112
+ ctrl,
113
+ alt,
114
+ shift,
115
+ };
86
116
  const ports = makePorts(this, { _styleNodes });
87
117
 
88
118
  // action execute
89
119
  const actions = await reduceKeydown(ports, ctx);
120
+
121
+ if ((await this.$.eventManager.triggerEvent('onKeyDown', { frameContext: fc, event: e })) === false) return;
122
+
123
+ // plugin event
124
+ if (
125
+ (await this._callPluginEventAsync('onKeyDown', { frameContext: fc, event: e, range, line: formatEl })) === false
126
+ )
127
+ return;
128
+
129
+ // action execute
90
130
  await actionExecutor(actions, { ports, ctx });
91
131
  }
92
132
 
@@ -110,7 +150,11 @@ export async function OnKeyUp_wysiwyg(fc, e) {
110
150
  const range = this.$.selection.getRange();
111
151
  let selectionNode = this.$.selection.getNode();
112
152
 
113
- if ((this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) && (((this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) && !keyCodeMap.isEsc(keyCode)) || !range.collapsed)) {
153
+ if (
154
+ (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) &&
155
+ (((this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) && !keyCodeMap.isEsc(keyCode)) ||
156
+ !range.collapsed)
157
+ ) {
114
158
  if (this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) {
115
159
  if (!keyCodeMap.isEsc(keyCode)) this._showToolbarBalloonDelay();
116
160
  } else {
@@ -121,14 +165,25 @@ export async function OnKeyUp_wysiwyg(fc, e) {
121
165
  }
122
166
 
123
167
  /** when format tag deleted */
124
- if (keyCodeMap.isBackspace(keyCode) && dom.check.isWysiwygFrame(selectionNode) && selectionNode.textContent === '' && selectionNode.children.length === 0) {
168
+ if (
169
+ keyCodeMap.isBackspace(keyCode) &&
170
+ dom.check.isWysiwygFrame(selectionNode) &&
171
+ selectionNode.textContent === '' &&
172
+ selectionNode.children.length === 0
173
+ ) {
125
174
  e.preventDefault();
126
175
  e.stopPropagation();
127
176
 
128
177
  selectionNode.innerHTML = '';
129
178
 
130
179
  const currentNodeName = this.$.store.get('currentNodes')[0];
131
- const oFormatTag = dom.utils.createElement(this.$.format.isLine(currentNodeName) && !dom.check.isListCell(currentNodeName) ? currentNodeName : this.$.options.get('defaultLine'), null, '<br>');
180
+ const oFormatTag = dom.utils.createElement(
181
+ this.$.format.isLine(currentNodeName) && !dom.check.isListCell(currentNodeName)
182
+ ? currentNodeName
183
+ : this.$.options.get('defaultLine'),
184
+ null,
185
+ '<br>',
186
+ );
132
187
  selectionNode.appendChild(oFormatTag);
133
188
  this.$.selection.setRange(oFormatTag, 0, oFormatTag, 0);
134
189
  this.applyTagEffect();
@@ -172,7 +227,12 @@ export async function OnKeyUp_wysiwyg(fc, e) {
172
227
  }
173
228
 
174
229
  const textKey = !keyState.ctrl && !keyState.alt && !keyCodeMap.isNonTextKey(keyCode);
175
- if (textKey && selectionNode.nodeType === 3 && unicode.zeroWidthRegExp.test(selectionNode.textContent) && !(e.isComposing !== undefined ? e.isComposing : this.isComposing)) {
230
+ if (
231
+ textKey &&
232
+ selectionNode.nodeType === 3 &&
233
+ unicode.zeroWidthRegExp.test(selectionNode.textContent) &&
234
+ !(e.isComposing !== undefined ? e.isComposing : this.isComposing)
235
+ ) {
176
236
  let so = range.startOffset,
177
237
  eo = range.endOffset;
178
238
  const frontZeroWidthCnt = (selectionNode.textContent.substring(0, eo).match(FRONT_ZEROWIDTH) || '').length;
@@ -182,7 +242,12 @@ export async function OnKeyUp_wysiwyg(fc, e) {
182
242
  this.$.selection.setRange(selectionNode, so < 0 ? 0 : so, selectionNode, eo < 0 ? 0 : eo);
183
243
  }
184
244
 
185
- if (keyCodeMap.isRemoveKey(keyCode) && dom.check.isZeroWidth(formatEl?.textContent) && !formatEl.previousElementSibling && !dom.check.isListCell(formatEl)) {
245
+ if (
246
+ keyCodeMap.isRemoveKey(keyCode) &&
247
+ dom.check.isZeroWidth(formatEl?.textContent) &&
248
+ !formatEl.previousElementSibling &&
249
+ !dom.check.isListCell(formatEl)
250
+ ) {
186
251
  const rsMode = this.$.options.get('retainStyleMode');
187
252
  if (rsMode !== 'none' && _styleNodes.value?.length > 0) {
188
253
  if (rsMode === 'repeat') {
@@ -209,7 +274,10 @@ export async function OnKeyUp_wysiwyg(fc, e) {
209
274
  if (fc.has('documentType_use_header')) {
210
275
  if (keyCodeMap.isDocumentTypeObserverKey(keyCode)) {
211
276
  fc.get('documentType').reHeader();
212
- const el = dom.query.getParentElement(this.$.selection.selectionNode, this.$.format.isLine.bind(this.$.format));
277
+ const el = dom.query.getParentElement(
278
+ this.$.selection.selectionNode,
279
+ this.$.format.isLine.bind(this.$.format),
280
+ );
213
281
  fc.get('documentType').on(el);
214
282
  } else {
215
283
  const el = dom.query.getParentElement(selectionNode, (current) => current.nodeType === 1);
@@ -220,7 +288,8 @@ export async function OnKeyUp_wysiwyg(fc, e) {
220
288
  // user event
221
289
  if ((await this.$.eventManager.triggerEvent('onKeyUp', { frameContext: fc, event: e })) === false) return;
222
290
  // plugin event
223
- if ((await this._callPluginEventAsync('onKeyUp', { frameContext: fc, event: e, range, line: formatEl })) === false) return;
291
+ if ((await this._callPluginEventAsync('onKeyUp', { frameContext: fc, event: e, range, line: formatEl })) === false)
292
+ return;
224
293
 
225
294
  if (keyCodeMap.isHistoryRelevantKey(keyCode)) {
226
295
  this.$.history.push(true);
@@ -3,6 +3,12 @@ import { _DragHandle } from '../../../modules/ui';
3
3
 
4
4
  const { _w } = env;
5
5
 
6
+ /**
7
+ * Per-frame rAF state for wysiwyg mousemove coalescing.
8
+ * @type {WeakMap<*, {rafId: ?number, event: ?MouseEvent}>}
9
+ */
10
+ const __wwMousemoveRaf = new WeakMap();
11
+
6
12
  /**
7
13
  * @typedef {import('../eventOrchestrator').default} EventManagerThis_handler_ww_mouse
8
14
  */
@@ -102,7 +108,12 @@ export async function OnClick_wysiwyg(fc, e) {
102
108
  if (e.detail === 3) {
103
109
  const range = this.$.selection.getRange();
104
110
  if (this.$.format.isLine(range.endContainer) && range.endOffset === 0) {
105
- this.$.selection.setRange(range.startContainer, range.startOffset, range.startContainer, range.startContainer.textContent.length);
111
+ this.$.selection.setRange(
112
+ range.startContainer,
113
+ range.startOffset,
114
+ range.startContainer,
115
+ range.startContainer.textContent.length,
116
+ );
106
117
  }
107
118
  }
108
119
 
@@ -122,7 +133,8 @@ export async function OnClick_wysiwyg(fc, e) {
122
133
  !dom.check.isWysiwygFrame(selectionNode) &&
123
134
  !this.$.component.is(selectionNode) &&
124
135
  (!dom.check.isTableElements(selectionNode) || dom.check.isTableCell(selectionNode)) &&
125
- this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine')) !== null
136
+ this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine')) !==
137
+ null
126
138
  ) {
127
139
  e.preventDefault();
128
140
  this.$.focusManager.focus();
@@ -134,7 +146,8 @@ export async function OnClick_wysiwyg(fc, e) {
134
146
  this.$.commandDispatcher._copyFormat();
135
147
 
136
148
  // Defer balloon toggle — selection range is finalized after mouseup event returns
137
- if (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) _w.setTimeout(this._toggleToolbarBalloon.bind(this), 0);
149
+ if (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon)
150
+ _w.setTimeout(this._toggleToolbarBalloon.bind(this), 0);
138
151
  }
139
152
 
140
153
  /**
@@ -144,14 +157,35 @@ export async function OnClick_wysiwyg(fc, e) {
144
157
  */
145
158
  export function OnMouseMove_wysiwyg(fc, e) {
146
159
  if (fc.get('isReadOnly') || fc.get('isDisabled')) return false;
147
- const eventTarget = dom.query.getEventTarget(e);
148
160
 
149
- // over component
150
- if (_DragHandle.get('__overInfo') !== false) {
151
- this.$.component.hoverSelect(eventTarget);
161
+ let s = __wwMousemoveRaf.get(fc);
162
+
163
+ if (!s) {
164
+ s = { rafId: null, event: null };
165
+ __wwMousemoveRaf.set(fc, s);
152
166
  }
153
167
 
154
- this._callPluginEvent('onMouseMove', { frameContext: fc, event: e });
168
+ s.event = e;
169
+ if (s.rafId !== null) return;
170
+
171
+ s.rafId = _w.requestAnimationFrame(() => {
172
+ const ev = s.event;
173
+ s.rafId = null;
174
+ s.event = null;
175
+ if (!ev) return;
176
+
177
+ const eventTarget = dom.query.getEventTarget(ev);
178
+
179
+ // over component — skip while select menu (block handle menu) is open
180
+ if (_DragHandle.get('__overInfo') !== false && !this.$.ui.selectMenuOn) {
181
+ this.$.component.hoverSelect(eventTarget);
182
+ }
183
+
184
+ // block handle
185
+ this.$.ui.blockHandle?.positionForTarget(eventTarget, ev.clientY);
186
+
187
+ this._callPluginEvent('onMouseMove', { frameContext: fc, event: ev });
188
+ });
155
189
  }
156
190
 
157
191
  /**
@@ -160,6 +194,9 @@ export function OnMouseMove_wysiwyg(fc, e) {
160
194
  * @param {MouseEvent} e - Event object
161
195
  */
162
196
  export async function OnMouseLeave_wysiwyg(fc, e) {
197
+ // block handle hide
198
+ this.$.ui.blockHandle?.hide(e);
199
+
163
200
  // user event
164
201
  if ((await this.$.eventManager.triggerEvent('onMouseLeave', { frameContext: fc, event: e })) === false) return;
165
202
  // plugin event