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,557 +1,618 @@
1
- import { _d, _w } from '../env';
2
- import check from './domCheck';
3
-
4
- /**
5
- * @template {Node} T
6
- * @description Clones a node while preserving its type.
7
- * @param {T} node - The node to clone.
8
- * @param {boolean} [deep=false] - Whether to perform a deep clone.
9
- * @returns {T} - The cloned node.
10
- */
11
- export function clone(node, deep = false) {
12
- return /** @type {T} */ (node.cloneNode(deep));
13
- }
14
-
15
- /**
16
- * @template {HTMLElement} T
17
- * @description Create Element node
18
- * @param {string} elementName Element name
19
- * @param {?Object<string, string>=} attributes The attributes of the tag. {style: 'font-size:12px;..', class: 'el_class',..}
20
- * @param {?string|Node=} inner A innerHTML string or inner node.
21
- * @returns {T}
22
- */
23
- export function createElement(elementName, attributes, inner) {
24
- const el = _d.createElement(elementName);
25
-
26
- if (attributes) {
27
- for (const key in attributes) {
28
- if (attributes[key] !== undefined && attributes[key] !== null) el.setAttribute(key, attributes[key]);
29
- }
30
- }
31
-
32
- if (inner) {
33
- if (typeof inner === 'string') {
34
- el.innerHTML = inner;
35
- } else if (typeof inner === 'object') {
36
- el.appendChild(inner);
37
- }
38
- }
39
-
40
- return /** @type {T} */ (el);
41
- }
42
-
43
- /**
44
- * @description Create text node
45
- * @param {string} text text content
46
- * @returns {Text}
47
- */
48
- export function createTextNode(text) {
49
- return _d.createTextNode(text || '');
50
- }
51
-
52
- /**
53
- * @description Get attributes of argument element to string ('class="---" name="---" ')
54
- * @param {Node} element Element object
55
- * @param {Array<string>|null} exceptAttrs Array of attribute names to exclude from the result
56
- * @returns {string}
57
- */
58
- export function getAttributesToString(element, exceptAttrs) {
59
- const attrs = /** @type {HTMLElement} */ (element).attributes;
60
- if (!attrs) return '';
61
-
62
- let attrString = '';
63
- for (let i = 0, len = attrs.length; i < len; i++) {
64
- if (exceptAttrs?.includes(attrs[i].name)) continue;
65
- attrString += attrs[i].name + '="' + attrs[i].value + '" ';
66
- }
67
-
68
- return attrString;
69
- }
70
-
71
- /**
72
- * @description Get the items array from the array that matches the condition.
73
- * @param {__se__NodeCollection} array Array to get item
74
- * @param {?(current: *) => boolean} validation Conditional function
75
- * @returns {Array<Node>|null}
76
- */
77
- export function arrayFilter(array, validation) {
78
- if (!array || array.length === 0) return null;
79
-
80
- validation =
81
- validation ||
82
- function () {
83
- return true;
84
- };
85
- const arr = [];
86
-
87
- for (let i = 0, len = array.length, a; i < len; i++) {
88
- a = array[i];
89
- if (validation(a)) {
90
- arr.push(a);
91
- }
92
- }
93
-
94
- return arr;
95
- }
96
-
97
- /**
98
- * @description Get the item from the array that matches the condition.
99
- * @param {__se__NodeCollection} array Array to get item
100
- * @param {?(current: *) => boolean} validation Conditional function
101
- * @returns {Node|null}
102
- */
103
- export function arrayFind(array, validation) {
104
- if (!array || array.length === 0) return null;
105
-
106
- validation =
107
- validation ||
108
- function () {
109
- return true;
110
- };
111
-
112
- for (let i = 0, len = array.length, a; i < len; i++) {
113
- a = array[i];
114
- if (validation(a)) {
115
- return a;
116
- }
117
- }
118
-
119
- return null;
120
- }
121
-
122
- /**
123
- * @description Check if an array contains an element
124
- * @param {__se__NodeCollection} array element array
125
- * @param {Node} node The node to check for
126
- * @returns {boolean}
127
- */
128
- export function arrayIncludes(array, node) {
129
- for (let i = 0; i < array.length; i++) {
130
- if (array[i] === node) {
131
- return true;
132
- }
133
- }
134
- return false;
135
- }
136
-
137
- /**
138
- * @description Get the index of the argument value in the element array
139
- * @param {__se__NodeCollection} array element array
140
- * @param {Node} node The element to find index
141
- * @returns {number}
142
- */
143
- export function getArrayIndex(array, node) {
144
- let idx = -1;
145
- for (let i = 0, len = array.length; i < len; i++) {
146
- if (array[i] === node) {
147
- idx = i;
148
- break;
149
- }
150
- }
151
-
152
- return idx;
153
- }
154
-
155
- /**
156
- * @description Get the next index of the argument value in the element array
157
- * @param {__se__NodeCollection} array element array
158
- * @param {Node} item The element to find index
159
- * @returns {number}
160
- */
161
- export function nextIndex(array, item) {
162
- const idx = getArrayIndex(array, item);
163
- if (idx === -1) return -1;
164
- return idx + 1;
165
- }
166
-
167
- /**
168
- * @description Get the previous index of the argument value in the element array
169
- * @param {__se__NodeCollection} array Element array
170
- * @param {Node} item The element to find index
171
- * @returns {number}
172
- */
173
- export function prevIndex(array, item) {
174
- const idx = getArrayIndex(array, item);
175
- if (idx === -1) return -1;
176
- return idx - 1;
177
- }
178
-
179
- /**
180
- * @description Add style and className of copyEl to originEl
181
- * @param {Node} originEl Origin element
182
- * @param {Node} copyEl Element to copy
183
- * @param {?Array<string>=} blacklist Blacklist array(LowerCase)
184
- */
185
- export function copyTagAttributes(originEl, copyEl, blacklist) {
186
- const o = /** @type {HTMLElement} */ (originEl);
187
- const c = /** @type {HTMLElement} */ (copyEl);
188
- if (c.style.cssText) {
189
- const copyStyles = c.style;
190
- for (let i = 0, len = copyStyles.length; i < len; i++) {
191
- o.style[copyStyles[i]] = copyStyles[copyStyles[i]];
192
- }
193
- }
194
-
195
- const attrs = c.attributes;
196
- for (let i = 0, len = attrs.length, name; i < len; i++) {
197
- name = attrs[i].name.toLowerCase();
198
- if (blacklist?.includes(name) || !attrs[i].value) o.removeAttribute(name);
199
- else if (name !== 'style') o.setAttribute(attrs[i].name, attrs[i].value);
200
- }
201
- }
202
-
203
- /**
204
- * @description Copy and apply attributes of format tag that should be maintained. (style, class) Ignore "__se__format__" class
205
- * @param {Node} originEl Origin element
206
- * @param {Node} copyEl Element to copy
207
- */
208
- export function copyFormatAttributes(originEl, copyEl) {
209
- const c = /** @type {HTMLElement} */ (copyEl.cloneNode(false));
210
- c.className = c.className.replace(/(\s|^)__se__format__[^\s]+/g, '');
211
- copyTagAttributes(originEl, c);
212
- }
213
-
214
- /**
215
- * @description Delete argumenu value element
216
- * @param {Node} item Node to be remove
217
- */
218
- export function removeItem(item) {
219
- if (!item) return;
220
- if ('remove' in item && typeof item.remove === 'function') item.remove();
221
- else if (item.parentNode) item.parentNode.removeChild(item);
222
- }
223
-
224
- /**
225
- * @description Replace element
226
- * @param {Node} element Target element
227
- * @param {string|Node} newElement String or element of the new element to apply
228
- */
229
- export function changeElement(element, newElement) {
230
- if (!element) return;
231
-
232
- if (typeof newElement === 'string') {
233
- if ('outerHTML' in element) {
234
- element.outerHTML = newElement;
235
- } else {
236
- const doc = createElement('DIV');
237
- doc.innerHTML = newElement;
238
- element.parentNode.replaceChild(doc.firstChild, element);
239
- }
240
- } else if (newElement?.nodeType === 1) {
241
- element.parentNode.replaceChild(newElement, element);
242
- }
243
- }
244
-
245
- /**
246
- * @description Set the text content value of the argument value element
247
- * @param {Node} node Element to replace text content
248
- * @param {string} txt Text to be applied
249
- */
250
- export function changeTxt(node, txt) {
251
- if (!node || !txt) return;
252
- node.textContent = txt;
253
- }
254
-
255
- /**
256
- * @description Set style, if all styles are deleted, the style properties are deleted.
257
- * @param {Node|Node[]} elements Element to set style
258
- * @param {string} styleName Style attribute name (marginLeft, textAlign...)
259
- * @param {string|number} value Style value
260
- */
261
- export function setStyle(elements, styleName, value) {
262
- elements = Array.isArray(elements) ? elements : [elements];
263
-
264
- for (let i = 0, len = elements.length, e; i < len; i++) {
265
- e = /** @type {HTMLElement} */ (elements[i]);
266
- e.style[styleName] = value;
267
- if (!value && !e.style.cssText) {
268
- e.removeAttribute('style');
269
- }
270
- }
271
- }
272
-
273
- /**
274
- * @description In the predefined code view mode, the buttons except the executable button are changed to the 'disabled' state.
275
- * @param {Array<HTMLButtonElement|HTMLInputElement>} buttonList (Button | Input) Element array
276
- * @param {boolean} disabled Disabled value
277
- * @param {boolean} [important=false] If priveleged mode should be used (Necessary to switch importantDisabled buttons)
278
- */
279
- export function setDisabled(buttonList, disabled, important) {
280
- for (let i = 0, len = buttonList.length; i < len; i++) {
281
- const button = buttonList[i];
282
- if (important || !check.isImportantDisabled(button)) button.disabled = disabled;
283
- if (important) {
284
- if (disabled) {
285
- button.setAttribute('data-important-disabled', '');
286
- } else {
287
- button.removeAttribute('data-important-disabled');
288
- }
289
- }
290
- }
291
- }
292
-
293
- /**
294
- * @description Determine whether any of the matched elements are assigned the given class
295
- * @param {?Node} element Elements to search class name
296
- * @param {string} className Class name to search for
297
- * @returns {boolean}
298
- */
299
- export function hasClass(element, className) {
300
- if (!element || element.nodeType !== 1) return;
301
- const valid = new RegExp(`(\\s|^)${className}(\\s|$)`);
302
- return valid.test(/** @type {HTMLElement} */ (element).className);
303
- }
304
-
305
- /**
306
- * @description Append the className value of the argument value element
307
- * @param {Node|__se__NodeCollection} element Elements to add class name
308
- * @param {string} className Class name to be add
309
- */
310
- export function addClass(element, className) {
311
- if (!element) return;
312
-
313
- const elements = element instanceof HTMLCollection || element instanceof NodeList || element instanceof Array ? element : [element];
314
- const classNames = className.split('|');
315
-
316
- for (let i = 0, len = elements.length; i < len; i++) {
317
- const e = elements[i];
318
- if (!e || e.nodeType !== 1) continue;
319
- for (const c of classNames) {
320
- if (c) /** @type {HTMLElement} */ (e).classList.add(c);
321
- }
322
- }
323
- }
324
-
325
- /**
326
- * @description Delete the className value of the argument value element
327
- * @param {Node|__se__NodeCollection} element Elements to remove class name
328
- * @param {string} className Class name to be remove
329
- */
330
- export function removeClass(element, className) {
331
- if (!element) return;
332
-
333
- const elements = element instanceof HTMLCollection || element instanceof NodeList || element instanceof Array ? element : [element];
334
- const classNames = className.split('|');
335
-
336
- for (let i = 0, len = elements.length; i < len; i++) {
337
- const e = elements[i];
338
- if (!e || e.nodeType !== 1) continue;
339
- for (const c of classNames) {
340
- if (c) /** @type {HTMLElement} */ (e).classList.remove(c);
341
- }
342
- }
343
- }
344
-
345
- /**
346
- * @description Argument value If there is no class name, insert it and delete the class name if it exists
347
- * @param {Node} element Element to replace class name
348
- * @param {string} className Class name to be change
349
- * @returns {boolean|undefined}
350
- */
351
- export function toggleClass(element, className) {
352
- if (!element || element.nodeType !== 1) return;
353
-
354
- const el = /** @type {HTMLElement} */ (element);
355
-
356
- let result = false;
357
- const valid = new RegExp(`(\\s|^)${className}(\\s|$)`);
358
- if (valid.test(el.className)) {
359
- el.className = el.className.replace(valid, ' ').trim();
360
- } else {
361
- el.className += ' ' + className;
362
- result = true;
363
- }
364
-
365
- if (!el.className.trim()) el.removeAttribute('class');
366
-
367
- return result;
368
- }
369
-
370
- /**
371
- * @description Flash the class name of the argument value element for a certain time
372
- * @param {Node} element Element to flash class name
373
- * @param {string} className class name
374
- * @param {number} [duration=120] duration milliseconds
375
- */
376
- export function flashClass(element, className, duration = 120) {
377
- addClass(element, className);
378
- _w.setTimeout(() => {
379
- removeClass(element, className);
380
- }, duration);
381
- }
382
-
383
- /**
384
- * @description Gets the size of the documentElement client size.
385
- * @param {Document} doc Document object
386
- * @returns {{w: number, h: number}} documentElement.clientWidth, documentElement.clientHeight
387
- */
388
- export function getClientSize(doc = _d) {
389
- return {
390
- w: doc.documentElement.clientWidth,
391
- h: doc.documentElement.clientHeight
392
- };
393
- }
394
-
395
- /**
396
- * @description Gets the size of the window visualViewport size
397
- * @returns {{top: number, left: number, scale: number}}
398
- */
399
- export function getViewportSize() {
400
- if ('visualViewport' in _w) {
401
- return {
402
- top: _w.visualViewport.pageTop,
403
- left: _w.visualViewport.pageLeft,
404
- scale: _w.visualViewport.scale
405
- };
406
- }
407
-
408
- return {
409
- top: 0,
410
- left: 0,
411
- scale: 1
412
- };
413
- }
414
-
415
- /**
416
- * @description Copies the "wwTarget" element and returns it with inline all styles applied.
417
- * @param {Node} wwTarget Target element to copy(.sun-editor.sun-editor-editable)
418
- * @param {boolean} includeWW Include the "wwTarget" element in the copy
419
- * @param {Array<string>} styles Style list - kamel case
420
- * @returns
421
- */
422
- export function applyInlineStylesAll(wwTarget, includeWW, styles) {
423
- if (!wwTarget) {
424
- console.warn('"parentTarget" is not exist');
425
- return null;
426
- }
427
-
428
- let ww = /** @type {HTMLElement} */ (wwTarget);
429
- const tempTarget = _d.createElement('DIV');
430
- tempTarget.style.display = 'none';
431
-
432
- if (/body/i.test(ww.nodeName)) {
433
- const wwDiv = _d.createElement('DIV');
434
- const attrs = ww.attributes;
435
- for (let i = 0, len = attrs.length; i < len; i++) {
436
- wwDiv.setAttribute(attrs[i].name, attrs[i].value);
437
- }
438
- wwDiv.innerHTML = ww.innerHTML;
439
- ww = wwDiv;
440
- } else {
441
- ww = /** @type {HTMLElement} */ (ww.cloneNode(true));
442
- }
443
-
444
- tempTarget.appendChild(ww);
445
- _d.body.appendChild(tempTarget);
446
-
447
- /** @type {HTMLElement[]} */
448
- const allElements = Array.from(ww.querySelectorAll('*'));
449
- const elements = includeWW ? [ww].concat(allElements) : allElements;
450
- for (let i = 0, el; (el = elements[i]); i++) {
451
- if (el.nodeType !== 1) continue;
452
- const computedStyle = _w.getComputedStyle(el);
453
- const els = el.style;
454
- for (const props of styles) {
455
- els.setProperty(props, computedStyle.getPropertyValue(props) || '');
456
- }
457
- }
458
-
459
- _d.body.removeChild(tempTarget);
460
-
461
- return ww;
462
- }
463
-
464
- /**
465
- * @description Wait for media elements to load
466
- * @param {Node} target Target element
467
- * @param {number} timeout Timeout milliseconds
468
- * @returns {Promise<void>}
469
- */
470
- export function waitForMediaLoad(target, timeout = 5000) {
471
- const doc = /** @type {HTMLElement|Document} */ (target || _d);
472
- return new Promise((resolveAll) => {
473
- const selectors = ['img', 'video', 'audio', 'iframe'];
474
- const mediaElements = selectors.flatMap((selector) => Array.from(doc.querySelectorAll(selector)));
475
-
476
- if (mediaElements.length === 0) {
477
- resolveAll();
478
- return;
479
- }
480
-
481
- const mediaPromises = mediaElements.map((element) => {
482
- // image
483
- if (element instanceof HTMLImageElement) {
484
- if (element.complete) {
485
- return Promise.resolve();
486
- }
487
- }
488
- // video, audio
489
- else if (element instanceof HTMLMediaElement) {
490
- if (element.readyState >= 2) {
491
- return Promise.resolve();
492
- }
493
- }
494
- // iframe
495
- else if (element instanceof HTMLIFrameElement) {
496
- try {
497
- if (element.contentDocument?.readyState === 'complete') {
498
- return Promise.resolve();
499
- }
500
- } catch (e) {
501
- console.warn(['[SUNEDITOR] Iframe load error', e]);
502
- }
503
- }
504
-
505
- // load event
506
- return new Promise((resolve) => {
507
- element.addEventListener('load', resolve, { once: true });
508
- element.addEventListener('error', resolve, { once: true });
509
- });
510
- });
511
-
512
- Promise.race([Promise.all(mediaPromises), new Promise((resolve) => _w.setTimeout(resolve, timeout))]).then(() => {
513
- resolveAll();
514
- });
515
- });
516
- }
517
-
518
- /**
519
- * @description Create tooltip HTML
520
- * @param {string} text Tooltip text
521
- * @returns {string} Tooltip HTML
522
- */
523
- export function createTooltipInner(text) {
524
- return `<span class="se-tooltip-inner"><span class="se-tooltip-text">${text}</span></span>`;
525
- }
526
-
527
- const utils = {
528
- clone,
529
- createElement,
530
- createTextNode,
531
- getAttributesToString,
532
- arrayFilter,
533
- arrayFind,
534
- arrayIncludes,
535
- getArrayIndex,
536
- nextIndex,
537
- prevIndex,
538
- copyTagAttributes,
539
- copyFormatAttributes,
540
- removeItem,
541
- changeElement,
542
- changeTxt,
543
- setStyle,
544
- setDisabled,
545
- hasClass,
546
- addClass,
547
- removeClass,
548
- toggleClass,
549
- flashClass,
550
- getClientSize,
551
- getViewportSize,
552
- applyInlineStylesAll,
553
- waitForMediaLoad,
554
- createTooltipInner
555
- };
556
-
557
- export default utils;
1
+ import { _d, _w } from '../env';
2
+ import check from './domCheck';
3
+
4
+ // ----- iframe-safe type check [START] -----
5
+ /**
6
+ * @private
7
+ * @description iframe-safe : Node type [HTMLCollection, NodeList, Array] check.
8
+ * @param {*} element
9
+ * @returns {element is HTMLCollection|NodeList|Array}
10
+ */
11
+ function IsElementArray(element) {
12
+ const type = Object.prototype.toString.call(element);
13
+ return type === '[object HTMLCollection]' || type === '[object NodeList]' || type === '[object Array]';
14
+ }
15
+
16
+ /**
17
+ * @private
18
+ * @description iframe-safe: check if element is an HTMLImageElement
19
+ * @param {*} element
20
+ * @returns {element is HTMLImageElement}
21
+ */
22
+ function IsHTMLImageElement(element) {
23
+ const type = Object.prototype.toString.call(element);
24
+ return type === '[object HTMLImageElement]';
25
+ }
26
+
27
+ /**
28
+ * @private
29
+ * @description iframe-safe: check if element is an HTMLMediaElement (video or audio)
30
+ * @param {*} element
31
+ * @returns {element is HTMLMediaElement}
32
+ */
33
+ function IsHTMLMediaElement(element) {
34
+ const type = Object.prototype.toString.call(element);
35
+ return type === '[object HTMLVideoElement]' || type === '[object HTMLAudioElement]';
36
+ }
37
+
38
+ /**
39
+ * @private
40
+ * @description iframe-safe: check if element is an HTMLIFrameElement
41
+ * @param {*} element
42
+ * @returns {element is HTMLIFrameElement}
43
+ */
44
+ function IsHTMLIFrameElement(element) {
45
+ const type = Object.prototype.toString.call(element);
46
+ return type === '[object HTMLIFrameElement]';
47
+ }
48
+ // ----- iframe-safe type check [END] -----
49
+
50
+ /**
51
+ * @template {Node} T
52
+ * @description Clones a node while preserving its type.
53
+ * @param {T} node - The node to clone.
54
+ * @param {boolean} [deep=false] - Whether to perform a deep clone.
55
+ * @returns {T} - The cloned node.
56
+ */
57
+ export function clone(node, deep = false) {
58
+ return /** @type {T} */ (node.cloneNode(deep));
59
+ }
60
+
61
+ /**
62
+ * @template {HTMLElement} T
63
+ * @description Create Element node
64
+ * @param {string} elementName Element name
65
+ * @param {?Object<string, string>=} attributes The attributes of the tag. {style: 'font-size:12px;..', class: 'el_class',..}
66
+ * @param {?string|Node=} inner A innerHTML string or inner node.
67
+ * @returns {T}
68
+ */
69
+ export function createElement(elementName, attributes, inner) {
70
+ const el = _d.createElement(elementName);
71
+
72
+ if (attributes) {
73
+ for (const key in attributes) {
74
+ if (attributes[key] !== undefined && attributes[key] !== null) el.setAttribute(key, attributes[key]);
75
+ }
76
+ }
77
+
78
+ if (inner) {
79
+ if (typeof inner === 'string') {
80
+ el.innerHTML = inner;
81
+ } else if (typeof inner === 'object') {
82
+ el.appendChild(inner);
83
+ }
84
+ }
85
+
86
+ return /** @type {T} */ (el);
87
+ }
88
+
89
+ /**
90
+ * @description Create text node
91
+ * @param {string} text text content
92
+ * @returns {Text}
93
+ */
94
+ export function createTextNode(text) {
95
+ return _d.createTextNode(text || '');
96
+ }
97
+
98
+ /**
99
+ * @description Get attributes of argument element to string ('class="---" name="---" ')
100
+ * @param {Node} element Element object
101
+ * @param {Array<string>|null} exceptAttrs Array of attribute names to exclude from the result
102
+ * @returns {string}
103
+ */
104
+ export function getAttributesToString(element, exceptAttrs) {
105
+ const attrs = /** @type {HTMLElement} */ (element).attributes;
106
+ if (!attrs) return '';
107
+
108
+ let attrString = '';
109
+ for (let i = 0, len = attrs.length; i < len; i++) {
110
+ if (exceptAttrs?.includes(attrs[i].name)) continue;
111
+ attrString += attrs[i].name + '="' + attrs[i].value + '" ';
112
+ }
113
+
114
+ return attrString;
115
+ }
116
+
117
+ /**
118
+ * @description Get the items array from the array that matches the condition.
119
+ * @param {__se__NodeCollection} array Array to get item
120
+ * @param {?(current: *) => boolean} validation Conditional function
121
+ * @returns {Array<Node>|null}
122
+ */
123
+ export function arrayFilter(array, validation) {
124
+ if (!array || array.length === 0) return null;
125
+
126
+ validation =
127
+ validation ||
128
+ function () {
129
+ return true;
130
+ };
131
+ const arr = [];
132
+
133
+ for (let i = 0, len = array.length, a; i < len; i++) {
134
+ a = array[i];
135
+ if (validation(a)) {
136
+ arr.push(a);
137
+ }
138
+ }
139
+
140
+ return arr;
141
+ }
142
+
143
+ /**
144
+ * @description Get the item from the array that matches the condition.
145
+ * @param {__se__NodeCollection} array Array to get item
146
+ * @param {?(current: *) => boolean} validation Conditional function
147
+ * @returns {Node|null}
148
+ */
149
+ export function arrayFind(array, validation) {
150
+ if (!array || array.length === 0) return null;
151
+
152
+ validation =
153
+ validation ||
154
+ function () {
155
+ return true;
156
+ };
157
+
158
+ for (let i = 0, len = array.length, a; i < len; i++) {
159
+ a = array[i];
160
+ if (validation(a)) {
161
+ return a;
162
+ }
163
+ }
164
+
165
+ return null;
166
+ }
167
+
168
+ /**
169
+ * @description Check if an array contains an element
170
+ * @param {__se__NodeCollection} array element array
171
+ * @param {Node} node The node to check for
172
+ * @returns {boolean}
173
+ */
174
+ export function arrayIncludes(array, node) {
175
+ for (let i = 0; i < array.length; i++) {
176
+ if (array[i] === node) {
177
+ return true;
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+
183
+ /**
184
+ * @description Get the index of the argument value in the element array
185
+ * @param {__se__NodeCollection} array element array
186
+ * @param {Node} node The element to find index
187
+ * @returns {number}
188
+ */
189
+ export function getArrayIndex(array, node) {
190
+ let idx = -1;
191
+ for (let i = 0, len = array.length; i < len; i++) {
192
+ if (array[i] === node) {
193
+ idx = i;
194
+ break;
195
+ }
196
+ }
197
+
198
+ return idx;
199
+ }
200
+
201
+ /**
202
+ * @description Get the next index of the argument value in the element array
203
+ * @param {__se__NodeCollection} array element array
204
+ * @param {Node} item The element to find index
205
+ * @returns {number}
206
+ */
207
+ export function nextIndex(array, item) {
208
+ const idx = getArrayIndex(array, item);
209
+ if (idx === -1) return -1;
210
+ return idx + 1;
211
+ }
212
+
213
+ /**
214
+ * @description Get the previous index of the argument value in the element array
215
+ * @param {__se__NodeCollection} array Element array
216
+ * @param {Node} item The element to find index
217
+ * @returns {number}
218
+ */
219
+ export function prevIndex(array, item) {
220
+ const idx = getArrayIndex(array, item);
221
+ if (idx === -1) return -1;
222
+ return idx - 1;
223
+ }
224
+
225
+ /**
226
+ * @description Add style and className of copyEl to originEl
227
+ * @param {Node} originEl Origin element
228
+ * @param {Node} copyEl Element to copy
229
+ * @param {?Array<string>=} blacklist Blacklist array(LowerCase)
230
+ */
231
+ export function copyTagAttributes(originEl, copyEl, blacklist) {
232
+ const o = /** @type {HTMLElement} */ (originEl);
233
+ const c = /** @type {HTMLElement} */ (copyEl);
234
+ if (c.style.cssText) {
235
+ const copyStyles = c.style;
236
+ for (let i = 0, len = copyStyles.length; i < len; i++) {
237
+ o.style[copyStyles[i]] = copyStyles[copyStyles[i]];
238
+ }
239
+ }
240
+
241
+ const attrs = c.attributes;
242
+ for (let i = 0, len = attrs.length, name; i < len; i++) {
243
+ name = attrs[i].name.toLowerCase();
244
+ if (blacklist?.includes(name) || !attrs[i].value) o.removeAttribute(name);
245
+ else if (name !== 'style') o.setAttribute(attrs[i].name, attrs[i].value);
246
+ }
247
+ }
248
+
249
+ /**
250
+ * @description Copy and apply attributes of format tag that should be maintained. (style, class) Ignore "__se__format__" class
251
+ * @param {Node} originEl Origin element
252
+ * @param {Node} copyEl Element to copy
253
+ */
254
+ export function copyFormatAttributes(originEl, copyEl) {
255
+ const c = /** @type {HTMLElement} */ (copyEl.cloneNode(false));
256
+ c.className = c.className.replace(/(\s|^)__se__format__[^\s]+/g, '');
257
+ copyTagAttributes(originEl, c);
258
+ }
259
+
260
+ /**
261
+ * @description Delete argumenu value element
262
+ * @param {Node} item Node to be remove
263
+ */
264
+ export function removeItem(item) {
265
+ if (!item) return;
266
+ if ('remove' in item && typeof item.remove === 'function') item.remove();
267
+ else if (item.parentNode) item.parentNode.removeChild(item);
268
+ }
269
+
270
+ /**
271
+ * @description Replace element
272
+ * @param {Node} element Target element
273
+ * @param {string|Node} newElement String or element of the new element to apply
274
+ */
275
+ export function changeElement(element, newElement) {
276
+ if (!element) return;
277
+
278
+ if (typeof newElement === 'string') {
279
+ if ('outerHTML' in element) {
280
+ element.outerHTML = newElement;
281
+ } else {
282
+ const doc = createElement('DIV');
283
+ doc.innerHTML = newElement;
284
+ element.parentNode.replaceChild(doc.firstChild, element);
285
+ }
286
+ } else if (newElement?.nodeType === 1) {
287
+ element.parentNode.replaceChild(newElement, element);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * @description Set the text content value of the argument value element
293
+ * @param {Node} node Element to replace text content
294
+ * @param {string} txt Text to be applied
295
+ */
296
+ export function changeTxt(node, txt) {
297
+ if (!node || !txt) return;
298
+ node.textContent = txt;
299
+ }
300
+
301
+ /**
302
+ * @description Set style, if all styles are deleted, the style properties are deleted.
303
+ * @param {Node|Node[]} elements Element to set style
304
+ * @param {string} styleName Style attribute name (marginLeft, textAlign...)
305
+ * @param {string|number} value Style value
306
+ */
307
+ export function setStyle(elements, styleName, value) {
308
+ elements = Array.isArray(elements) ? elements : [elements];
309
+
310
+ for (let i = 0, len = elements.length, e; i < len; i++) {
311
+ e = /** @type {HTMLElement} */ (elements[i]);
312
+ e.style[styleName] = value;
313
+ if (!e.style.cssText) {
314
+ e.removeAttribute('style');
315
+ }
316
+ }
317
+ }
318
+
319
+ /**
320
+ * @description Gets the style value of the element. If the elements is an array, the style of the first element is returned.
321
+ * @param {Node} element Element to get style from.
322
+ * @param {string} styleName Style attribute name (e.g., 'marginLeft', 'textAlign').
323
+ * @returns {string | undefined} The value of the style attribute, or undefined if the element does not exist.
324
+ */
325
+ export function getStyle(element, styleName) {
326
+ if (element?.nodeType !== 1) {
327
+ return undefined;
328
+ }
329
+
330
+ return /** @type {HTMLElement} */ (element).style[styleName];
331
+ }
332
+
333
+ /**
334
+ * @description In the predefined code view mode, the buttons except the executable button are changed to the 'disabled' state.
335
+ * @param {Array<HTMLButtonElement|HTMLInputElement>} buttonList (Button | Input) Element array
336
+ * @param {boolean} disabled Disabled value
337
+ * @param {boolean} [important=false] If priveleged mode should be used (Necessary to switch importantDisabled buttons)
338
+ */
339
+ export function setDisabled(buttonList, disabled, important) {
340
+ for (let i = 0, len = buttonList.length; i < len; i++) {
341
+ const button = buttonList[i];
342
+ if (important || !check.isImportantDisabled(button)) button.disabled = disabled;
343
+ if (important) {
344
+ if (disabled) {
345
+ button.setAttribute('data-important-disabled', '');
346
+ } else {
347
+ button.removeAttribute('data-important-disabled');
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ /**
354
+ * @description Determine whether any of the matched elements are assigned the given class
355
+ * @param {?Node} element Elements to search class name
356
+ * @param {string} className Class name to search for
357
+ * @returns {boolean}
358
+ */
359
+ export function hasClass(element, className) {
360
+ if (!element || element.nodeType !== 1) return;
361
+ const valid = new RegExp(`(\\s|^)${className}(\\s|$)`);
362
+ return valid.test(/** @type {HTMLElement} */ (element).className);
363
+ }
364
+
365
+ /**
366
+ * @description Append the className value of the argument value element
367
+ * @param {Node|__se__NodeCollection} element Elements to add class name
368
+ * @param {string} className Class name to be add
369
+ */
370
+ export function addClass(element, className) {
371
+ if (!element) return;
372
+
373
+ const elements = IsElementArray(element) ? element : [element];
374
+ const classNames = className.split('|');
375
+
376
+ for (let i = 0, len = elements.length; i < len; i++) {
377
+ const e = elements[i];
378
+ if (!e || e.nodeType !== 1) continue;
379
+ for (const c of classNames) {
380
+ if (c) /** @type {HTMLElement} */ (e).classList.add(c);
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * @description Delete the className value of the argument value element
387
+ * @param {Node|__se__NodeCollection} element Elements to remove class name
388
+ * @param {string} className Class name to be remove
389
+ */
390
+ export function removeClass(element, className) {
391
+ if (!element) return;
392
+
393
+ const elements = IsElementArray(element) ? element : [element];
394
+ const classNames = className.split('|');
395
+
396
+ for (let i = 0, len = elements.length; i < len; i++) {
397
+ const e = elements[i];
398
+ if (!e || e.nodeType !== 1) continue;
399
+ for (const c of classNames) {
400
+ if (c) /** @type {HTMLElement} */ (e).classList.remove(c);
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ * @description Argument value If there is no class name, insert it and delete the class name if it exists
407
+ * @param {Node} element Element to replace class name
408
+ * @param {string} className Class name to be change
409
+ * @returns {boolean|undefined}
410
+ */
411
+ export function toggleClass(element, className) {
412
+ if (!element || element.nodeType !== 1) return;
413
+
414
+ const el = /** @type {HTMLElement} */ (element);
415
+
416
+ let result = false;
417
+ const valid = new RegExp(`(\\s|^)${className}(\\s|$)`);
418
+ if (valid.test(el.className)) {
419
+ el.className = el.className.replace(valid, ' ').trim();
420
+ } else {
421
+ el.className += ' ' + className;
422
+ result = true;
423
+ }
424
+
425
+ if (!el.className.trim()) el.removeAttribute('class');
426
+
427
+ return result;
428
+ }
429
+
430
+ /**
431
+ * @description Flash the class name of the argument value element for a certain time
432
+ * @param {Node} element Element to flash class name
433
+ * @param {string} className class name
434
+ * @param {number} [duration=120] duration milliseconds
435
+ */
436
+ export function flashClass(element, className, duration = 120) {
437
+ addClass(element, className);
438
+ _w.setTimeout(() => {
439
+ removeClass(element, className);
440
+ }, duration);
441
+ }
442
+
443
+ /**
444
+ * @description Gets the size of the documentElement client size.
445
+ * @param {Document} doc Document object
446
+ * @returns {{w: number, h: number}} documentElement.clientWidth, documentElement.clientHeight
447
+ */
448
+ export function getClientSize(doc = _d) {
449
+ return {
450
+ w: doc.documentElement.clientWidth,
451
+ h: doc.documentElement.clientHeight
452
+ };
453
+ }
454
+
455
+ /**
456
+ * @description Gets the size of the window visualViewport size
457
+ * @returns {{top: number, left: number, scale: number}}
458
+ */
459
+ export function getViewportSize() {
460
+ if ('visualViewport' in _w) {
461
+ return {
462
+ top: _w.visualViewport.pageTop,
463
+ left: _w.visualViewport.pageLeft,
464
+ scale: _w.visualViewport.scale
465
+ };
466
+ }
467
+
468
+ return {
469
+ top: 0,
470
+ left: 0,
471
+ scale: 1
472
+ };
473
+ }
474
+
475
+ /**
476
+ * @description Copies the "wwTarget" element and returns it with inline all styles applied.
477
+ * @param {Node} wwTarget Target element to copy(.sun-editor.sun-editor-editable)
478
+ * @param {boolean} includeWW Include the "wwTarget" element in the copy
479
+ * @param {Array<string>} styles Style list - kamel case
480
+ * @returns
481
+ */
482
+ export function applyInlineStylesAll(wwTarget, includeWW, styles) {
483
+ if (!wwTarget) {
484
+ console.warn('"parentTarget" is not exist');
485
+ return null;
486
+ }
487
+
488
+ let ww = /** @type {HTMLElement} */ (wwTarget);
489
+ const tempTarget = _d.createElement('DIV');
490
+ tempTarget.style.display = 'none';
491
+
492
+ if (/body/i.test(ww.nodeName)) {
493
+ const wwDiv = _d.createElement('DIV');
494
+ const attrs = ww.attributes;
495
+ for (let i = 0, len = attrs.length; i < len; i++) {
496
+ wwDiv.setAttribute(attrs[i].name, attrs[i].value);
497
+ }
498
+ wwDiv.innerHTML = ww.innerHTML;
499
+ ww = wwDiv;
500
+ } else {
501
+ ww = /** @type {HTMLElement} */ (ww.cloneNode(true));
502
+ }
503
+
504
+ tempTarget.appendChild(ww);
505
+ _d.body.appendChild(tempTarget);
506
+
507
+ /** @type {HTMLElement[]} */
508
+ const allElements = Array.from(ww.querySelectorAll('*'));
509
+ const elements = includeWW ? [ww].concat(allElements) : allElements;
510
+ for (let i = 0, el; (el = elements[i]); i++) {
511
+ if (el.nodeType !== 1) continue;
512
+ const computedStyle = _w.getComputedStyle(el);
513
+ const els = el.style;
514
+ for (const props of styles) {
515
+ els.setProperty(props, computedStyle.getPropertyValue(props) || '');
516
+ }
517
+ }
518
+
519
+ _d.body.removeChild(tempTarget);
520
+
521
+ return ww;
522
+ }
523
+
524
+ /**
525
+ * @description Wait for media elements to load
526
+ * @param {Node} target Target element
527
+ * @param {number} timeout Timeout milliseconds
528
+ * @returns {Promise<void>}
529
+ */
530
+ export function waitForMediaLoad(target, timeout = 5000) {
531
+ const doc = /** @type {HTMLElement|Document} */ (target || _d);
532
+ return new Promise((resolveAll) => {
533
+ const selectors = ['img', 'video', 'audio', 'iframe'];
534
+ const mediaElements = selectors.flatMap((selector) => Array.from(doc.querySelectorAll(selector)));
535
+
536
+ if (mediaElements.length === 0) {
537
+ resolveAll();
538
+ return;
539
+ }
540
+
541
+ const mediaPromises = mediaElements.map((element) => {
542
+ // image
543
+ if (IsHTMLImageElement(element)) {
544
+ if (element.complete) {
545
+ return Promise.resolve();
546
+ }
547
+ }
548
+ // video, audio
549
+ else if (IsHTMLMediaElement(element)) {
550
+ if (element.readyState >= 2) {
551
+ return Promise.resolve();
552
+ }
553
+ }
554
+ // iframe
555
+ else if (IsHTMLIFrameElement(element)) {
556
+ try {
557
+ if (element.contentDocument?.readyState === 'complete') {
558
+ return Promise.resolve();
559
+ }
560
+ } catch (e) {
561
+ console.warn(['[SUNEDITOR] Iframe load error', e]);
562
+ }
563
+ }
564
+
565
+ // load event
566
+ return new Promise((resolve) => {
567
+ element.addEventListener('load', resolve, { once: true });
568
+ element.addEventListener('error', resolve, { once: true });
569
+ });
570
+ });
571
+
572
+ Promise.race([Promise.all(mediaPromises), new Promise((resolve) => _w.setTimeout(resolve, timeout))]).then(() => {
573
+ resolveAll();
574
+ });
575
+ });
576
+ }
577
+
578
+ /**
579
+ * @description Create tooltip HTML
580
+ * @param {string} text Tooltip text
581
+ * @returns {string} Tooltip HTML
582
+ */
583
+ export function createTooltipInner(text) {
584
+ return `<span class="se-tooltip-inner"><span class="se-tooltip-text">${text}</span></span>`;
585
+ }
586
+
587
+ const utils = {
588
+ clone,
589
+ createElement,
590
+ createTextNode,
591
+ getAttributesToString,
592
+ arrayFilter,
593
+ arrayFind,
594
+ arrayIncludes,
595
+ getArrayIndex,
596
+ nextIndex,
597
+ prevIndex,
598
+ copyTagAttributes,
599
+ copyFormatAttributes,
600
+ removeItem,
601
+ changeElement,
602
+ changeTxt,
603
+ setStyle,
604
+ getStyle,
605
+ setDisabled,
606
+ hasClass,
607
+ addClass,
608
+ removeClass,
609
+ toggleClass,
610
+ flashClass,
611
+ getClientSize,
612
+ getViewportSize,
613
+ applyInlineStylesAll,
614
+ waitForMediaLoad,
615
+ createTooltipInner
616
+ };
617
+
618
+ export default utils;