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,620 +1,710 @@
1
- /**
2
- * @fileoverview Selection class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { dom, unicode, env } from '../../helper';
7
-
8
- /**
9
- * @typedef {Omit<Selection_ & Partial<__se__EditorInjector>, 'selection'>} SelectionThis
10
- */
11
-
12
- /**
13
- * @typedef {import('./offset').RectsInfo} RectsInfo_selection
14
- */
15
-
16
- /**
17
- * @constructor
18
- * @this {SelectionThis}
19
- * @description Selection, Range related class
20
- * @param {__se__EditorCore} editor - The root editor instance
21
- */
22
- function Selection_(editor) {
23
- CoreInjector.call(this, editor);
24
-
25
- // members
26
- /** @type {Range} */
27
- this.range = null;
28
- /** @type {HTMLElement|Text} */
29
- this.selectionNode = null;
30
- this.__iframeFocus = false;
31
- }
32
-
33
- Selection_.prototype = {
34
- /**
35
- * @this {SelectionThis}
36
- * @description Get window selection obejct
37
- * @returns {Selection}
38
- */
39
- get() {
40
- const wSelection = this.editor.frameContext.get('_ww').getSelection();
41
- let selection = null;
42
-
43
- if (this._shadowRoot) {
44
- selection = wSelection.getComposedRanges?.();
45
- }
46
-
47
- if (!selection) {
48
- selection = wSelection;
49
- }
50
-
51
- if (!selection) return null;
52
- if (!this.status._range && !this.editor.frameContext.get('wysiwyg').contains(selection.focusNode)) {
53
- selection.removeAllRanges();
54
- selection.addRange(this._createDefaultRange());
55
- }
56
- return selection;
57
- },
58
-
59
- /**
60
- * @this {SelectionThis}
61
- * @description Check if the range object is valid
62
- * @param {*} range Range object
63
- * @returns {boolean}
64
- */
65
- isRange(range) {
66
- // return /Range/.test(Object.prototype.toString.call(range?.__proto__));
67
- return range instanceof Range;
68
- },
69
-
70
- /**
71
- * @this {SelectionThis}
72
- * @description Get current editor's range object
73
- * @returns {Range}
74
- */
75
- getRange() {
76
- const range = this.status._range || this._createDefaultRange();
77
- const selection = this.get();
78
- if (range.collapsed === selection.isCollapsed || !this.editor.frameContext.get('wysiwyg').contains(selection.focusNode)) {
79
- if (this.component.is(range.startContainer)) {
80
- const compInfo = this.component.get(range.startContainer);
81
- const container = compInfo?.container;
82
- if (!container) return range;
83
- return this.setRange(container, 0, container, 1);
84
- }
85
-
86
- return range;
87
- }
88
-
89
- if (selection.rangeCount > 0) {
90
- this.status._range = selection.getRangeAt(0);
91
- return this.status._range;
92
- } else {
93
- const sc = selection.anchorNode,
94
- ec = selection.focusNode,
95
- so = selection.anchorOffset,
96
- eo = selection.focusOffset;
97
- const compareValue = dom.query.compareElements(sc, ec);
98
- const rightDir = compareValue.ancestor && (compareValue.result === 0 ? so <= eo : compareValue.result > 1 ? true : false);
99
- return this.setRange(rightDir ? sc : ec, rightDir ? so : eo, rightDir ? ec : sc, rightDir ? eo : so);
100
- }
101
- },
102
-
103
- /**
104
- * @this {SelectionThis}
105
- * @description Set current editor's range object and return.
106
- * @param {Node|Range} startCon Range object or The startContainer property of the selection object
107
- * @param {number} [startOff] The startOffset property of the selection object.
108
- * @param {Node} [endCon] The endContainer property of the selection object.
109
- * @param {number} [endOff] The endOffset property of the selection object.
110
- * @returns {Range}
111
- */
112
- setRange(startCon, startOff, endCon, endOff) {
113
- /** @type {Node} */
114
- let sc;
115
- /** @type {number} */
116
- let so;
117
- /** @type {Node} */
118
- let ec;
119
- /** @type {number} */
120
- let eo;
121
-
122
- if (this.isRange(startCon)) {
123
- const r = /** @type {Range} */ (startCon);
124
- sc = r.startContainer;
125
- so = r.startOffset;
126
- ec = r.endContainer;
127
- eo = r.endOffset;
128
- } else {
129
- sc = /** @type {Node} */ (startCon);
130
- so = startOff;
131
- ec = endCon;
132
- eo = endOff;
133
- }
134
-
135
- if (!sc || !ec) return;
136
- if ((dom.check.isBreak(sc) || sc.nodeType === 3) && so > sc.textContent.length) so = sc.textContent.length;
137
- if ((dom.check.isBreak(ec) || ec.nodeType === 3) && eo > ec.textContent.length) eo = ec.textContent.length;
138
- if (this.format.isLine(sc)) {
139
- sc = sc.childNodes[so > 0 ? sc.childNodes.length - 1 : 0] || sc;
140
- so = so > 0 ? (sc.nodeType === 1 && !dom.check.isBreak(sc) ? 1 : sc.textContent ? sc.textContent.length : 0) : 0;
141
- }
142
- if (this.format.isLine(ec)) {
143
- ec = ec.childNodes[eo > 0 ? ec.childNodes.length - 1 : 0] || ec;
144
- eo = eo > 0 ? (ec.nodeType === 1 && !dom.check.isBreak(ec) ? 1 : ec.textContent ? ec.textContent.length : 0) : 0;
145
- }
146
-
147
- const range = this.editor.frameContext.get('_wd').createRange();
148
-
149
- try {
150
- range.setStart(sc, so);
151
- range.setEnd(ec, eo);
152
- } catch (error) {
153
- console.warn('[SUNEDITOR.selection.focus.warn]', error.message);
154
- this.editor._nativeFocus();
155
- return;
156
- }
157
-
158
- const selection = this.get();
159
-
160
- if (selection.removeAllRanges) {
161
- selection.removeAllRanges();
162
- }
163
-
164
- selection.addRange(range);
165
- this.status._range = range;
166
- this._rangeInfo(range, this.get());
167
-
168
- if (this.editor.frameOptions.get('iframe')) this.__focus();
169
-
170
- return range;
171
- },
172
-
173
- /**
174
- * @this {SelectionThis}
175
- * @description Remove range object and button effect
176
- */
177
- removeRange() {
178
- this.status._range = null;
179
- this.selectionNode = null;
180
- this.editor.effectNode = null;
181
- if (this.status.hasFocus) this.get().removeAllRanges();
182
- this.eventManager._setKeyEffect([]);
183
- },
184
-
185
- /**
186
- * @this {SelectionThis}
187
- * @description Returns the range (container and offset) near the given target node.
188
- * - If the target node has a next sibling, it returns the next sibling with an offset of 0.
189
- * - If there is no next sibling but a previous sibling exists, it returns the previous sibling with an offset of 1.
190
- * @param {Node} target Target node whose neighboring range is to be determined.
191
- * @returns {{container: Node, offset: number}|null} An object containing the nearest container node and its offset.
192
- */
193
- getNearRange(target) {
194
- const next = target.nextSibling;
195
- const prev = target.previousSibling;
196
- if (next) {
197
- return {
198
- container: next,
199
- offset: 0
200
- };
201
- } else if (prev) {
202
- return {
203
- container: prev,
204
- offset: 1
205
- };
206
- }
207
-
208
- return null;
209
- },
210
-
211
- /**
212
- * @this {SelectionThis}
213
- * @description If the "range" object is a non-editable area, add a line at the top of the editor and update the "range" object.
214
- * @param {Range} range core.getRange()
215
- * @param {?Node=} container If there is "container" argument, it creates a line in front of the container.
216
- * @returns {Range} a new "range" or argument "range".
217
- */
218
- getRangeAndAddLine(range, container) {
219
- if (this._isNone(range)) {
220
- const parent = container?.parentElement || this.editor.frameContext.get('wysiwyg');
221
- const op = dom.utils.createElement(this.options.get('defaultLine'), null, '<br>');
222
- parent.insertBefore(op, container && container !== parent ? (!(/** @type {HTMLElement} */ (container).previousElementSibling) ? container : /** @type {HTMLElement} */ (container).nextElementSibling) : parent.firstElementChild);
223
- this.setRange(op.firstElementChild, 0, op.firstElementChild, 1);
224
- range = this.status._range;
225
- }
226
- return range;
227
- },
228
-
229
- /**
230
- * @this {SelectionThis}
231
- * @description Get current select node
232
- * @returns {HTMLElement|Text}
233
- */
234
- getNode() {
235
- if (!this.editor.frameContext.get('wysiwyg').contains(this.selectionNode)) this._init();
236
- if (!this.selectionNode) {
237
- const selectionNode = /** @type {HTMLElement|Text} */ (dom.query.getEdgeChild(this.editor.frameContext.get('wysiwyg').firstChild, (current) => current.childNodes.length === 0 || current.nodeType === 3, false));
238
- if (!selectionNode) {
239
- this._init();
240
- } else {
241
- this.selectionNode = selectionNode;
242
- return selectionNode;
243
- }
244
- }
245
- return this.selectionNode;
246
- },
247
-
248
- /**
249
- * @this {SelectionThis}
250
- * @description Get the Rects object.
251
- * @param {?Range|Node} target Range | Node | null
252
- * @param {"start"|"end"} position It is based on the position of the rect object to be returned in case of range selection.
253
- * @returns {{rects: RectsInfo_selection, position: "start"|"end", scrollLeft: number, scrollTop: number}}
254
- */
255
- getRects(target, position) {
256
- const targetAbs = dom.check.isElement(/** @type {Node} */ (target)) ? this._w.getComputedStyle(target).position === 'absolute' : false;
257
- target = /** @type {Range} */ (!target || dom.check.isText(/** @type {Node} */ (target)) ? this.getRange() : target);
258
- const globalScroll = this.offset.getGlobalScroll();
259
- let isStartPosition = position === 'start';
260
- let scrollLeft = globalScroll.left;
261
- let scrollTop = globalScroll.top;
262
-
263
- let rects = /** @type {*} */ (target).getClientRects();
264
- rects = rects[isStartPosition ? 0 : rects.length - 1];
265
-
266
- if (!rects) {
267
- const node = this.getNode();
268
- if (this.format.isLine(node)) {
269
- const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
270
- this.html.insertNode(zeroWidth, { afterNode: null, skipCharCount: true });
271
- this.setRange(zeroWidth, 1, zeroWidth, 1);
272
- this._init();
273
- rects = this.getRange().getClientRects();
274
- rects = rects[isStartPosition ? 0 : rects.length - 1];
275
- }
276
-
277
- if (!rects) {
278
- const nodeOffset = this.offset.get(node);
279
- rects = {
280
- left: nodeOffset.left,
281
- top: nodeOffset.top,
282
- right: nodeOffset.left + /** @type {HTMLElement} */ (node).offsetWidth,
283
- bottom: nodeOffset.top + /** @type {HTMLElement} */ (node).offsetHeight,
284
- noText: true
285
- };
286
- scrollLeft = 0;
287
- scrollTop = 0;
288
- }
289
-
290
- isStartPosition = true;
291
- }
292
-
293
- const iframeRects = /^iframe$/i.test(this.editor.frameContext.get('wysiwygFrame').nodeName) ? this.editor.frameContext.get('wysiwygFrame').getClientRects()[0] : null;
294
- if (!targetAbs && iframeRects) {
295
- rects = {
296
- left: rects.left + iframeRects.left,
297
- top: rects.top + iframeRects.top,
298
- right: rects.right + iframeRects.right - iframeRects.width,
299
- bottom: rects.bottom + iframeRects.bottom - iframeRects.height
300
- };
301
- }
302
-
303
- return {
304
- rects: rects,
305
- position: isStartPosition ? 'start' : 'end',
306
- scrollLeft: scrollLeft,
307
- scrollTop: scrollTop
308
- };
309
- },
310
-
311
- /**
312
- * @this {SelectionThis}
313
- * @description Get the custom range object of the event.
314
- * @param {DragEvent} e Event object
315
- * @returns {{sc: Node, so: number, ec: Node, eo: number}} {sc: startContainer, so: startOffset, ec: endContainer, eo: endOffset}
316
- */
317
- getDragEventLocationRange(e) {
318
- const wd = this.editor.frameContext.get('_wd');
319
- let r, sc, so, ec, eo;
320
-
321
- if (wd.caretPositionFromPoint) {
322
- r = wd.caretPositionFromPoint(e.clientX, e.clientY);
323
- sc = r.offsetNode;
324
- so = r.offset;
325
- ec = r.offsetNode;
326
- eo = r.offset;
327
- } else if (wd.caretRangeFromPoint) {
328
- r = wd.caretRangeFromPoint(e.clientX, e.clientY);
329
- sc = r.startContainer;
330
- so = r.startOffset;
331
- ec = r.endContainer;
332
- eo = r.endOffset;
333
- }
334
-
335
- if (!r) {
336
- r = this.getRange();
337
- sc = r.startContainer;
338
- so = r.startOffset;
339
- ec = r.endContainer;
340
- eo = r.endOffset;
341
- }
342
-
343
- return {
344
- sc,
345
- so,
346
- ec,
347
- eo
348
- };
349
- },
350
-
351
- /**
352
- * @this {SelectionThis}
353
- * @description Scroll to the corresponding selection or range position.
354
- * @param {Selection|Range|Node} ref selection or range object
355
- * @param {?Object<string, *>=} scrollOption option of scrollTo
356
- */
357
- scrollTo(ref, scrollOption) {
358
- if (ref instanceof Selection) {
359
- ref = ref.getRangeAt(0);
360
- } else if (ref instanceof Node) {
361
- ref = this.setRange(ref, 1, ref, 1);
362
- } else if (typeof ref?.startContainer === 'undefined') {
363
- console.warn('[SUNEDITOR.html.scrollTo.warn] "selectionRange" must be Selection or Range or Node object.', ref);
364
- }
365
-
366
- const rect = ref.getBoundingClientRect();
367
- const isVisible = rect.top >= 0 && rect.bottom <= this.editor.frameContext.get('wysiwygFrame').innerHeight;
368
-
369
- if (isVisible) return;
370
-
371
- const el = dom.query.getParentElement(ref.startContainer, (current) => current.nodeType === 1);
372
- el?.scrollIntoView?.(scrollOption || this.options.get('scrollToOptions'));
373
- },
374
-
375
- /**
376
- * @private
377
- * @this {SelectionThis}
378
- * @description Returns true if there is no valid selection.
379
- * @param {Range} range selection.getRange()
380
- * @returns {boolean}
381
- */
382
- _isNone(range) {
383
- const comm = /** @type {HTMLElement} */ (range.commonAncestorContainer);
384
- return (
385
- (dom.check.isWysiwygFrame(range.startContainer) && dom.check.isWysiwygFrame(range.endContainer)) ||
386
- /FIGURE/i.test(comm.nodeName) ||
387
- (this.editor._fileManager.regExp.test(comm.nodeName) && (!this.editor._fileManager.tagAttrs[comm.nodeName] || this.editor._fileManager.tagAttrs[comm.nodeName]?.every((v) => comm.hasAttribute(v)))) ||
388
- this.component.is(comm)
389
- );
390
- },
391
-
392
- /**
393
- * @private
394
- * @this {SelectionThis}
395
- * @description Return the range object of editor's first child node
396
- * @returns {Range}
397
- */
398
- _createDefaultRange() {
399
- const wysiwyg = this.editor.frameContext.get('wysiwyg');
400
- const range = this.editor.frameContext.get('_wd').createRange();
401
-
402
- let firstFormat = wysiwyg.firstElementChild;
403
- let focusEl = null;
404
- if (!firstFormat) {
405
- focusEl = dom.utils.createElement('BR');
406
- firstFormat = dom.utils.createElement(this.options.get('defaultLine'), null, focusEl);
407
- wysiwyg.appendChild(firstFormat);
408
- } else {
409
- focusEl = firstFormat.firstChild;
410
- if (!focusEl) {
411
- focusEl = dom.utils.createElement('BR');
412
- firstFormat.appendChild(focusEl);
413
- }
414
- }
415
-
416
- range.setStart(focusEl, 0);
417
- range.setEnd(focusEl, 0);
418
-
419
- return range;
420
- },
421
-
422
- /**
423
- * @private
424
- * @this {SelectionThis}
425
- * @description Set "range" and "selection" info.
426
- * @param {Range} range range object.
427
- * @param {Selection} selection selection object.
428
- */
429
- _rangeInfo(range, selection) {
430
- let selectionNode = null;
431
- this.status._range = range;
432
-
433
- if (range.collapsed) {
434
- if (dom.check.isWysiwygFrame(range.commonAncestorContainer)) selectionNode = range.commonAncestorContainer.children[range.startOffset] || range.commonAncestorContainer;
435
- else selectionNode = range.commonAncestorContainer;
436
- } else {
437
- selectionNode = selection.anchorNode;
438
- }
439
-
440
- this.selectionNode = /** @type {HTMLElement|Text} */ (selectionNode);
441
- },
442
-
443
- /**
444
- * @private
445
- * @this {SelectionThis}
446
- * @description Saving the range object and the currently selected node of editor
447
- */
448
- _init() {
449
- const activeEl = this.editor.frameContext.get('_wd').activeElement;
450
- if (dom.check.isInputElement(activeEl)) {
451
- this.selectionNode = activeEl;
452
- return activeEl;
453
- }
454
-
455
- const selection = this.get();
456
-
457
- if (!selection) return null;
458
- let range = null;
459
-
460
- if (selection.rangeCount > 0) {
461
- range = selection.getRangeAt(0);
462
- } else {
463
- range = this._createDefaultRange();
464
- }
465
-
466
- this._rangeInfo(range, selection);
467
- },
468
-
469
- /**
470
- * @private
471
- * @this {SelectionThis}
472
- * @description Focus method
473
- */
474
- __focus() {
475
- try {
476
- this.__iframeFocus = true;
477
- const caption = dom.query.getParentElement(this.getNode(), 'figcaption');
478
- if (caption) {
479
- caption.focus();
480
- } else {
481
- this.editor.frameContext.get('wysiwyg').focus();
482
- }
483
- } finally {
484
- env._w.setTimeout(() => (this.__iframeFocus = false), 0);
485
- }
486
- },
487
-
488
- /**
489
- * @private
490
- * @this {SelectionThis}
491
- * @description Reset range object to text node selected status.
492
- * @returns {boolean} Returns false if there is no valid selection.
493
- */
494
- _resetRangeToTextNode() {
495
- let rangeObj = this.getRange();
496
- if (this._isNone(rangeObj)) {
497
- if (!dom.check.isWysiwygFrame(rangeObj.startContainer) || !dom.check.isWysiwygFrame(rangeObj.endContainer)) return false;
498
- const ww = /** @type {HTMLElement} */ (rangeObj.commonAncestorContainer);
499
- const first = ww.children[rangeObj.startOffset];
500
- const end = ww.children[rangeObj.endOffset];
501
- if (!(rangeObj = this.setRange(first, 0, end, first === end ? 0 : 1))) return false;
502
- }
503
-
504
- const range = rangeObj;
505
- const collapsed = range.collapsed;
506
- let startCon = range.startContainer;
507
- let startOff = range.startOffset;
508
- let endCon = range.endContainer;
509
- let endOff = range.endOffset;
510
- let tempCon, tempOffset, tempChild;
511
-
512
- if (this.format.isLine(startCon)) {
513
- if (!startCon.childNodes[startOff]) {
514
- startCon = startCon.lastChild || startCon;
515
- startOff = startCon.textContent.length;
516
- } else {
517
- startCon = startCon.childNodes[startOff] || startCon;
518
- startOff = 0;
519
- }
520
- while (startCon?.nodeType === 1 && startCon.firstChild) {
521
- startCon = startCon.firstChild || startCon;
522
- startOff = 0;
523
- }
524
- }
525
- if (this.format.isLine(endCon)) {
526
- endCon = endCon.childNodes[endOff] || endCon.lastChild || endCon;
527
- while (endCon?.nodeType === 1 && endCon.lastChild) {
528
- endCon = endCon.lastChild;
529
- }
530
- if (collapsed) endOff = 0;
531
- else if (endOff > 0) endOff = endCon.textContent.length;
532
- }
533
-
534
- // startContainer
535
- tempCon = dom.check.isWysiwygFrame(startCon) ? this.editor.frameContext.get('wysiwyg').firstChild : startCon;
536
- tempOffset = startOff;
537
-
538
- if (dom.check.isBreak(tempCon) || (tempCon.nodeType === 1 && tempCon.childNodes.length > 0)) {
539
- const onlyBreak = dom.check.isBreak(tempCon);
540
- if (!onlyBreak) {
541
- const tempConCache = tempCon;
542
- while (tempCon && !dom.check.isBreak(tempCon) && tempCon.nodeType === 1) {
543
- tempChild = tempCon.childNodes;
544
- if (tempChild.length === 0) break;
545
- tempCon = tempChild[tempOffset > 0 ? tempOffset - 1 : tempOffset] || !/FIGURE/i.test(tempChild[0].nodeName) ? tempChild[0] : tempCon.previousElementSibling || tempCon.previousSibling || startCon;
546
- tempOffset = tempOffset > 0 ? tempCon.textContent.length : tempOffset;
547
- }
548
-
549
- let format = this.format.getLine(tempCon, null);
550
- if (format === this.format.getBlock(format, null)) {
551
- tempCon = tempCon || tempConCache;
552
- format = dom.utils.createElement(dom.query.getParentElement(tempCon, dom.check.isTableCell) ? 'DIV' : this.options.get('defaultLine'));
553
- tempCon.parentNode.insertBefore(format, tempCon);
554
- if (tempCon !== tempConCache) format.appendChild(tempCon);
555
- }
556
- }
557
-
558
- if (dom.check.isBreak(tempCon) || this.component.is(tempCon)) {
559
- const emptyText = dom.utils.createTextNode(unicode.zeroWidthSpace);
560
- tempCon.parentNode.insertBefore(emptyText, tempCon);
561
- tempCon = emptyText;
562
- if (onlyBreak) {
563
- if (startCon === endCon) {
564
- endCon = tempCon;
565
- endOff = 1;
566
- }
567
- }
568
- }
569
- }
570
-
571
- // set startContainer
572
- startCon = tempCon;
573
- startOff = tempOffset;
574
-
575
- // endContainer
576
- tempCon = dom.check.isWysiwygFrame(endCon) ? this.editor.frameContext.get('wysiwyg').lastChild : endCon;
577
- tempOffset = endOff;
578
-
579
- if (dom.check.isBreak(tempCon) || (tempCon.nodeType === 1 && tempCon.childNodes.length > 0)) {
580
- const onlyBreak = dom.check.isBreak(tempCon);
581
- if (!onlyBreak) {
582
- while (tempCon && !dom.check.isBreak(tempCon) && tempCon.nodeType === 1) {
583
- tempChild = tempCon.childNodes;
584
- if (tempChild.length === 0) break;
585
- tempCon = tempChild[tempOffset > 0 ? tempOffset - 1 : tempOffset] || !/FIGURE/i.test(tempChild[0].nodeName) ? tempChild[0] : tempCon.previousElementSibling || tempCon.previousSibling || startCon;
586
- tempOffset = tempOffset > 0 ? tempCon.textContent.length : tempOffset;
587
- }
588
-
589
- let format = this.format.getLine(tempCon, null);
590
- if (format === this.format.getBlock(format, null)) {
591
- format = dom.utils.createElement(dom.check.isTableCell(format) ? 'DIV' : this.options.get('defaultLine'));
592
- tempCon.parentNode.insertBefore(format, tempCon);
593
- format.appendChild(tempCon);
594
- }
595
- }
596
-
597
- if (dom.check.isBreak(tempCon)) {
598
- const emptyText = dom.utils.createTextNode(unicode.zeroWidthSpace);
599
- tempCon.parentNode.insertBefore(emptyText, tempCon);
600
- tempCon = emptyText;
601
- tempOffset = 1;
602
- if (onlyBreak && !tempCon.previousSibling) {
603
- dom.utils.removeItem(endCon);
604
- }
605
- }
606
- }
607
-
608
- // set endContainer
609
- endCon = tempCon;
610
- endOff = tempOffset;
611
-
612
- // set Range
613
- this.setRange(startCon, startOff, endCon, endOff);
614
- return true;
615
- },
616
-
617
- constructor: Selection_
618
- };
619
-
620
- export default Selection_;
1
+ /**
2
+ * @fileoverview Selection class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { dom, unicode, env, numbers } from '../../helper';
7
+ const { _w, isTouchDevice } = env;
8
+
9
+ /**
10
+ * @typedef {Omit<Selection_ & Partial<__se__EditorInjector>, 'selection'>} SelectionThis
11
+ */
12
+
13
+ /**
14
+ * @typedef {import('./offset').RectsInfo} RectsInfo_selection
15
+ */
16
+
17
+ /**
18
+ * @constructor
19
+ * @this {SelectionThis}
20
+ * @description Selection, Range related class
21
+ * @param {__se__EditorCore} editor - The root editor instance
22
+ */
23
+ function Selection_(editor) {
24
+ CoreInjector.call(this, editor);
25
+
26
+ // members
27
+ /** @type {Range} */
28
+ this.range = null;
29
+ /** @type {HTMLElement|Text} */
30
+ this.selectionNode = null;
31
+ this.__iframeFocus = false;
32
+
33
+ _w.setTimeout(() => {
34
+ this.__hasScrollParents = this.eventManager.scrollparents.length > 0;
35
+ this._scrollMargin = (numbers.get(_w.getComputedStyle(editor.frameContext.get('wysiwyg')).scrollMargin, 0) || 40) + numbers.get(_w.getComputedStyle(editor.frameContext.get('wrapper')).paddingBottom, 0);
36
+ }, 1000);
37
+ }
38
+
39
+ Selection_.prototype = {
40
+ /**
41
+ * @this {SelectionThis}
42
+ * @description Get window selection obejct
43
+ * @returns {Selection}
44
+ */
45
+ get() {
46
+ let selection = null;
47
+
48
+ if (typeof this.editor._shadowRoot?.getSelection === 'function') {
49
+ selection = this.editor._shadowRoot.getSelection();
50
+ } else {
51
+ selection = this.editor.frameContext.get('_ww').getSelection();
52
+ }
53
+
54
+ if (!selection) return null;
55
+ if (!this.status._range && !this.editor.frameContext.get('wysiwyg').contains(selection.focusNode)) {
56
+ selection.removeAllRanges();
57
+ selection.addRange(this._createDefaultRange());
58
+ }
59
+ return selection;
60
+ },
61
+
62
+ /**
63
+ * @this {SelectionThis}
64
+ * @description Check if the range object is valid
65
+ * @param {*} range Range object
66
+ * @returns {boolean}
67
+ */
68
+ isRange(range) {
69
+ // return /Range/.test(Object.prototype.toString.call(range?.__proto__));
70
+ return this.instanceCheck.isRange(range);
71
+ },
72
+
73
+ /**
74
+ * @this {SelectionThis}
75
+ * @description Get current editor's range object
76
+ * @returns {Range}
77
+ */
78
+ getRange() {
79
+ const range = this.status._range || this._createDefaultRange();
80
+ const selection = this.get();
81
+ if (range.collapsed === selection.isCollapsed || !this.editor.frameContext.get('wysiwyg').contains(selection.focusNode)) {
82
+ if (this.component.is(range.startContainer)) {
83
+ const compInfo = this.component.get(range.startContainer);
84
+ const container = compInfo?.container;
85
+ if (!container) return range;
86
+ return this.setRange(container, 0, container, 1);
87
+ }
88
+
89
+ return range;
90
+ }
91
+
92
+ if (selection.rangeCount > 0) {
93
+ this.status._range = selection.getRangeAt(0);
94
+ return this.status._range;
95
+ } else {
96
+ const sc = selection.anchorNode,
97
+ ec = selection.focusNode,
98
+ so = selection.anchorOffset,
99
+ eo = selection.focusOffset;
100
+ const compareValue = dom.query.compareElements(sc, ec);
101
+ const rightDir = compareValue.ancestor && (compareValue.result === 0 ? so <= eo : compareValue.result > 1 ? true : false);
102
+ return this.setRange(rightDir ? sc : ec, rightDir ? so : eo, rightDir ? ec : sc, rightDir ? eo : so);
103
+ }
104
+ },
105
+
106
+ /**
107
+ * @this {SelectionThis}
108
+ * @description Set current editor's range object and return.
109
+ * @param {Node|Range} startCon Range object or The startContainer property of the selection object
110
+ * @param {number} [startOff] The startOffset property of the selection object.
111
+ * @param {Node} [endCon] The endContainer property of the selection object.
112
+ * @param {number} [endOff] The endOffset property of the selection object.
113
+ * @returns {Range}
114
+ */
115
+ setRange(startCon, startOff, endCon, endOff) {
116
+ /** @type {Node} */
117
+ let sc;
118
+ /** @type {number} */
119
+ let so;
120
+ /** @type {Node} */
121
+ let ec;
122
+ /** @type {number} */
123
+ let eo;
124
+
125
+ if (this.isRange(startCon)) {
126
+ const r = /** @type {Range} */ (startCon);
127
+ sc = r.startContainer;
128
+ so = r.startOffset;
129
+ ec = r.endContainer;
130
+ eo = r.endOffset;
131
+ } else {
132
+ sc = /** @type {Node} */ (startCon);
133
+ so = startOff;
134
+ ec = endCon;
135
+ eo = endOff;
136
+ }
137
+
138
+ if (!sc || !ec) return;
139
+ if ((dom.check.isBreak(sc) || sc.nodeType === 3) && so > sc.textContent.length) so = sc.textContent.length;
140
+ if ((dom.check.isBreak(ec) || ec.nodeType === 3) && eo > ec.textContent.length) eo = ec.textContent.length;
141
+ if (this.format.isLine(sc)) {
142
+ sc = sc.childNodes[so > 0 ? sc.childNodes.length - 1 : 0] || sc;
143
+ so = so > 0 ? (sc.nodeType === 1 && !dom.check.isBreak(sc) ? 1 : sc.textContent ? sc.textContent.length : 0) : 0;
144
+ }
145
+ if (this.format.isLine(ec)) {
146
+ ec = ec.childNodes[eo > 0 ? ec.childNodes.length - 1 : 0] || ec;
147
+ eo = eo > 0 ? (ec.nodeType === 1 && !dom.check.isBreak(ec) ? 1 : ec.textContent ? ec.textContent.length : 0) : 0;
148
+ }
149
+
150
+ const range = this.editor.frameContext.get('_wd').createRange();
151
+
152
+ try {
153
+ range.setStart(sc, so);
154
+ range.setEnd(ec, eo);
155
+ this.status.hasFocus = true;
156
+ } catch (error) {
157
+ console.warn('[SUNEDITOR.selection.focus.warn]', error.message);
158
+ this.editor._nativeFocus();
159
+ return;
160
+ }
161
+
162
+ const selection = this.get();
163
+
164
+ if (selection.removeAllRanges) {
165
+ selection.removeAllRanges();
166
+ }
167
+
168
+ selection.addRange(range);
169
+ this.status._range = range;
170
+ this._rangeInfo(range, this.get());
171
+
172
+ if (this.editor.frameOptions.get('iframe')) this.__focus();
173
+
174
+ return range;
175
+ },
176
+
177
+ /**
178
+ * @this {SelectionThis}
179
+ * @description Remove range object and button effect
180
+ */
181
+ removeRange() {
182
+ this.status._range = null;
183
+ this.selectionNode = null;
184
+ this.editor.effectNode = null;
185
+ if (this.status.hasFocus) this.get().removeAllRanges();
186
+ this.eventManager._setKeyEffect([]);
187
+ },
188
+
189
+ /**
190
+ * @this {SelectionThis}
191
+ * @description Returns the range (container and offset) near the given target node.
192
+ * - If the target node has a next sibling, it returns the next sibling with an offset of 0.
193
+ * - If there is no next sibling but a previous sibling exists, it returns the previous sibling with an offset of 1.
194
+ * @param {Node} target Target node whose neighboring range is to be determined.
195
+ * @returns {{container: Node, offset: number}|null} An object containing the nearest container node and its offset.
196
+ */
197
+ getNearRange(target) {
198
+ const next = target.nextSibling;
199
+ const prev = target.previousSibling;
200
+ if (next) {
201
+ return {
202
+ container: next,
203
+ offset: 0
204
+ };
205
+ } else if (prev) {
206
+ return {
207
+ container: prev,
208
+ offset: 1
209
+ };
210
+ }
211
+
212
+ return null;
213
+ },
214
+
215
+ /**
216
+ * @this {SelectionThis}
217
+ * @description If the "range" object is a non-editable area, add a line at the top of the editor and update the "range" object.
218
+ * @param {Range} range core.getRange()
219
+ * @param {?Node=} container If there is "container" argument, it creates a line in front of the container.
220
+ * @returns {Range} a new "range" or argument "range".
221
+ */
222
+ getRangeAndAddLine(range, container) {
223
+ if (this._isNone(range)) {
224
+ const parent = container?.parentElement || this.editor.frameContext.get('wysiwyg');
225
+ const op = dom.utils.createElement(this.options.get('defaultLine'), null, '<br>');
226
+ parent.insertBefore(op, container && container !== parent ? (!(/** @type {HTMLElement} */ (container).previousElementSibling) ? container : /** @type {HTMLElement} */ (container).nextElementSibling) : parent.firstElementChild);
227
+ this.setRange(op.firstElementChild, 0, op.firstElementChild, 1);
228
+ range = this.status._range;
229
+ }
230
+ return range;
231
+ },
232
+
233
+ /**
234
+ * @this {SelectionThis}
235
+ * @description Get current select node
236
+ * @returns {HTMLElement|Text}
237
+ */
238
+ getNode() {
239
+ if (!this.editor.frameContext.get('wysiwyg').contains(this.selectionNode)) this._init();
240
+ if (!this.selectionNode) {
241
+ const selectionNode = /** @type {HTMLElement|Text} */ (dom.query.getEdgeChild(this.editor.frameContext.get('wysiwyg').firstChild, (current) => current.childNodes.length === 0 || current.nodeType === 3, false));
242
+ if (!selectionNode) {
243
+ this._init();
244
+ } else {
245
+ this.selectionNode = selectionNode;
246
+ return selectionNode;
247
+ }
248
+ }
249
+ return this.selectionNode;
250
+ },
251
+
252
+ /**
253
+ * @this {SelectionThis}
254
+ * @description Get the Rects object.
255
+ * @param {?Range|Node} target Range | Node | null
256
+ * @param {"start"|"end"} position It is based on the position of the rect object to be returned in case of range selection.
257
+ * @returns {{rects: RectsInfo_selection, position: "start"|"end", scrollLeft: number, scrollTop: number}}
258
+ */
259
+ getRects(target, position) {
260
+ const targetAbs = dom.check.isElement(/** @type {Node} */ (target)) ? _w.getComputedStyle(target).position === 'absolute' : false;
261
+ target = /** @type {Range} */ (!target || dom.check.isText(/** @type {Node} */ (target)) ? this.getRange() : target);
262
+ const globalScroll = this.offset.getGlobalScroll();
263
+ let isStartPosition = position === 'start';
264
+ let scrollLeft = globalScroll.left;
265
+ let scrollTop = globalScroll.top;
266
+
267
+ let rects = /** @type {*} */ (target).getClientRects();
268
+ rects = rects[isStartPosition ? 0 : rects.length - 1];
269
+
270
+ if (!rects) {
271
+ const node = this.getNode();
272
+ if (this.format.isLine(node)) {
273
+ const zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
274
+ this.html.insertNode(zeroWidth, { afterNode: null, skipCharCount: true });
275
+ this.setRange(zeroWidth, 1, zeroWidth, 1);
276
+ this._init();
277
+ rects = this.getRange().getClientRects();
278
+ rects = rects[isStartPosition ? 0 : rects.length - 1];
279
+ }
280
+
281
+ if (!rects) {
282
+ const nodeOffset = this.offset.get(node);
283
+ rects = {
284
+ left: nodeOffset.left,
285
+ top: nodeOffset.top,
286
+ right: nodeOffset.left + /** @type {HTMLElement} */ (node).offsetWidth,
287
+ bottom: nodeOffset.top + /** @type {HTMLElement} */ (node).offsetHeight,
288
+ noText: true
289
+ };
290
+ scrollLeft = 0;
291
+ scrollTop = 0;
292
+ }
293
+
294
+ isStartPosition = true;
295
+ }
296
+
297
+ const iframeRects = /^iframe$/i.test(this.editor.frameContext.get('wysiwygFrame').nodeName) ? this.editor.frameContext.get('wysiwygFrame').getClientRects()[0] : null;
298
+ if (!targetAbs && iframeRects) {
299
+ rects = {
300
+ left: rects.left + iframeRects.left,
301
+ top: rects.top + iframeRects.top,
302
+ right: rects.right + iframeRects.right - iframeRects.width,
303
+ bottom: rects.bottom + iframeRects.bottom - iframeRects.height
304
+ };
305
+ }
306
+
307
+ return {
308
+ rects: rects,
309
+ position: isStartPosition ? 'start' : 'end',
310
+ scrollLeft: scrollLeft,
311
+ scrollTop: scrollTop
312
+ };
313
+ },
314
+
315
+ /**
316
+ * @this {SelectionThis}
317
+ * @description Get the custom range object of the event.
318
+ * @param {DragEvent} e Event object
319
+ * @returns {{sc: Node, so: number, ec: Node, eo: number}} {sc: startContainer, so: startOffset, ec: endContainer, eo: endOffset}
320
+ */
321
+ getDragEventLocationRange(e) {
322
+ const wd = this.editor.frameContext.get('_wd');
323
+ let r, sc, so, ec, eo;
324
+
325
+ if (wd.caretPositionFromPoint) {
326
+ r = wd.caretPositionFromPoint(e.clientX, e.clientY);
327
+ sc = r.offsetNode;
328
+ so = r.offset;
329
+ ec = r.offsetNode;
330
+ eo = r.offset;
331
+ } else if (wd.caretRangeFromPoint) {
332
+ r = wd.caretRangeFromPoint(e.clientX, e.clientY);
333
+ sc = r.startContainer;
334
+ so = r.startOffset;
335
+ ec = r.endContainer;
336
+ eo = r.endOffset;
337
+ }
338
+
339
+ return {
340
+ sc,
341
+ so,
342
+ ec,
343
+ eo
344
+ };
345
+ },
346
+
347
+ /**
348
+ * @this {SelectionThis}
349
+ * @description Scroll to the corresponding selection or range position.
350
+ * @param {Selection|Range|Node} ref selection or range object
351
+ * @param {?Object<string, *>=} scrollOption option of scrollTo
352
+ */
353
+ scrollTo(ref, scrollOption) {
354
+ if (this.instanceCheck.isSelection(ref)) {
355
+ ref = ref.getRangeAt(0);
356
+ } else if (this.instanceCheck.isNode(ref)) {
357
+ ref = this.setRange(ref, 1, ref, 1);
358
+ } else if (typeof ref?.startContainer === 'undefined') {
359
+ console.warn('[SUNEDITOR.html.scrollTo.warn] "selectionRange" must be Selection or Range or Node object.', ref);
360
+ }
361
+
362
+ scrollOption = { behavior: 'smooth', block: 'nearest', inline: 'nearest', ...scrollOption };
363
+ const el = dom.query.getParentElement(ref.startContainer, (current) => current.nodeType === 1);
364
+
365
+ const { frameContext, frameOptions } = this.editor;
366
+ const ww = frameContext.get('_ww');
367
+ const wwFrame = frameContext.get('wysiwygFrame');
368
+ const isIframe = frameOptions.get('iframe');
369
+ const isAutoHeight = frameOptions.get('height') === 'auto';
370
+ const initViewportHeight = this.status.initViewportHeight;
371
+ const viewportHeight = this.status.currentViewportHeight;
372
+ const scrollY = isAutoHeight ? _w.scrollY : isIframe ? ww.scrollY : wwFrame.scrollTop;
373
+ const realToolbarHeight = this.context.get('toolbar.main').offsetHeight;
374
+ const toolbarHeight = this.toolbar._sticky ? realToolbarHeight : 0;
375
+ const statusbarHeight = frameContext.get('statusbar')?.offsetHeight || 0;
376
+
377
+ if (this.__hasScrollParents || (!isIframe && (!isTouchDevice || initViewportHeight - viewportHeight < 150))) {
378
+ el?.scrollIntoView(scrollOption);
379
+
380
+ if (toolbarHeight && scrollY > _w.scrollY) {
381
+ _w.scrollBy(0, -toolbarHeight);
382
+ } else if (isAutoHeight) {
383
+ _w.scrollBy(0, statusbarHeight);
384
+ }
385
+
386
+ return;
387
+ }
388
+
389
+ // --- When there is no upper scroll and it is an iframe ---
390
+ const PADDING = this._scrollMargin;
391
+ const viewHeight = isAutoHeight ? viewportHeight : wwFrame.offsetHeight;
392
+ const elH = el.offsetHeight || 0;
393
+
394
+ const behavior = scrollOption?.behavior;
395
+ if (isAutoHeight) {
396
+ if (isIframe) {
397
+ const rect = this.getRects(ref, 'end').rects;
398
+ const topMargin = rect.top + elH - toolbarHeight;
399
+ const bottomMargin = viewHeight - PADDING - (rect.top + elH);
400
+ if (topMargin >= 0 && bottomMargin >= 0) return;
401
+
402
+ const newScrollTop = scrollY - (topMargin < 0 ? -(topMargin - PADDING) : bottomMargin);
403
+ _w.scrollTo({
404
+ top: newScrollTop,
405
+ behavior
406
+ });
407
+ } else {
408
+ const rect = this.offset.getGlobal(el);
409
+ const scrollMargin = viewHeight + scrollY - rect.top - elH;
410
+
411
+ if (scrollMargin - PADDING > 0 && viewHeight > scrollMargin + PADDING + toolbarHeight) return;
412
+
413
+ const newScrollTop = scrollMargin <= PADDING ? scrollY - scrollMargin + PADDING + statusbarHeight : scrollY - scrollMargin + (viewHeight - elH - PADDING);
414
+ _w.scrollTo({
415
+ top: newScrollTop,
416
+ behavior
417
+ });
418
+ }
419
+ } else {
420
+ // local scroll
421
+ const { top } = this.offset.getLocal(el);
422
+
423
+ const keepLocalScroll = top - PADDING > 0 && top + PADDING <= viewHeight;
424
+ const rectScroll = top - PADDING > 0 ? top + PADDING - viewHeight : top - (toolbarHeight + elH);
425
+ let newScrollTop = scrollY + rectScroll;
426
+
427
+ // frame scroll
428
+ const gy = _w.scrollY;
429
+ const globalRect = this.offset.getGlobal();
430
+ const topMargin = gy - globalRect.top + realToolbarHeight;
431
+ const bottomMargin = globalRect.top + globalRect.height - (gy + viewportHeight) + realToolbarHeight;
432
+
433
+ // set frame scroll
434
+ if (topMargin > 0) {
435
+ const newFrameY = (keepLocalScroll ? top : top + scrollY - newScrollTop) - elH - PADDING - topMargin;
436
+ if (newFrameY < 0) {
437
+ newScrollTop += realToolbarHeight;
438
+ _w.scrollTo({
439
+ top: gy + newFrameY,
440
+ behavior: 'smooth'
441
+ });
442
+ }
443
+ }
444
+ if (bottomMargin > 0) {
445
+ const newFrameY = (keepLocalScroll ? top : top + scrollY - newScrollTop) + elH + PADDING - (globalRect.height - bottomMargin);
446
+ if (newFrameY > 0) {
447
+ newScrollTop += statusbarHeight;
448
+ _w.scrollTo({
449
+ top: gy + newFrameY,
450
+ behavior: 'smooth'
451
+ });
452
+ }
453
+ }
454
+
455
+ // set local scroll
456
+ if (!keepLocalScroll) {
457
+ (isIframe ? ww : wwFrame).scrollTo({
458
+ top: newScrollTop,
459
+ behavior
460
+ });
461
+ }
462
+ }
463
+ },
464
+
465
+ /**
466
+ * @private
467
+ * @this {SelectionThis}
468
+ * @description Returns true if there is no valid selection.
469
+ * @param {Range} range selection.getRange()
470
+ * @returns {boolean}
471
+ */
472
+ _isNone(range) {
473
+ const comm = /** @type {HTMLElement} */ (range.commonAncestorContainer);
474
+ return (
475
+ (dom.check.isWysiwygFrame(range.startContainer) && dom.check.isWysiwygFrame(range.endContainer)) ||
476
+ /FIGURE/i.test(comm.nodeName) ||
477
+ (this.editor._fileManager.regExp.test(comm.nodeName) && (!this.editor._fileManager.tagAttrs[comm.nodeName] || this.editor._fileManager.tagAttrs[comm.nodeName]?.every((v) => comm.hasAttribute(v)))) ||
478
+ this.component.is(comm)
479
+ );
480
+ },
481
+
482
+ /**
483
+ * @private
484
+ * @this {SelectionThis}
485
+ * @description Return the range object of editor's first child node
486
+ * @returns {Range}
487
+ */
488
+ _createDefaultRange() {
489
+ const wysiwyg = this.editor.frameContext.get('wysiwyg');
490
+ const range = this.editor.frameContext.get('_wd').createRange();
491
+
492
+ let firstFormat = wysiwyg.firstElementChild;
493
+ let focusEl = null;
494
+ if (!firstFormat) {
495
+ focusEl = dom.utils.createElement('BR');
496
+ firstFormat = dom.utils.createElement(this.options.get('defaultLine'), null, focusEl);
497
+ wysiwyg.appendChild(firstFormat);
498
+ } else {
499
+ focusEl = firstFormat.firstChild;
500
+ if (!focusEl) {
501
+ focusEl = dom.utils.createElement('BR');
502
+ firstFormat.appendChild(focusEl);
503
+ }
504
+ }
505
+
506
+ range.setStart(focusEl, 0);
507
+ range.setEnd(focusEl, 0);
508
+
509
+ return range;
510
+ },
511
+
512
+ /**
513
+ * @private
514
+ * @this {SelectionThis}
515
+ * @description Set "range" and "selection" info.
516
+ * @param {Range} range range object.
517
+ * @param {Selection} selection selection object.
518
+ */
519
+ _rangeInfo(range, selection) {
520
+ let selectionNode = null;
521
+ this.status._range = range;
522
+
523
+ if (range.collapsed) {
524
+ if (dom.check.isWysiwygFrame(range.commonAncestorContainer)) selectionNode = range.commonAncestorContainer.children[range.startOffset] || range.commonAncestorContainer;
525
+ else selectionNode = range.commonAncestorContainer;
526
+ } else {
527
+ selectionNode = selection.anchorNode;
528
+ }
529
+
530
+ this.selectionNode = /** @type {HTMLElement|Text} */ (selectionNode);
531
+ },
532
+
533
+ /**
534
+ * @private
535
+ * @this {SelectionThis}
536
+ * @description Saving the range object and the currently selected node of editor
537
+ */
538
+ _init() {
539
+ const activeEl = this.editor.frameContext.get('_wd').activeElement;
540
+ if (dom.check.isInputElement(activeEl)) {
541
+ this.selectionNode = activeEl;
542
+ return activeEl;
543
+ }
544
+
545
+ const selection = this.get();
546
+
547
+ if (!selection) return null;
548
+ let range = null;
549
+
550
+ if (selection.rangeCount > 0) {
551
+ range = selection.getRangeAt(0);
552
+ } else {
553
+ range = this._createDefaultRange();
554
+ }
555
+
556
+ this._rangeInfo(range, selection);
557
+ },
558
+
559
+ /**
560
+ * @private
561
+ * @this {SelectionThis}
562
+ * @description Focus method
563
+ */
564
+ __focus() {
565
+ try {
566
+ this.__iframeFocus = true;
567
+ const caption = dom.query.getParentElement(this.getNode(), 'figcaption');
568
+ if (caption) {
569
+ caption.focus();
570
+ } else {
571
+ this.editor.frameContext.get('wysiwyg').focus();
572
+ }
573
+ } finally {
574
+ _w.setTimeout(() => (this.__iframeFocus = false), 0);
575
+ }
576
+ },
577
+
578
+ /**
579
+ * @private
580
+ * @this {SelectionThis}
581
+ * @description Reset range object to text node selected status.
582
+ * @returns {boolean} Returns false if there is no valid selection.
583
+ */
584
+ _resetRangeToTextNode() {
585
+ let rangeObj = this.getRange();
586
+ if (this._isNone(rangeObj)) {
587
+ if (!dom.check.isWysiwygFrame(rangeObj.startContainer) || !dom.check.isWysiwygFrame(rangeObj.endContainer)) return false;
588
+ const ww = /** @type {HTMLElement} */ (rangeObj.commonAncestorContainer);
589
+ const first = ww.children[rangeObj.startOffset];
590
+ const end = ww.children[rangeObj.endOffset];
591
+ if (!(rangeObj = this.setRange(first, 0, end, first === end ? 0 : 1))) return false;
592
+ }
593
+
594
+ const range = rangeObj;
595
+ const collapsed = range.collapsed;
596
+ let startCon = range.startContainer;
597
+ let startOff = range.startOffset;
598
+ let endCon = range.endContainer;
599
+ let endOff = range.endOffset;
600
+ let tempCon, tempOffset, tempChild;
601
+
602
+ if (this.format.isLine(startCon)) {
603
+ if (!startCon.childNodes[startOff]) {
604
+ startCon = startCon.lastChild || startCon;
605
+ startOff = startCon.textContent.length;
606
+ } else {
607
+ startCon = startCon.childNodes[startOff] || startCon;
608
+ startOff = 0;
609
+ }
610
+ while (startCon?.nodeType === 1 && startCon.firstChild) {
611
+ startCon = startCon.firstChild || startCon;
612
+ startOff = 0;
613
+ }
614
+ }
615
+ if (this.format.isLine(endCon)) {
616
+ endCon = endCon.childNodes[endOff] || endCon.lastChild || endCon;
617
+ while (endCon?.nodeType === 1 && endCon.lastChild) {
618
+ endCon = endCon.lastChild;
619
+ }
620
+ if (collapsed) endOff = 0;
621
+ else if (endOff > 0) endOff = endCon.textContent.length;
622
+ }
623
+
624
+ // startContainer
625
+ tempCon = dom.check.isWysiwygFrame(startCon) ? this.editor.frameContext.get('wysiwyg').firstChild : startCon;
626
+ tempOffset = startOff;
627
+
628
+ if (dom.check.isBreak(tempCon) || (tempCon.nodeType === 1 && tempCon.childNodes.length > 0)) {
629
+ const onlyBreak = dom.check.isBreak(tempCon);
630
+ if (!onlyBreak) {
631
+ const tempConCache = tempCon;
632
+ while (tempCon && !dom.check.isBreak(tempCon) && tempCon.nodeType === 1) {
633
+ tempChild = tempCon.childNodes;
634
+ if (tempChild.length === 0) break;
635
+ tempCon = tempChild[tempOffset > 0 ? tempOffset - 1 : tempOffset] || !/FIGURE/i.test(tempChild[0].nodeName) ? tempChild[0] : tempCon.previousElementSibling || tempCon.previousSibling || startCon;
636
+ tempOffset = tempOffset > 0 ? tempCon.textContent.length : tempOffset;
637
+ }
638
+
639
+ let format = this.format.getLine(tempCon, null);
640
+ if (format === this.format.getBlock(format, null)) {
641
+ tempCon = tempCon || tempConCache;
642
+ format = dom.utils.createElement(dom.query.getParentElement(tempCon, dom.check.isTableCell) ? 'DIV' : this.options.get('defaultLine'));
643
+ tempCon.parentNode.insertBefore(format, tempCon);
644
+ if (tempCon !== tempConCache) format.appendChild(tempCon);
645
+ }
646
+ }
647
+
648
+ if (dom.check.isBreak(tempCon) || this.component.is(tempCon)) {
649
+ const emptyText = dom.utils.createTextNode(unicode.zeroWidthSpace);
650
+ tempCon.parentNode.insertBefore(emptyText, tempCon);
651
+ tempCon = emptyText;
652
+ if (onlyBreak) {
653
+ if (startCon === endCon) {
654
+ endCon = tempCon;
655
+ endOff = 1;
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ // set startContainer
662
+ startCon = tempCon;
663
+ startOff = tempOffset;
664
+
665
+ // endContainer
666
+ tempCon = dom.check.isWysiwygFrame(endCon) ? this.editor.frameContext.get('wysiwyg').lastChild : endCon;
667
+ tempOffset = endOff;
668
+
669
+ if (dom.check.isBreak(tempCon) || (tempCon.nodeType === 1 && tempCon.childNodes.length > 0)) {
670
+ const onlyBreak = dom.check.isBreak(tempCon);
671
+ if (!onlyBreak) {
672
+ while (tempCon && !dom.check.isBreak(tempCon) && tempCon.nodeType === 1) {
673
+ tempChild = tempCon.childNodes;
674
+ if (tempChild.length === 0) break;
675
+ tempCon = tempChild[tempOffset > 0 ? tempOffset - 1 : tempOffset] || !/FIGURE/i.test(tempChild[0].nodeName) ? tempChild[0] : tempCon.previousElementSibling || tempCon.previousSibling || startCon;
676
+ tempOffset = tempOffset > 0 ? tempCon.textContent.length : tempOffset;
677
+ }
678
+
679
+ let format = this.format.getLine(tempCon, null);
680
+ if (format === this.format.getBlock(format, null)) {
681
+ format = dom.utils.createElement(dom.check.isTableCell(format) ? 'DIV' : this.options.get('defaultLine'));
682
+ tempCon.parentNode.insertBefore(format, tempCon);
683
+ format.appendChild(tempCon);
684
+ }
685
+ }
686
+
687
+ if (dom.check.isBreak(tempCon)) {
688
+ const emptyText = dom.utils.createTextNode(unicode.zeroWidthSpace);
689
+ tempCon.parentNode.insertBefore(emptyText, tempCon);
690
+ tempCon = emptyText;
691
+ tempOffset = 1;
692
+ if (onlyBreak && !tempCon.previousSibling) {
693
+ dom.utils.removeItem(endCon);
694
+ }
695
+ }
696
+ }
697
+
698
+ // set endContainer
699
+ endCon = tempCon;
700
+ endOff = tempOffset;
701
+
702
+ // set Range
703
+ this.setRange(startCon, startOff, endCon, endOff);
704
+ return true;
705
+ },
706
+
707
+ constructor: Selection_
708
+ };
709
+
710
+ export default Selection_;