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,454 +1,474 @@
1
- import EditorInjector from '../editorInjector';
2
- import { dom, env, keyCodeMap } from '../helper';
3
- import { _DragHandle } from '../modules';
4
-
5
- const { _w, ON_OVER_COMPONENT } = env;
6
- const INDEX_00 = '2147483646';
7
- const INDEX_0 = '2147483645';
8
- const INDEX_S_1 = '2147483641';
9
- const INDEX_1 = '2147483640';
10
-
11
- /**
12
- * @typedef {Object} ControllerInfo
13
- * @property {*} inst The controller instance
14
- * @property {string} [position="bottom"] The controller position ("bottom"|"top")
15
- * @property {HTMLElement} [form=null] The controller element
16
- * @property {HTMLElement|Range} [target=null] The controller target element
17
- * @property {boolean} [notInCarrier=false] If the controller is not in the "carrierWrapper", set it to true.
18
- * @property {boolean} [isRangeTarget=false] If the target is a Range, set it to true.
19
- * @property {boolean} [fixed=false] If the controller is fixed and should not be closed, set it to true.
20
- */
21
-
22
- /**
23
- * @typedef {Object} ControllerParams
24
- * @property {"top"|"bottom"} [position="bottom"] Controller position
25
- * @property {boolean=} [isWWTarget=true] If the controller is in the WYSIWYG area, set it to true.
26
- * @property {() => void=} [initMethod=null] Method to be called when the controller is closed.
27
- * @property {boolean=} [disabled=false] If true, When the "controller" is opened, buttons without the "se-component-enabled" class are disabled.
28
- * @property {Array<HTMLElement>=} [parents=[]] The parent "controller" array when "controller" is opened nested.
29
- * @property {boolean=} [parentsHide=false] If true, the parent element is hidden when the controller is opened.
30
- * @property {HTMLElement=} [sibling=null] The related sibling controller element that this controller is positioned relative to.
31
- * @property {"top"|"side"} [siblingPosition="top"] The relative position of this controller to the sibling element (e.g., display above or beside the sibling).
32
- * @property {boolean=} [isInsideForm=false] If the controller is inside a form, set it to true.
33
- * @property {boolean=} [isOutsideForm=false] If the controller is outside a form, set it to true.
34
- */
35
-
36
- /**
37
- * @class
38
- * @description Controller module class that handles the UI and interaction logic for a specific editor controller element.
39
- */
40
- class Controller extends EditorInjector {
41
- /**
42
- * @constructor
43
- * @param {*} inst The instance object that called the constructor.
44
- * @param {Node} element Controller element
45
- * @param {ControllerParams} params Controller options
46
- * @param {?string=} _name An optional name for the controller key.
47
- */
48
- constructor(inst, element, params, _name) {
49
- super(inst.editor);
50
-
51
- // members
52
- this.kind = _name || inst.constructor.key || inst.constructor.name;
53
- this.inst = inst;
54
- this.form = /** @type {HTMLFormElement} */ (element);
55
- this.isOpen = false;
56
- this.currentTarget = null;
57
- this.currentPositionTarget = null;
58
- this.isWWTarget = params.isWWTarget ?? true;
59
- this.position = params.position || 'bottom';
60
- this.disabled = !!params.disabled;
61
- this.parents = /** @type {Array<HTMLElement>} */ (params.parents || []);
62
- this.parentsHide = !!params.parentsHide;
63
- this.sibling = /** @type {HTMLElement} */ (params.sibling || null);
64
- this.siblingPosition = ['top', 'side'].includes(params.siblingPosition) ? params.siblingPosition : 'top';
65
- this.isInsideForm = !!params.isInsideForm;
66
- this.isOutsideForm = !!params.isOutsideForm;
67
- this.toTop = false;
68
- this._reserveIndex = false;
69
- this._initMethod = typeof params.initMethod === 'function' ? params.initMethod : null;
70
- this.__globalEventHandlers = { keydown: this.#CloseListener_keydown.bind(this), mousedown: this.#CloseListener_mousedown.bind(this) };
71
- this._bindClose_key = null;
72
- this._bindClose_mouse = null;
73
- /** @type {{left?: number, top?: number, addOfffset?: {left?: number, top?: number}}} */
74
- this.__offset = {};
75
- this.__addOffset = { left: 0, top: 0 };
76
- this.__shadowRootEventForm = null;
77
- this.__shadowRootEventListener = null;
78
-
79
- // add element
80
- this.carrierWrapper.appendChild(element);
81
-
82
- // init
83
- this.eventManager.addEvent(element, 'click', this.#Action.bind(this));
84
- this.eventManager.addEvent(element, 'mouseenter', this.#MouseEnter.bind(this));
85
- this.eventManager.addEvent(element, 'mouseleave', this.#MouseLeave.bind(this));
86
- }
87
-
88
- /**
89
- * @description Open a modal plugin
90
- * @param {Node|Range} target Target element
91
- * @param {Node} [positionTarget] Position target element
92
- * @param {Object} [params={}] params
93
- * @param {boolean=} params.isWWTarget If the controller is in the WYSIWYG area, set it to true.
94
- * @param {() => void=} params.initMethod Method to be called when the controller is closed.
95
- * @param {boolean=} params.disabled If true, When the "controller" is opened, buttons without the "se-component-enabled" class are disabled. (default: this.disabled)
96
- * @param {{left?: number, top?: number}=} params.addOffset Additional offset values
97
- */
98
- open(target, positionTarget, { isWWTarget, initMethod, disabled, addOffset } = {}) {
99
- if (_DragHandle.get('__overInfo') === ON_OVER_COMPONENT) {
100
- return;
101
- }
102
-
103
- if (!target) {
104
- console.warn('[SUNEDITOR.Controller.open.fail] The target element is required.');
105
- return;
106
- }
107
-
108
- if (this.editor.isBalloon) this.toolbar.hide();
109
- else if (this.editor.isSubBalloon) this.subToolbar.hide();
110
-
111
- if (disabled ?? this.disabled) {
112
- this.ui.setControllerOnDisabledButtons(true);
113
- } else {
114
- this.ui.setControllerOnDisabledButtons(false);
115
- }
116
-
117
- this.currentPositionTarget = positionTarget || target;
118
- this.isWWTarget = isWWTarget ?? this.isWWTarget;
119
- if (typeof initMethod === 'function') this._initMethod = initMethod;
120
- this.editor.currentControllerName = this.kind;
121
-
122
- this.__addOffset = { left: 0, top: 0 };
123
- if (addOffset) this.__addOffset = { ...this.__addOffset, ...addOffset };
124
-
125
- const parents = this.isOutsideForm ? this.parents : [];
126
- this.editor.opendControllers?.forEach((e) => {
127
- if (!parents.includes(e.form)) e.form.style.zIndex = INDEX_1;
128
- });
129
-
130
- if (this.parentsHide) {
131
- this.parents.forEach((e) => {
132
- e.style.display = 'none';
133
- });
134
- }
135
-
136
- this.__addGlobalEvent();
137
-
138
- // add sibling offset
139
- if (this.sibling) {
140
- if (this.siblingPosition === 'top') {
141
- this.__addOffset.top += -this.sibling.offsetHeight + 1;
142
- } else {
143
- this.__addOffset.left += this.form.offsetWidth + this.sibling.offsetWidth - 1;
144
- }
145
- }
146
- // display controller
147
- this._setControllerPosition(this.form, this.currentPositionTarget, false);
148
-
149
- const isRangeTarget = target instanceof Range;
150
- this.currentTarget = isRangeTarget ? null : target;
151
- this._controllerOn(this.form, target, isRangeTarget);
152
- this._w.setTimeout(() => _DragHandle.set('__overInfo', false), 0);
153
- }
154
-
155
- /**
156
- * @description Close a modal plugin
157
- * - The plugin's "init" method is called.
158
- * @param {boolean=} force If true, parent controllers are forcibly closed.
159
- */
160
- close(force) {
161
- if (!this.isOpen) return;
162
-
163
- this.toTop = false;
164
- this.isOpen = false;
165
- this.__offset = {};
166
- this.__addOffset = { left: 0, top: 0 };
167
-
168
- this.__removeGlobalEvent();
169
-
170
- if (typeof this._initMethod === 'function') this._initMethod();
171
- this._controllerOff();
172
-
173
- if (this.parentsHide && !force) {
174
- this.parents.forEach((e) => {
175
- e.style.display = 'block';
176
- });
177
- }
178
-
179
- if (this.parents.length > 0) return;
180
- if (typeof this.inst.close === 'function') this.inst.close();
181
- this.component.deselect();
182
- }
183
-
184
- /**
185
- * @description Hide controller
186
- */
187
- hide() {
188
- this.form.style.display = 'none';
189
- }
190
-
191
- /**
192
- * @description Show controller
193
- */
194
- show() {
195
- this._setControllerPosition(this.form, this.currentPositionTarget, false);
196
- }
197
-
198
- /**
199
- * @description Sets whether the element (form) should be brought to the top based on z-index.
200
- * @param {boolean} value - true: '2147483646', false: '2147483645'.
201
- */
202
- bringToTop(value) {
203
- this.toTop = value;
204
- this.form.style.zIndex = value ? INDEX_00 : INDEX_0;
205
- }
206
-
207
- /**
208
- * @description Reset controller position
209
- * @param {Node=} target
210
- */
211
- resetPosition(target) {
212
- this._setControllerPosition(this.form, target || this.currentPositionTarget, true);
213
- }
214
-
215
- /**
216
- * @private
217
- * @description Show controller at editor area (controller elements, function, "controller target element(@Required)", "controller name(@Required)", etc..)
218
- * @param {HTMLFormElement} form Controller element
219
- * @param {Node|Range} target Controller target element
220
- * @param {boolean} isRangeTarget If the target is a Range, set it to true.
221
- */
222
- async _controllerOn(form, target, isRangeTarget) {
223
- /** @type {ControllerInfo} */
224
- const info = {
225
- position: this.position,
226
- inst: this,
227
- form: /** @type {HTMLElement} */ (form),
228
- target: /** @type {HTMLElement} */ (target),
229
- isRangeTarget,
230
- notInCarrier: !this.carrierWrapper.contains(form)
231
- };
232
-
233
- if ((await this.triggerEvent('onBeforeShowController', { caller: this.kind, frameContext: this.editor.frameContext, info })) === false) return;
234
-
235
- form.style.display = 'block';
236
- if (this._shadowRoot) {
237
- this.__shadowRootEventForm = form;
238
- this.__shadowRootEventListener = (e) => e.stopPropagation();
239
- form.addEventListener('mousedown', this.__shadowRootEventListener);
240
- }
241
-
242
- this.editor._controllerTargetContext = this.editor.frameContext.get('topArea');
243
-
244
- if (!this.isOpen) {
245
- this.editor.opendControllers.push(info);
246
- }
247
-
248
- this.isOpen = true;
249
- this.editor._preventBlur = true;
250
- this.editor.status.onSelected = true;
251
- this.triggerEvent('onShowController', { caller: this.kind, frameContext: this.editor.frameContext, info });
252
- }
253
-
254
- /**
255
- * @private
256
- * @description Hide controller at editor area (link button, image resize button..)
257
- */
258
- _controllerOff() {
259
- this.form.style.display = 'none';
260
- this.editor.opendControllers = this.editor.opendControllers.filter((v) => v.form !== this.form);
261
- if (this.editor.currentControllerName !== this.kind && this.editor.opendControllers.length > 0) return;
262
-
263
- this.ui.setControllerOnDisabledButtons(false);
264
-
265
- this.editor.frameContext.get('lineBreaker_t').style.display = this.editor.frameContext.get('lineBreaker_b').style.display = 'none';
266
- this.editor.effectNode = null;
267
- this.editor.currentControllerName = '';
268
- this.editor._preventBlur = false;
269
- this.editor._controllerTargetContext = null;
270
- _w.setTimeout(() => {
271
- this.editor.status.onSelected = false;
272
- }, 0);
273
- if (this.__shadowRootEventForm) {
274
- this.__shadowRootEventForm.removeEventListener('mousedown', this.__shadowRootEventListener);
275
- this.__shadowRootEventForm = this.__shadowRootEventListener = null;
276
- }
277
- if (typeof this.inst.reset === 'function') this.inst.reset();
278
- }
279
-
280
- /**
281
- * @private
282
- * @description Specify the position of the controller.
283
- * @param {HTMLElement} controller Controller element.
284
- * @param {Node|Range} refer Element or Range that is the basis of the controller's position.
285
- * @param {boolean} [skipAutoReposition=false] If true, skips scroll/resize-based automatic positioning logic.
286
- */
287
- _setControllerPosition(controller, refer, skipAutoReposition) {
288
- controller.style.visibility = 'hidden';
289
- controller.style.display = 'block';
290
-
291
- if (this.selection.isRange(refer)) {
292
- if (!this.offset.setRangePosition(this.form, /** @type {Range} */ (refer), { position: 'bottom' })) {
293
- this.hide();
294
- return;
295
- }
296
- } else {
297
- if (refer) {
298
- const positionResult = this.offset.setAbsPosition(controller, /** @type {HTMLElement} */ (refer), { addOffset: this.__addOffset, position: this.position, isWWTarget: this.isWWTarget, inst: this });
299
- if (!positionResult) {
300
- this.hide();
301
- return;
302
- }
303
-
304
- if (!skipAutoReposition && this.sibling && this.siblingPosition === 'top' && positionResult.position !== this.position) {
305
- const resetPosition = controller.offsetTop - this.__addOffset.top;
306
- if (positionResult.position === 'bottom') {
307
- this._reserveIndex = true;
308
- controller.style.top = resetPosition + this.sibling.offsetHeight - 2 + 'px';
309
- } else {
310
- controller.style.top = resetPosition - this.sibling.offsetHeight + 2 + 'px';
311
- }
312
- } else {
313
- this._reserveIndex = false;
314
- }
315
- }
316
- }
317
-
318
- controller.style.zIndex = this.toTop ? INDEX_0 : this._reserveIndex ? INDEX_S_1 : INDEX_1;
319
- controller.style.visibility = '';
320
- }
321
-
322
- /**
323
- * @private
324
- * @description Adds global event listeners.
325
- * - When the controller is opened
326
- */
327
- __addGlobalEvent() {
328
- this.__removeGlobalEvent();
329
- this._bindClose_key = this.eventManager.addGlobalEvent('keydown', this.__globalEventHandlers.keydown, true);
330
- this._bindClose_mouse = this.eventManager.addGlobalEvent('mousedown', this.__globalEventHandlers.mousedown, true);
331
- }
332
-
333
- /**
334
- * @private
335
- * @description Removes global event listeners.
336
- * - When the ESC key is pressed, the controller is closed.
337
- */
338
- __removeGlobalEvent() {
339
- this.component.__removeGlobalEvent();
340
- if (this._bindClose_key) this._bindClose_key = this.eventManager.removeGlobalEvent(this._bindClose_key);
341
- if (this._bindClose_mouse) this._bindClose_mouse = this.eventManager.removeGlobalEvent(this._bindClose_mouse);
342
- }
343
-
344
- /**
345
- * @private
346
- * @description Checks if the controller is fixed and should not be closed.
347
- * @returns {boolean} True if the controller is fixed.
348
- */
349
- _checkFixed() {
350
- if (this.editor.selectMenuOn) return true;
351
-
352
- const cont = this.editor.opendControllers;
353
- for (let i = 0; i < cont.length; i++) {
354
- if (cont[i].inst === this && cont[i].fixed) {
355
- return true;
356
- }
357
- }
358
- return false;
359
- }
360
-
361
- /**
362
- * @private
363
- * @description Checks if the given target is within a form or controller.
364
- * @param {Node} target The target element.
365
- * @returns {boolean} True if the target is inside a form or controller.
366
- */
367
- _checkForm(target) {
368
- if (dom.check.isWysiwygFrame(target)) return false;
369
- if (dom.utils.hasClass(target, 'se-drag-handle')) return true;
370
-
371
- let isParentForm = false;
372
- if (this.isInsideForm && this.parents?.length > 0) {
373
- this.parents.some((e) => {
374
- if (e.contains(target)) {
375
- isParentForm = true;
376
- return true;
377
- }
378
- });
379
- }
380
-
381
- return !isParentForm && (!!dom.query.getParentElement(target, '.se-controller') || target?.contains(this.inst._element));
382
- }
383
-
384
- /**
385
- * @param {MouseEvent} e - Event object
386
- */
387
- #Action(e) {
388
- const eventTarget = dom.query.getEventTarget(e);
389
- const target = dom.query.getCommandTarget(eventTarget);
390
- if (!target) return;
391
-
392
- e.stopPropagation();
393
- e.preventDefault();
394
-
395
- this.inst.controllerAction(target);
396
- }
397
-
398
- /**
399
- * @param {MouseEvent} e - Event object
400
- */
401
- #MouseEnter(e) {
402
- this.editor.currentControllerName = this.kind;
403
- if (this.parents.length > 0 && this.isInsideForm) return;
404
-
405
- const eventTarget = dom.query.getEventTarget(e);
406
- eventTarget.style.zIndex = this.toTop ? INDEX_00 : INDEX_0;
407
- }
408
-
409
- /**
410
- * @param {MouseEvent} e - Event object
411
- */
412
- #MouseLeave(e) {
413
- if (this.parents.length > 0 && this.isInsideForm) return;
414
-
415
- const eventTarget = dom.query.getEventTarget(e);
416
- eventTarget.style.zIndex = this.toTop ? INDEX_0 : this._reserveIndex ? INDEX_S_1 : INDEX_1;
417
- }
418
-
419
- /**
420
- * @param {KeyboardEvent} e - Event object
421
- */
422
- #CloseListener_keydown(e) {
423
- if (this._checkFixed()) return;
424
- const keyCode = e.code;
425
- const ctrl = keyCodeMap.isCtrl(e);
426
- if (ctrl || !keyCodeMap.isNonResponseKey(keyCode)) return;
427
-
428
- const eventTarget = dom.query.getEventTarget(e);
429
- if (this.form.contains(eventTarget) || this._checkForm(eventTarget)) return;
430
- if (this.editor._fileManager.pluginRegExp.test(this.kind) && !keyCodeMap.isEsc(keyCode)) return;
431
-
432
- this.close();
433
- }
434
-
435
- /**
436
- * @param {KeyboardEvent} e - Event object
437
- */
438
- #CloseListener_mousedown(e) {
439
- const eventTarget = dom.query.getEventTarget(e);
440
- if (this.inst?._element?.contains(eventTarget)) {
441
- this.isOpen = false;
442
- return;
443
- }
444
-
445
- this.isOpen = true;
446
- if (eventTarget === this.inst._element || eventTarget === this.currentTarget || this._checkFixed() || this.form.contains(eventTarget) || this._checkForm(eventTarget)) {
447
- return;
448
- }
449
-
450
- this.close(true);
451
- }
452
- }
453
-
454
- export default Controller;
1
+ import EditorInjector from '../editorInjector';
2
+ import { dom, env, keyCodeMap } from '../helper';
3
+ import { _DragHandle } from '../modules';
4
+
5
+ const { _w, isMobile, ON_OVER_COMPONENT } = env;
6
+ const INDEX_00 = '2147483646';
7
+ const INDEX_0 = '2147483645';
8
+ const INDEX_S_1 = '2147483641';
9
+ const INDEX_1 = '2147483640';
10
+
11
+ /**
12
+ * @typedef {Object} ControllerInfo
13
+ * @property {*} inst The controller instance
14
+ * @property {string} [position="bottom"] The controller position ("bottom"|"top")
15
+ * @property {HTMLElement} [form=null] The controller element
16
+ * @property {HTMLElement|Range} [target=null] The controller target element
17
+ * @property {boolean} [notInCarrier=false] If the controller is not in the "carrierWrapper", set it to true.
18
+ * @property {boolean} [isRangeTarget=false] If the target is a Range, set it to true.
19
+ * @property {boolean} [fixed=false] If the controller is fixed and should not be closed, set it to true.
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} ControllerParams
24
+ * @property {"top"|"bottom"} [position="bottom"] Controller position
25
+ * @property {boolean=} [isWWTarget=true] If the controller is in the WYSIWYG area, set it to true.
26
+ * @property {() => void=} [initMethod=null] Method to be called when the controller is closed.
27
+ * @property {boolean=} [disabled=false] If true, When the "controller" is opened, buttons without the "se-component-enabled" class are disabled.
28
+ * @property {Array<HTMLElement>=} [parents=[]] The parent "controller" array when "controller" is opened nested.
29
+ * @property {boolean=} [parentsHide=false] If true, the parent element is hidden when the controller is opened.
30
+ * @property {HTMLElement=} [sibling=null] The related sibling controller element that this controller is positioned relative to.
31
+ * - e.g.) table plugin :: 118
32
+ * @property {boolean=} [siblingMain=false] If true, This sibling controller is the main controller.
33
+ * - You must specify this option, if use "sibling"
34
+ * @property {boolean=} [isInsideForm=false] If the controller is inside a form, set it to true.
35
+ * @property {boolean=} [isOutsideForm=false] If the controller is outside a form, set it to true.
36
+ */
37
+
38
+ /**
39
+ * @class
40
+ * @description Controller module class that handles the UI and interaction logic for a specific editor controller element.
41
+ */
42
+ class Controller extends EditorInjector {
43
+ /**
44
+ * @constructor
45
+ * @param {*} inst The instance object that called the constructor.
46
+ * @param {Node} element Controller element
47
+ * @param {ControllerParams} params Controller options
48
+ * @param {?string=} _name An optional name for the controller key.
49
+ */
50
+ constructor(inst, element, params, _name) {
51
+ super(inst.editor);
52
+
53
+ // members
54
+ this.kind = _name || inst.constructor.key || inst.constructor.name;
55
+ this.inst = inst;
56
+ this.form = /** @type {HTMLFormElement} */ (element);
57
+ this.isOpen = false;
58
+ this.currentTarget = null;
59
+ this.currentPositionTarget = null;
60
+ this.isWWTarget = params.isWWTarget ?? true;
61
+ this.position = params.position || 'bottom';
62
+ this.disabled = !!params.disabled;
63
+ this.parents = /** @type {Array<HTMLElement>} */ (params.parents || []);
64
+ this.parentsHide = !!params.parentsHide;
65
+ this.sibling = /** @type {HTMLElement} */ (params.sibling || null);
66
+ this.siblingMain = !!params.siblingMain;
67
+ this.isInsideForm = !!params.isInsideForm;
68
+ this.isOutsideForm = !!params.isOutsideForm;
69
+ this.toTop = false;
70
+ this._reserveIndex = false;
71
+ this._initMethod = typeof params.initMethod === 'function' ? params.initMethod : null;
72
+ this.__globalEventHandlers = { keydown: this.#CloseListener_keydown.bind(this), mousedown: this.#CloseListener_mousedown.bind(this) };
73
+ this._bindClose_key = null;
74
+ this._bindClose_mouse = null;
75
+ /** @type {{left?: number, top?: number, addOfffset?: {left?: number, top?: number}}} */
76
+ this.__offset = {};
77
+ this.__addOffset = { left: 0, top: 0 };
78
+ this.__shadowRootEventForm = null;
79
+ this.__shadowRootEventListener = null;
80
+
81
+ // add element
82
+ this.carrierWrapper.appendChild(element);
83
+
84
+ // init
85
+ this.eventManager.addEvent(element, 'click', this.#Action.bind(this));
86
+ this.eventManager.addEvent(element, 'mouseenter', this.#MouseEnter.bind(this));
87
+ this.eventManager.addEvent(element, 'mouseleave', this.#MouseLeave.bind(this));
88
+ }
89
+
90
+ /**
91
+ * @description Open a modal plugin
92
+ * @param {Node|Range} target Target element
93
+ * @param {Node} [positionTarget] Position target element
94
+ * @param {Object} [params={}] params
95
+ * @param {boolean=} params.isWWTarget If the controller is in the WYSIWYG area, set it to true.
96
+ * @param {() => void=} params.initMethod Method to be called when the controller is closed.
97
+ * @param {boolean=} params.disabled If true, When the "controller" is opened, buttons without the "se-component-enabled" class are disabled. (default: this.disabled)
98
+ * @param {{left?: number, top?: number}=} params.addOffset Additional offset values
99
+ */
100
+ open(target, positionTarget, { isWWTarget, initMethod, disabled, addOffset } = {}) {
101
+ if (_DragHandle.get('__overInfo') === ON_OVER_COMPONENT) {
102
+ return;
103
+ }
104
+
105
+ if (!target) {
106
+ console.warn('[SUNEDITOR.Controller.open.fail] The target element is required.');
107
+ return;
108
+ }
109
+
110
+ if (this.editor.isBalloon) this.toolbar.hide();
111
+ else if (this.editor.isSubBalloon) this.subToolbar.hide();
112
+
113
+ if (!this.status.hasFocus) {
114
+ if (disabled ?? this.disabled) {
115
+ this.ui.setControllerOnDisabledButtons(true);
116
+ } else {
117
+ this.ui.setControllerOnDisabledButtons(false);
118
+ }
119
+ }
120
+
121
+ this.currentPositionTarget = positionTarget || target;
122
+ this.isWWTarget = isWWTarget ?? this.isWWTarget;
123
+ if (typeof initMethod === 'function') this._initMethod = initMethod;
124
+ this.editor.currentControllerName = this.kind;
125
+
126
+ this.__addOffset = { left: 0, top: 0 };
127
+ if (addOffset) this.__addOffset = { ...this.__addOffset, ...addOffset };
128
+
129
+ const parents = this.isOutsideForm ? this.parents : [];
130
+ this.editor.opendControllers?.forEach((e) => {
131
+ if (!parents.includes(e.form)) e.form.style.zIndex = INDEX_1;
132
+ });
133
+
134
+ if (this.parentsHide) {
135
+ this.parents.forEach((e) => {
136
+ e.style.display = 'none';
137
+ });
138
+ }
139
+
140
+ this.__addGlobalEvent();
141
+
142
+ // display controller
143
+ this._setControllerPosition(this.form, this.currentPositionTarget, false);
144
+
145
+ const isRangeTarget = this.instanceCheck.isRange(target);
146
+ this.currentTarget = isRangeTarget ? null : target;
147
+ this._controllerOn(this.form, target, isRangeTarget);
148
+ this._w.setTimeout(() => _DragHandle.set('__overInfo', false), 0);
149
+ }
150
+
151
+ /**
152
+ * @description Close a modal plugin
153
+ * - The plugin's "init" method is called.
154
+ * @param {boolean=} force If true, parent controllers are forcibly closed.
155
+ */
156
+ close(force) {
157
+ if (!this.isOpen) return;
158
+
159
+ this.toTop = false;
160
+ this.isOpen = false;
161
+ this.__offset = {};
162
+ this.__addOffset = { left: 0, top: 0 };
163
+
164
+ this.__removeGlobalEvent();
165
+
166
+ if (typeof this._initMethod === 'function') this._initMethod();
167
+ this._controllerOff();
168
+
169
+ if (this.parentsHide && !force) {
170
+ this.parents.forEach((e) => {
171
+ e.style.display = 'block';
172
+ });
173
+ }
174
+
175
+ if (this.parents.length > 0) return;
176
+ if (typeof this.inst.close === 'function') this.inst.close();
177
+ this.component.deselect();
178
+ }
179
+
180
+ /**
181
+ * @description Hide controller
182
+ */
183
+ hide() {
184
+ this.form.style.display = 'none';
185
+ }
186
+
187
+ /**
188
+ * @description Show controller
189
+ */
190
+ show() {
191
+ this._setControllerPosition(this.form, this.currentPositionTarget, false);
192
+ }
193
+
194
+ /**
195
+ * @description Sets whether the element (form) should be brought to the top based on z-index.
196
+ * @param {boolean} value - true: '2147483646', false: '2147483645'.
197
+ */
198
+ bringToTop(value) {
199
+ this.toTop = value;
200
+ this.form.style.zIndex = value ? INDEX_00 : INDEX_0;
201
+ }
202
+
203
+ /**
204
+ * @description Reset controller position
205
+ * @param {Node=} target
206
+ */
207
+ resetPosition(target) {
208
+ this._setControllerPosition(this.form, target || this.currentPositionTarget, true);
209
+ }
210
+
211
+ /**
212
+ * @private
213
+ * @description Show controller at editor area (controller elements, function, "controller target element(@Required)", "controller name(@Required)", etc..)
214
+ * @param {HTMLFormElement} form Controller element
215
+ * @param {Node|Range} target Controller target element
216
+ * @param {boolean} isRangeTarget If the target is a Range, set it to true.
217
+ */
218
+ async _controllerOn(form, target, isRangeTarget) {
219
+ /** @type {ControllerInfo} */
220
+ const info = {
221
+ position: this.position,
222
+ inst: this,
223
+ form: /** @type {HTMLElement} */ (form),
224
+ target: /** @type {HTMLElement} */ (target),
225
+ isRangeTarget,
226
+ notInCarrier: !this.carrierWrapper.contains(form)
227
+ };
228
+
229
+ if ((await this.triggerEvent('onBeforeShowController', { caller: this.kind, frameContext: this.editor.frameContext, info })) === false) return;
230
+
231
+ form.style.display = 'block';
232
+ if (this.editor._shadowRoot) {
233
+ this.__shadowRootEventForm = form;
234
+ this.__shadowRootEventListener = (e) => e.stopPropagation();
235
+ form.addEventListener('mousedown', this.__shadowRootEventListener);
236
+ }
237
+
238
+ this.editor._controllerTargetContext = this.editor.frameContext.get('topArea');
239
+
240
+ if (!this.isOpen) {
241
+ this.editor.opendControllers.push(info);
242
+ }
243
+
244
+ this.isOpen = true;
245
+ this.editor._preventBlur = true;
246
+ this.editor.status.onSelected = true;
247
+ this.triggerEvent('onShowController', { caller: this.kind, frameContext: this.editor.frameContext, info });
248
+ }
249
+
250
+ /**
251
+ * @private
252
+ * @description Hide controller at editor area (link button, image resize button..)
253
+ */
254
+ _controllerOff() {
255
+ this.form.style.display = 'none';
256
+ this.editor.opendControllers = this.editor.opendControllers.filter((v) => v.form !== this.form);
257
+ if (this.editor.currentControllerName !== this.kind && this.editor.opendControllers.length > 0) return;
258
+
259
+ this.ui.setControllerOnDisabledButtons(false);
260
+
261
+ this.editor.frameContext.get('lineBreaker_t').style.display = this.editor.frameContext.get('lineBreaker_b').style.display = 'none';
262
+ this.editor.effectNode = null;
263
+ this.editor.currentControllerName = '';
264
+ this.editor._preventBlur = false;
265
+ this.editor._controllerTargetContext = null;
266
+ _w.setTimeout(() => {
267
+ this.editor.status.onSelected = false;
268
+ }, 0);
269
+ if (this.__shadowRootEventForm) {
270
+ this.__shadowRootEventForm.removeEventListener('mousedown', this.__shadowRootEventListener);
271
+ this.__shadowRootEventForm = this.__shadowRootEventListener = null;
272
+ }
273
+ if (typeof this.inst.reset === 'function') this.inst.reset();
274
+ }
275
+
276
+ /**
277
+ * @private
278
+ * @description Specify the position of the controller.
279
+ * @param {HTMLElement} controller Controller element.
280
+ * @param {Node|Range} refer Element or Range that is the basis of the controller's position.
281
+ * @param {boolean} [skipAutoReposition=false] If true, skips scroll/resize-based automatic positioning logic.
282
+ */
283
+ _setControllerPosition(controller, refer, skipAutoReposition) {
284
+ controller.style.visibility = 'hidden';
285
+ controller.style.display = 'block';
286
+
287
+ if (this.sibling && this.sibling.style.display !== 'block') {
288
+ this.sibling.style.visibility = 'hidden';
289
+ this.sibling.style.display = 'block';
290
+ }
291
+
292
+ if (this.selection.isRange(refer)) {
293
+ if (!this.offset.setRangePosition(this.form, /** @type {Range} */ (refer), { position: 'bottom' })) {
294
+ this.hide();
295
+ return;
296
+ }
297
+ } else {
298
+ if (refer) {
299
+ const positionResult = this.offset.setAbsPosition(controller, /** @type {HTMLElement} */ (refer), { addOffset: this.__addOffset, position: this.position, isWWTarget: this.isWWTarget, inst: this, sibling: this.sibling });
300
+ if (!positionResult) {
301
+ this.hide();
302
+ return;
303
+ }
304
+
305
+ if (!skipAutoReposition && this.sibling && !this.siblingMain) {
306
+ const resetPosition = controller.offsetTop - this.__addOffset.top;
307
+ if (positionResult.position === 'bottom') {
308
+ this._reserveIndex = true;
309
+ controller.style.top = resetPosition + this.sibling.offsetHeight - 1 + 'px';
310
+ } else {
311
+ this._reserveIndex = false;
312
+ controller.style.top = resetPosition - this.sibling.offsetHeight + 2 + 'px';
313
+ }
314
+ } else {
315
+ this._reserveIndex = false;
316
+ }
317
+ }
318
+ }
319
+
320
+ controller.style.zIndex = this.toTop ? INDEX_0 : this._reserveIndex ? INDEX_S_1 : INDEX_1;
321
+ controller.style.visibility = '';
322
+ }
323
+
324
+ /**
325
+ * @private
326
+ * @description Adds global event listeners.
327
+ * - When the controller is opened
328
+ */
329
+ __addGlobalEvent() {
330
+ this.__removeGlobalEvent();
331
+ this._bindClose_key = this.eventManager.addGlobalEvent('keydown', this.__globalEventHandlers.keydown, true);
332
+ this._bindClose_mouse = this.eventManager.addGlobalEvent(isMobile ? 'click' : 'mousedown', this.__globalEventHandlers.mousedown, true);
333
+ }
334
+
335
+ /**
336
+ * @private
337
+ * @description Removes global event listeners.
338
+ * - When the ESC key is pressed, the controller is closed.
339
+ */
340
+ __removeGlobalEvent() {
341
+ this.component.__removeGlobalEvent();
342
+ if (this._bindClose_key) this._bindClose_key = this.eventManager.removeGlobalEvent(this._bindClose_key);
343
+ if (this._bindClose_mouse) this._bindClose_mouse = this.eventManager.removeGlobalEvent(this._bindClose_mouse);
344
+ }
345
+
346
+ /**
347
+ * @private
348
+ * @description Checks if the controller is fixed and should not be closed.
349
+ * @returns {boolean} True if the controller is fixed.
350
+ */
351
+ _checkFixed() {
352
+ if (this.editor.selectMenuOn) return true;
353
+
354
+ const cont = this.editor.opendControllers;
355
+ for (let i = 0; i < cont.length; i++) {
356
+ if (cont[i].inst === this && cont[i].fixed) {
357
+ return true;
358
+ }
359
+ }
360
+ return false;
361
+ }
362
+
363
+ /**
364
+ * @private
365
+ * @description Checks if the given target is within a form or controller.
366
+ * @param {Node} target The target element.
367
+ * @returns {boolean} True if the target is inside a form or controller.
368
+ */
369
+ _checkForm(target) {
370
+ if (dom.check.isWysiwygFrame(target)) return false;
371
+ if (dom.utils.hasClass(target, 'se-drag-handle')) return true;
372
+
373
+ let isParentForm = false;
374
+ if (this.isInsideForm && this.parents?.length > 0) {
375
+ this.parents.some((e) => {
376
+ if (e.contains(target)) {
377
+ isParentForm = true;
378
+ return true;
379
+ }
380
+ });
381
+ }
382
+
383
+ return !isParentForm && (!!dom.query.getParentElement(target, '.se-controller') || target?.contains(this.inst._element));
384
+ }
385
+
386
+ /**
387
+ * @param {MouseEvent} e - Event object
388
+ */
389
+ #Action(e) {
390
+ const eventTarget = dom.query.getEventTarget(e);
391
+ const target = dom.query.getCommandTarget(eventTarget);
392
+ if (!target) return;
393
+
394
+ e.stopPropagation();
395
+ e.preventDefault();
396
+
397
+ this.inst.controllerAction(target);
398
+ }
399
+
400
+ /**
401
+ * @param {MouseEvent} e - Event object
402
+ */
403
+ #MouseEnter(e) {
404
+ this.editor.currentControllerName = this.kind;
405
+ if (this.parents.length > 0 && this.isInsideForm) return;
406
+
407
+ const eventTarget = dom.query.getEventTarget(e);
408
+ eventTarget.style.zIndex = this.toTop ? INDEX_00 : INDEX_0;
409
+ }
410
+
411
+ /**
412
+ * @param {MouseEvent} e - Event object
413
+ */
414
+ #MouseLeave(e) {
415
+ if (this.parents.length > 0 && this.isInsideForm) return;
416
+
417
+ const eventTarget = dom.query.getEventTarget(e);
418
+ eventTarget.style.zIndex = this.toTop ? INDEX_0 : this._reserveIndex ? INDEX_S_1 : INDEX_1;
419
+ }
420
+
421
+ /**
422
+ * @param {KeyboardEvent} e - Event object
423
+ */
424
+ #CloseListener_keydown(e) {
425
+ if (this._checkFixed()) return;
426
+ const keyCode = e.code;
427
+ const ctrl = keyCodeMap.isCtrl(e);
428
+ if (ctrl || !keyCodeMap.isNonResponseKey(keyCode)) return;
429
+
430
+ const eventTarget = dom.query.getEventTarget(e);
431
+ if (this.form.contains(eventTarget) || this._checkForm(eventTarget)) return;
432
+ if (this.editor._fileManager.pluginRegExp.test(this.kind) && !keyCodeMap.isEsc(keyCode)) return;
433
+
434
+ this.#PostCloseEvent(eventTarget);
435
+ this.close();
436
+ }
437
+
438
+ /**
439
+ * @param {KeyboardEvent} e - Event object
440
+ */
441
+ #CloseListener_mousedown(e) {
442
+ const eventTarget = dom.query.getEventTarget(e);
443
+ if (this.inst?._element?.contains(eventTarget)) {
444
+ this.isOpen = false;
445
+ return;
446
+ }
447
+
448
+ this.isOpen = true;
449
+ if (
450
+ eventTarget === this.inst._element ||
451
+ eventTarget === this.currentTarget ||
452
+ this._checkFixed() ||
453
+ this.form.contains(eventTarget) ||
454
+ this._checkForm(eventTarget) ||
455
+ dom.query.getParentElement(eventTarget, '.se-line-breaker-component')
456
+ ) {
457
+ return;
458
+ }
459
+
460
+ this.#PostCloseEvent(eventTarget);
461
+ this.close(true);
462
+ }
463
+
464
+ /**
465
+ * @param {HTMLElement} eventTarget - The target element that triggered the event.
466
+ */
467
+ #PostCloseEvent(eventTarget) {
468
+ if (!this.editor.frameContext.get('wysiwyg').contains(eventTarget)) {
469
+ this.component.__prevent = false;
470
+ }
471
+ }
472
+ }
473
+
474
+ export default Controller;