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,891 +1,858 @@
1
- /**
2
- * @fileoverview Offset class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { getParentElement } from '../../helper/dom/domQuery';
7
- import { isWysiwygFrame, isElement } from '../../helper/dom/domCheck';
8
- import { hasClass, addClass, removeClass, getClientSize } from '../../helper/dom/domUtils';
9
- import { numbers } from '../../helper';
10
- import { _w, _d } from '../../helper/env';
11
-
12
- /**
13
- * @typedef {Omit<Offset & Partial<__se__EditorInjector>, 'offset'>} OffsetThis
14
- */
15
-
16
- /**
17
- * @typedef {Object} RectsInfo Bounding rectangle information of the selection range.
18
- * @property {number} rects.left - The left position of the selection.
19
- * @property {number} rects.right - The right position of the selection.
20
- * @property {number} rects.top - The top position of the selection.
21
- * @property {number} rects.bottom - The bottom position of the selection.
22
- * @property {boolean} [rects.noText] - Whether the selection contains text.
23
- * @property {number} [rects.width] - The width of the selection.
24
- * @property {number} [rects.height] - The height of the selection.
25
- */
26
-
27
- /**
28
- * @typedef {Object} OffsetInfo
29
- * @property {number} top - The top position of the node relative to the entire document, including iframe offsets.
30
- * @property {number} left - The left position of the node relative to the entire document, including iframe offsets.
31
- */
32
-
33
- /**
34
- * @typedef {Object} OffsetLocalInfo
35
- * @property {number} top - The top position of the node relative to the WYSIWYG editor.
36
- * @property {number} left - The left position of the node relative to the WYSIWYG editor.
37
- * @property {number} right - The right position of the node relative to the WYSIWYG editor.
38
- * @property {number} scrollX - The horizontal scroll offset inside the WYSIWYG editor.
39
- * @property {number} scrollY - The vertical scroll offset inside the WYSIWYG editor.
40
- */
41
-
42
- /**
43
- * @typedef {Object} OffsetGlobalInfo
44
- * @property {number} top - The top position of the element relative to the entire document.
45
- * @property {number} left - The left position of the element relative to the entire document.
46
- * @property {number} width - The total width of the element, including its content, padding, and border.
47
- * @property {number} height - The total height of the element, including its content, padding, and border.
48
- * @property {number} scrollTop - The amount of vertical scrolling applied to the element.
49
- * @property {number} scrollLeft - The amount of horizontal scrolling applied to the element.
50
- */
51
-
52
- /**
53
- * @typedef {Object} OffsetGlobalScrollInfo
54
- * @property {number} top - Total top scroll distance
55
- * @property {number} left - Total left scroll distance
56
- * @property {number} width - Total width including scrollable area
57
- * @property {number} height - Total height including scrollable area
58
- * @property {number} x - Horizontal offset from the top reference element
59
- * @property {number} y - Vertical offset from the top reference element
60
- * @property {HTMLElement|Window|null} ohOffsetEl - Element or window used as the vertical scroll reference
61
- * @property {HTMLElement|Window|null} owOffsetEl - Element or window used as the horizontal scroll reference
62
- * @property {number} oh - Height of the vertical scrollable area (clientHeight)
63
- * @property {number} ow - Width of the horizontal scrollable area (clientWidth)
64
- * @property {boolean} heightEditorRefer - Indicates if the vertical scroll reference is the editor area
65
- * @property {boolean} widthEditorRefer - Indicates if the horizontal scroll reference is the editor area
66
- * @property {number} ts - Top position of the height offset element relative to the viewport
67
- * @property {number} ls - Left position of the width offset element relative to the viewport
68
- */
69
-
70
- /**
71
- * @typedef {Object} OffsetWWScrollInfo
72
- * @property {number} top - The top scroll offset inside the WYSIWYG editor.
73
- * @property {number} left - The left scroll offset inside the WYSIWYG editor.
74
- * @property {number} width - The total width of the WYSIWYG editor's scrollable area.
75
- * @property {number} height - The total height of the WYSIWYG editor's scrollable area.
76
- * @property {number} bottom - The sum of `top` and `height`, representing the bottom-most scrollable position.
77
- * @property {RectsInfo} rects - The bounding rectangle of the editor's visible area.
78
- */
79
-
80
- /**
81
- * @constructor
82
- * @this {OffsetThis}
83
- * @description Offset class, get the position of the element
84
- * @param {__se__EditorCore} editor - The root editor instance
85
- */
86
- function Offset(editor) {
87
- CoreInjector.call(this, editor);
88
-
89
- // members
90
- this._scrollEvent = null;
91
- this._elTop = 0;
92
- this._scrollY = 0;
93
- this._isFixed = false;
94
- }
95
-
96
- Offset.prototype = {
97
- /**
98
- * @this {OffsetThis}
99
- * @description Gets the position just outside the argument's internal editor (wysiwygFrame).
100
- * @param {Node} node Target node.
101
- * @returns {OffsetInfo} Position relative to the editor frame.
102
- */
103
- get(node) {
104
- const wFrame = this.editor.frameContext.get('wysiwygFrame');
105
- const iframe = /iframe/i.test(wFrame?.nodeName);
106
- const off = this.getLocal(node);
107
-
108
- return {
109
- left: off.left + (iframe ? wFrame.parentElement.offsetLeft : 0),
110
- top: off.top + (iframe ? wFrame.parentElement.offsetTop : 0)
111
- };
112
- },
113
-
114
- /**
115
- * @this {OffsetThis}
116
- * @description Gets the position inside the internal editor of the argument.
117
- * @param {Node} node Target node.
118
- * @returns {OffsetLocalInfo} Position relative to the WYSIWYG editor.
119
- */
120
- getLocal(node) {
121
- const target = /** @type {HTMLElement} */ (node);
122
- let offsetLeft = 0;
123
- let offsetTop = 0;
124
- let l = 0;
125
- let t = 0;
126
- let r = 0;
127
- let offsetElement = target.nodeType === 3 ? target.parentElement : target;
128
- const targetWidth = target.offsetWidth;
129
- const wysiwyg = getParentElement(target, isWysiwygFrame.bind(this));
130
- const self = offsetElement;
131
-
132
- while (offsetElement && !hasClass(offsetElement, 'se-wrapper') && offsetElement !== wysiwyg) {
133
- offsetLeft += offsetElement.offsetLeft - (self !== offsetElement ? offsetElement.scrollLeft : 0);
134
- offsetTop += offsetElement.offsetTop + (self !== offsetElement ? offsetElement.scrollTop : 0);
135
- offsetElement = /** @type {HTMLElement} */ (offsetElement.offsetParent);
136
- }
137
-
138
- const wwFrame = this.editor.frameContext.get('wysiwygFrame');
139
- if (this.editor.frameContext.get('wysiwyg').contains(target)) {
140
- l = wwFrame.offsetLeft;
141
- t = wwFrame.offsetTop;
142
- r = wwFrame.parentElement.offsetWidth - (wwFrame.offsetLeft + wwFrame.offsetWidth);
143
- }
144
-
145
- const eventWysiwyg = this.editor.frameContext.get('eventWysiwyg');
146
- offsetLeft += l;
147
- offsetTop += t - (wysiwyg ? wysiwyg.scrollTop : 0);
148
- return {
149
- left: offsetLeft,
150
- top: offsetTop,
151
- right: offsetElement?.offsetWidth ? offsetElement.offsetWidth - (offsetLeft - l + targetWidth) + r : 0,
152
- scrollX: eventWysiwyg.scrollX || eventWysiwyg.scrollLeft || 0,
153
- scrollY: eventWysiwyg.scrollY || eventWysiwyg.scrollTop || 0
154
- };
155
- },
156
-
157
- /**
158
- * @this {OffsetThis}
159
- * @description Returns the position of the argument relative to the global document.
160
- * @param {?Node=} node Target element.
161
- * @returns {OffsetGlobalInfo} Global position and scroll values.
162
- */
163
- getGlobal(node) {
164
- const topArea = this.editor.frameContext.get('topArea');
165
- const wFrame = this.editor.frameContext.get('wysiwygFrame');
166
-
167
- let isTop = false;
168
- let targetAbs = false;
169
- if (!node) node = topArea;
170
- if (node === topArea) isTop = true;
171
- if (!isTop && isElement(node)) {
172
- targetAbs = _w.getComputedStyle(node).position === 'absolute';
173
- }
174
-
175
- let element = /** @type {HTMLElement} */ (node);
176
- const w = element.offsetWidth;
177
- const h = element.offsetHeight;
178
- let t = 0,
179
- l = 0,
180
- st = 0,
181
- sl = 0;
182
-
183
- while (element) {
184
- t += element.offsetTop;
185
- l += element.offsetLeft;
186
- st += element.scrollTop;
187
- sl += element.scrollLeft;
188
- element = /** @type {HTMLElement} */ (element.offsetParent);
189
- }
190
-
191
- if (!targetAbs && !isTop && /^iframe$/i.test(wFrame.nodeName) && this.editor.frameContext.get('wysiwyg').contains(element)) {
192
- element = this.editor.frameContext.get('wrapper');
193
- while (element) {
194
- t += element.offsetTop;
195
- l += element.offsetLeft;
196
- element = /** @type {HTMLElement} */ (element.offsetParent);
197
- }
198
- }
199
-
200
- return {
201
- top: t + st,
202
- left: l + sl,
203
- width: w,
204
- height: h,
205
- scrollTop: st,
206
- scrollLeft: sl
207
- };
208
- },
209
-
210
- /**
211
- * @this {OffsetThis}
212
- * @description Gets the current editor-relative scroll offset.
213
- * @param {?Node=} node Target element.
214
- * @returns {OffsetGlobalScrollInfo} Global scroll information.
215
- */
216
- getGlobalScroll(node) {
217
- const topArea = this.editor.frameContext.get('topArea');
218
- let isTop = false;
219
- let targetAbs = false;
220
- if (!node) node = topArea;
221
- if (node === topArea) isTop = true;
222
- if (!isTop && isElement(node)) {
223
- targetAbs = _w.getComputedStyle(node).position === 'absolute';
224
- }
225
-
226
- const element = /** @type {HTMLElement} */ (node);
227
- let t = 0,
228
- l = 0,
229
- h = 0,
230
- w = 0,
231
- x = 0,
232
- y = 0,
233
- oh = 0,
234
- ow = 0,
235
- ohOffsetEl = null,
236
- owOffsetEl = null,
237
- ohel = null,
238
- owel = null,
239
- el = element;
240
-
241
- while (el) {
242
- t += el.scrollTop;
243
- l += el.scrollLeft;
244
- h += el.scrollHeight;
245
- w += el.scrollWidth;
246
- if (el.scrollTop > 0) {
247
- y += el.offsetTop;
248
- }
249
- if (el.scrollHeight > el.clientHeight) {
250
- oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
251
- ohOffsetEl = ohel || ohOffsetEl || el;
252
- ohel = el;
253
- }
254
- if (el.scrollLeft > 0) {
255
- x += el.offsetLeft;
256
- }
257
- if (el.scrollWidth > el.clientWidth) {
258
- ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
259
- owOffsetEl = owel || owOffsetEl || el;
260
- owel = el;
261
- }
262
- el = el.parentElement;
263
- }
264
-
265
- if (!targetAbs && !isTop && /^iframe$/i.test(this.editor.frameContext.get('wysiwygFrame').nodeName)) {
266
- el = this.editor.frameContext.get('wrapper');
267
- ohOffsetEl = owOffsetEl = topArea;
268
- while (el) {
269
- t += el.scrollTop;
270
- l += el.scrollLeft;
271
- h += el.scrollHeight;
272
- w += el.scrollWidth;
273
- if (el.scrollTop > 0) {
274
- y += el.offsetTop;
275
- }
276
- if (el.scrollHeight > el.clientHeight) {
277
- oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
278
- ohel = el;
279
- }
280
- if (el.scrollLeft > 0) {
281
- x += el.offsetLeft;
282
- }
283
- if (el.scrollWidth > el.clientWidth) {
284
- ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
285
- owel = el;
286
- }
287
- el = el.parentElement;
288
- }
289
- }
290
-
291
- el = /** @type {HTMLElement} */ (this._shadowRoot?.host);
292
- if (el) ohOffsetEl = owOffsetEl = topArea;
293
- while (el) {
294
- t += el.scrollTop;
295
- l += el.scrollLeft;
296
- h += el.scrollHeight;
297
- w += el.scrollWidth;
298
- if (el.scrollTop > 0) {
299
- y += el.offsetTop;
300
- }
301
- if (el.scrollHeight > el.clientHeight) {
302
- oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
303
- ohel = el;
304
- }
305
- if (el.scrollLeft > 0) {
306
- x += el.offsetLeft;
307
- }
308
- if (el.scrollWidth > el.clientWidth) {
309
- ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
310
- owel = el;
311
- }
312
- el = el.parentElement;
313
- }
314
-
315
- const heightEditorRefer = topArea.contains(ohOffsetEl);
316
- const widthEditorRefer = topArea.contains(owOffsetEl);
317
- ohOffsetEl = heightEditorRefer ? topArea : ohOffsetEl;
318
- owOffsetEl = widthEditorRefer ? topArea : owOffsetEl;
319
- const ts = !ohOffsetEl ? 0 : ohOffsetEl.getBoundingClientRect().top + (!ohOffsetEl.parentElement || /^html$/i.test(ohOffsetEl.parentElement.nodeName) ? _w.scrollY : 0);
320
- const ls = !owOffsetEl ? 0 : owOffsetEl.getBoundingClientRect().left + (!owOffsetEl.parentElement || /^html$/i.test(owOffsetEl.parentElement.nodeName) ? _w.scrollX : 0);
321
-
322
- oh = heightEditorRefer ? topArea.clientHeight : oh;
323
- ow = widthEditorRefer ? topArea.clientWidth : ow;
324
-
325
- const clientSize = getClientSize(this.editor.frameContext.get('_wd'));
326
- return {
327
- top: t,
328
- left: l,
329
- ts: ts,
330
- ls: ls,
331
- width: w,
332
- height: h,
333
- x: x,
334
- y: y,
335
- ohOffsetEl: targetAbs ? window : ohOffsetEl,
336
- owOffsetEl: targetAbs ? window : owOffsetEl,
337
- oh: targetAbs ? clientSize.h : oh,
338
- ow: targetAbs ? clientSize.w : ow,
339
- heightEditorRefer: heightEditorRefer,
340
- widthEditorRefer: widthEditorRefer
341
- };
342
- },
343
-
344
- /**
345
- * @this {OffsetThis}
346
- * @description Get the scroll info of the WYSIWYG area.
347
- * @returns {OffsetWWScrollInfo} Scroll information within the editor.
348
- */
349
- getWWScroll() {
350
- const eventWysiwyg = this.editor.frameContext.get('wysiwyg');
351
- const rects = this.selection.getRects(eventWysiwyg, 'start').rects;
352
- const top = eventWysiwyg.scrollY || eventWysiwyg.scrollTop || 0;
353
- const height = eventWysiwyg.scrollHeight || 0;
354
-
355
- return {
356
- top,
357
- left: eventWysiwyg.scrollX || eventWysiwyg.scrollLeft || 0,
358
- width: eventWysiwyg.scrollWidth || 0,
359
- height,
360
- bottom: top + height,
361
- rects
362
- };
363
- },
364
-
365
- /**
366
- * @this {OffsetThis}
367
- * @description Sets the relative position of an element
368
- * @param {HTMLElement} element Element to position
369
- * @param {HTMLElement} e_container Element's root container
370
- * @param {HTMLElement} target Target element to position against
371
- * @param {HTMLElement} t_container Target's root container
372
- * @param {boolean} _reload Whether to reload position
373
- */
374
- setRelPosition(element, e_container, target, t_container, _reload) {
375
- this._scrollY = _w.scrollY;
376
- let wy = 0;
377
- let tCon = t_container;
378
- do {
379
- if ((this._isFixed = /^fixed$/i.test(_w.getComputedStyle(tCon).position))) {
380
- wy += this._scrollY;
381
- break;
382
- }
383
- } while (!hasClass(tCon, 'sun-editor') && (tCon = tCon.parentElement));
384
-
385
- if (!_reload) {
386
- this.__removeGlobalEvent();
387
- this._scrollEvent = this.editor.eventManager.addGlobalEvent('scroll', FixedScroll.bind(this, element, e_container, target, t_container), false);
388
- }
389
-
390
- const ew = element.offsetWidth;
391
- const tw = target.offsetWidth;
392
- const tl = this.getGlobal(target).left;
393
- const tcleft = this.getGlobal(t_container).left;
394
-
395
- // left
396
- if (this.options.get('_rtl')) {
397
- const rtlW = ew > tw ? ew - tw : 0;
398
- const rtlL = rtlW > 0 ? 0 : tw - ew;
399
- element.style.left = `${tl - rtlW + rtlL + tcleft}px`;
400
- if (tcleft > this.getGlobal(element).left) {
401
- element.style.left = tcleft + 'px';
402
- }
403
- } else {
404
- const cw = t_container.offsetWidth + tcleft;
405
- const overLeft = cw <= ew ? 0 : cw - (tl + ew);
406
- if (overLeft < 0) element.style.left = `${tl + overLeft + tcleft}px`;
407
- else element.style.left = `${tl}px`;
408
- }
409
-
410
- // top
411
- const isSameContainer = t_container.contains(element);
412
- const containerTop = isSameContainer ? this.getGlobal(e_container).top : 0;
413
- const elHeight = element.offsetHeight;
414
- const scrollTop = this.getGlobalScroll().top;
415
- let bt = wy;
416
- let offsetEl = target;
417
- while (offsetEl && offsetEl !== e_container) {
418
- bt += offsetEl.offsetTop;
419
- offsetEl = /** @type {HTMLElement} */ (offsetEl.offsetParent);
420
- }
421
-
422
- const menuHeight_bottom = getClientSize(_d).h - (containerTop - scrollTop + bt + target.offsetHeight);
423
- if (menuHeight_bottom < elHeight) {
424
- let menuTop = -1 * (elHeight - bt + 3);
425
- const insTop = containerTop - scrollTop + menuTop;
426
- const menuHeight_top = elHeight + (insTop < 0 ? insTop : 0);
427
-
428
- if (menuHeight_top > menuHeight_bottom) {
429
- element.style.height = `${menuHeight_top}px`;
430
- menuTop = -1 * (menuHeight_top - bt + 3);
431
- } else {
432
- element.style.height = `${menuHeight_bottom}px`;
433
- menuTop = bt + target.offsetHeight;
434
- }
435
-
436
- element.style.top = `${menuTop}px`;
437
- } else {
438
- element.style.top = `${bt + target.offsetHeight}px`;
439
- }
440
-
441
- if (this._isFixed) {
442
- this._elTop = element.offsetTop;
443
- }
444
- },
445
-
446
- /**
447
- * @this {OffsetThis}
448
- * @description Sets the absolute position of an element
449
- * @param {HTMLElement} element Element to position
450
- * @param {HTMLElement} target Target element
451
- * @param {Object} params Position parameters
452
- * @param {boolean} [params.isWWTarget=false] Whether the target is within the editor's WYSIWYG area
453
- * @param {{left:number, top:number}} [params.addOffset={left:0, top:0}] Additional offset
454
- * @param {"bottom"|"top"} [params.position="bottom"] Position ('bottom'|'top')
455
- * @param {*} params.inst Instance object of caller
456
- * @returns {{position: "top" | "bottom"} | undefined} Success -> {position: current position}
457
- */
458
- setAbsPosition(element, target, params) {
459
- const addOffset = {
460
- left: 0,
461
- top: 0,
462
- ...params.addOffset
463
- };
464
- const position = params.position || 'bottom';
465
- const inst = params.inst;
466
- const isLTR = !this.options.get('_rtl');
467
-
468
- if (!isLTR) {
469
- addOffset.left *= -1;
470
- }
471
-
472
- const isWWTarget = this.editor.frameContext.get('wrapper').contains(target) || params.isWWTarget;
473
- const isCtrlTarget = getParentElement(target, '.se-controller');
474
- const isTargetAbs = isWWTarget && !isCtrlTarget;
475
- const clientSize = getClientSize(_d);
476
- const wwScroll = isTargetAbs ? this.getWWScroll() : this._getWindowScroll();
477
- const targetRect = isCtrlTarget ? target.getBoundingClientRect() : this.selection.getRects(target, 'start').rects;
478
- const targetOffset = this.getGlobal(target);
479
- const arrow = /** @type {HTMLElement} */ (hasClass(element.firstElementChild, 'se-arrow') ? element.firstElementChild : null);
480
-
481
- // top ----------------------------------------------------------------------------------------------------
482
- const ah = arrow ? arrow.offsetHeight : 0;
483
- const elH = element.offsetHeight;
484
- const targetH = target.offsetHeight;
485
- // margin
486
- const tmtw = targetRect.top;
487
- const tmbw = clientSize.h - targetRect.bottom;
488
- const toolbarH = !this.editor.toolbar._sticky && (this.editor.isBalloon || this.editor.isInline) ? 0 : this.context.get('toolbar.main').offsetHeight;
489
-
490
- // check margin
491
- const { rmt, rmb, rt } = this._getVMargin(tmtw, tmbw, toolbarH, clientSize, targetRect, isTargetAbs, wwScroll);
492
- if (isWWTarget && (rmb + targetH <= 0 || rmt + rt + targetH <= 0)) return;
493
-
494
- let t = addOffset.top;
495
- let y = 0;
496
- let arrowDir = '';
497
- if (position === 'bottom') {
498
- arrowDir = 'up';
499
- t += targetRect.bottom + ah + _w.scrollY;
500
- y = rmb - (elH + ah);
501
- if (y < 0) {
502
- arrowDir = 'down';
503
- t -= targetH + elH + ah * 2;
504
- y = toolbarH + rmt - (elH + ah);
505
- if (y < 0) {
506
- arrowDir = '';
507
- t -= y;
508
- }
509
- }
510
- } else {
511
- arrowDir = 'down';
512
- t += targetRect.top - elH - ah + _w.scrollY;
513
- y = rmt - (elH + ah);
514
- if (y < 0) {
515
- arrowDir = 'up';
516
- t += targetH + elH + ah * 2;
517
- y = rmb - (elH + ah);
518
- if (y < 0) {
519
- arrowDir = '';
520
- t += y;
521
- }
522
- }
523
- }
524
-
525
- this._setArrow(arrow, arrowDir);
526
- element.style.top = `${t}px`;
527
-
528
- // left ----------------------------------------------------------------------------------------------------
529
- const radius = (element.nodeType === 1 ? numbers.get(_w.getComputedStyle(element).borderRadius) : 0) || 0;
530
- const targetW = targetOffset.width;
531
- const elW = element.offsetWidth;
532
- const aw = arrow ? arrow.offsetWidth : 0;
533
- // margin
534
- const tmlw = targetRect.left;
535
- const tmrw = clientSize.w - targetRect.right;
536
- let rml, rmr;
537
- if (this.editor.frameContext.get('isFullScreen')) {
538
- rml = tmlw;
539
- rmr = tmrw;
540
- } else {
541
- rml = targetRect.left;
542
- rmr = clientSize.w - targetRect.right;
543
- }
544
-
545
- if (isWWTarget && (rml + targetW <= 0 || rmr + targetW <= 0)) return;
546
- if (arrow) {
547
- arrow.style.left = '';
548
- arrow.style.right = '';
549
- }
550
-
551
- let l = addOffset.left;
552
- let x = 0;
553
- let ax = 0;
554
- let awLimit = 0;
555
- if (isLTR) {
556
- l += targetRect.left + _w.scrollX - (rml < 0 ? rml : 0);
557
- x = targetW + rml;
558
- if (x < aw) {
559
- awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
560
- ax = awLimit;
561
- }
562
- x = targetW + rmr - elW;
563
- if (x < 0) {
564
- l += x;
565
- awLimit = elW - 1 - (aw / 2 + (radius <= 2 ? 0 : radius - 2));
566
- ax = -(x - aw / 2);
567
- ax = ax > awLimit ? awLimit : ax;
568
- }
569
- if (arrow && ax > 0) arrow.style.left = ax + 'px';
570
- } else {
571
- l += targetRect.right - elW + _w.scrollX + (rmr < 0 ? rmr : 0);
572
- x = targetW + rmr;
573
- if (x < aw) {
574
- awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
575
- ax = awLimit;
576
- }
577
- x = targetW + rml - elW;
578
- if (x < 0) {
579
- l -= x;
580
- awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
581
- ax = -(x - aw / 2);
582
- ax = ax < awLimit ? awLimit : ax > elW - awLimit ? elW - awLimit : ax;
583
- }
584
- if (arrow && ax > 0) arrow.style.right = ax + 'px';
585
- }
586
-
587
- element.style.left = `${l}px`;
588
- inst.__offset = {
589
- left: element.offsetLeft + wwScroll.left,
590
- top: element.offsetTop + wwScroll.top,
591
- addOffset: addOffset
592
- };
593
-
594
- return { position: arrowDir === 'up' ? 'bottom' : 'top' };
595
- },
596
-
597
- /**
598
- * @this {OffsetThis}
599
- * @description Sets the position of an element relative to a range
600
- * @param {HTMLElement} element Element to position
601
- * @param {?Range} range Range to position against.
602
- * - if null, the current selection range is used
603
- * @param {Object} [options={}] Position options
604
- * @param {"bottom"|"top"} [options.position="bottom"] Position ('bottom'|'top')
605
- * @param {number} [options.addTop=0] Additional top offset
606
- * @returns {boolean} Success / Failure
607
- */
608
- setRangePosition(element, range, { position, addTop } = {}) {
609
- element.style.top = '-10000px';
610
- element.style.visibility = 'hidden';
611
- element.style.display = 'block';
612
-
613
- let positionTop = position === 'top';
614
- range = range || this.selection.getRange();
615
- const rectsObj = this.selection.getRects(range, positionTop ? 'start' : 'end');
616
- positionTop = rectsObj.position === 'start';
617
-
618
- const isFullScreen = this.editor.frameContext.get('isFullScreen');
619
- const topArea = this.editor.frameContext.get('topArea');
620
- const rects = rectsObj.rects;
621
- const scrollLeft = isFullScreen ? 0 : rectsObj.scrollLeft;
622
- const scrollTop = isFullScreen ? 0 : rectsObj.scrollTop;
623
- const editorWidth = topArea.offsetWidth;
624
- const offsets = this.getGlobal(topArea);
625
- const editorLeft = offsets.left;
626
- const toolbarWidth = element.offsetWidth;
627
- const toolbarHeight = element.offsetHeight;
628
-
629
- this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
630
- if (this.getGlobal(element).top - offsets.top < 0) {
631
- positionTop = !positionTop;
632
- this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
633
- }
634
-
635
- if (toolbarWidth !== element.offsetWidth || toolbarHeight !== element.offsetHeight) {
636
- this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
637
- }
638
-
639
- // check margin
640
- const isTargetAbs = !this.carrierWrapper.contains(element);
641
- const clientSize = getClientSize(_d);
642
- const wwScroll = isTargetAbs ? this.getWWScroll() : this._getWindowScroll();
643
- const targetH = rects.height;
644
- const tmtw = rects.top;
645
- const tmbw = clientSize.h - rects.bottom;
646
- const toolbarH = !this.editor.toolbar._sticky && (this.editor.isBalloon || this.editor.isInline) ? 0 : this.context.get('toolbar.main').offsetHeight;
647
-
648
- const { rmt, rmb, rt } = this._getVMargin(tmtw, tmbw, toolbarH, clientSize, rects, isTargetAbs, wwScroll);
649
- if (rmb + targetH <= 0 || rmt + rt + targetH <= 0) return;
650
-
651
- _w.setTimeout(() => {
652
- element.style.visibility = '';
653
- }, 0);
654
-
655
- return true;
656
- },
657
-
658
- /**
659
- * @private
660
- * @this {OffsetThis}
661
- * @description Sets the position of an element relative to the selection range in the editor.
662
- * - This method calculates the top and left offsets for the element, ensuring it
663
- * - does not overflow the editor boundaries and adjusts the arrow positioning accordingly.
664
- * @param {boolean} isDirTop - Determines whether the element should be positioned above (`true`) or below (`false`) the target.
665
- * @param {RectsInfo} rects - Bounding rectangle information of the selection range.
666
- * @param {HTMLElement} element - The element to be positioned.
667
- * @param {number} editorLeft - The left position of the editor.
668
- * @param {number} editorWidth - The width of the editor.
669
- * @param {number} scrollLeft - The horizontal scroll offset.
670
- * @param {number} scrollTop - The vertical scroll offset.
671
- * @param {number} [addTop=0] - Additional top margin adjustment.
672
- */
673
- _setOffsetOnRange(isDirTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop = 0) {
674
- const padding = 1;
675
- const arrow = /** @type {HTMLElement} */ (element.querySelector('.se-arrow '));
676
- const arrowMargin = Math.round(arrow.offsetWidth / 2);
677
- const elW = element.offsetWidth;
678
- const elH = rects.noText && !isDirTop ? 0 : element.offsetHeight;
679
-
680
- const absoluteLeft = (isDirTop ? rects.left : rects.right) - editorLeft - elW / 2 + scrollLeft;
681
- const overRight = absoluteLeft + elW - editorWidth;
682
-
683
- let t = (isDirTop ? rects.top - elH - arrowMargin : rects.bottom + arrowMargin) - (rects.noText ? 0 : addTop) + scrollTop;
684
- const l = absoluteLeft < 0 ? padding : overRight < 0 ? absoluteLeft : absoluteLeft - overRight - padding - 1;
685
-
686
- let resetTop = false;
687
- const space = t + (isDirTop ? this.getGlobal(this.editor.frameContext.get('topArea')).top : element.offsetHeight - this.editor.frameContext.get('wysiwyg').offsetHeight);
688
- if (!isDirTop && space > 0 && this._getPageBottomSpace() < space) {
689
- isDirTop = true;
690
- resetTop = true;
691
- } else if (isDirTop && _d.documentElement.offsetTop > space) {
692
- isDirTop = false;
693
- resetTop = true;
694
- }
695
-
696
- if (resetTop) t = (isDirTop ? rects.top - elH - arrowMargin : rects.bottom + arrowMargin) - (rects.noText ? 0 : addTop) + scrollTop;
697
-
698
- element.style.left = Math.floor(l) + 'px';
699
- element.style.top = Math.floor(t) + 'px';
700
-
701
- if (isDirTop) {
702
- removeClass(arrow, 'se-arrow-up');
703
- addClass(arrow, 'se-arrow-down');
704
- } else {
705
- removeClass(arrow, 'se-arrow-down');
706
- addClass(arrow, 'se-arrow-up');
707
- }
708
-
709
- const arrow_left = Math.floor(elW / 2 + (absoluteLeft - l));
710
- arrow.style.left = (arrow_left + arrowMargin > element.offsetWidth ? element.offsetWidth - arrowMargin : arrow_left < arrowMargin ? arrowMargin : arrow_left) + 'px';
711
- },
712
-
713
- /**
714
- * @private
715
- * @this {OffsetThis}
716
- * @description Get available space from page bottom
717
- * @returns {number} Available space
718
- */
719
- _getPageBottomSpace() {
720
- const topArea = this.editor.frameContext.get('topArea');
721
- return _d.documentElement.scrollHeight - (this.getGlobal(topArea).top + topArea.offsetHeight);
722
- },
723
-
724
- /**
725
- * @private
726
- * @this {OffsetThis}
727
- * @description Calculates the vertical margin offsets for the target element relative to the editor frame.
728
- * - This method determines the top and bottom margins based on various conditions such as
729
- * - fullscreen mode, iframe usage, toolbar height, and scroll positions.
730
- * @param {number} tmtw Top margin to window
731
- * @param {number} tmbw Bottom margin to window
732
- * @param {number} toolbarH Toolbar height
733
- * @param {{w: number, h: number}} clientSize documentElement.clientWidth, documentElement.clientHeight
734
- * @param {RectsInfo} targetRect Target rect object
735
- * @param {boolean} isTargetAbs Is target absolute position
736
- * @param {OffsetWWScrollInfo} wwScroll WYSIWYG scroll info
737
- * @returns {{rmt:number, rmb:number, rt:number}} Margin values (rmt: top margin, rmb: bottom margin, rt: Toolbar height offset adjustment)
738
- */
739
- _getVMargin(tmtw, tmbw, toolbarH, clientSize, targetRect, isTargetAbs, wwScroll) {
740
- let rmt = 0;
741
- let rmb = 0;
742
- let rt = 0;
743
- if (this.editor.frameContext.get('isFullScreen')) {
744
- rmt = tmtw - toolbarH;
745
- rmb = tmbw;
746
- } else {
747
- const isIframe = isTargetAbs && this.editor.frameOptions.get('iframe');
748
- const tMargin = targetRect.top;
749
- const bMargin = clientSize.h - targetRect.bottom;
750
- const editorOffset = this.getGlobal();
751
- const editorScroll = this.getGlobalScroll();
752
- const statusBarH = this.editor.frameContext.get('statusbar')?.offsetHeight || 0;
753
-
754
- if (isIframe) {
755
- const emt = editorOffset.top - editorScroll.top - editorScroll.ts;
756
- const editorH = this.editor.frameContext.get('topArea').offsetHeight;
757
- rmt = targetRect.top - emt;
758
- rmb = bMargin - (editorScroll.oh - (editorH + emt) + statusBarH);
759
- } else {
760
- rt = !this.editor.toolbar._sticky && !this.options.get('toolbar_container') ? toolbarH : 0;
761
- const wst = !isTargetAbs && /\d+/.test(this.editor.frameOptions.get('height')) ? editorOffset.top - _w.scrollY + rt : 0;
762
- const wsb = !isTargetAbs && /\d+/.test(this.editor.frameOptions.get('height')) ? _w.innerHeight - (editorOffset.top + editorOffset.height - _w.scrollY) : 0;
763
- let st = wst;
764
- if (toolbarH > wst) {
765
- if (this.editor.toolbar._sticky) {
766
- st = toolbarH;
767
- toolbarH = 0;
768
- } else {
769
- st = wst + toolbarH;
770
- }
771
- } else if (this.options.get('toolbar_container')) {
772
- toolbarH = 0;
773
- } else {
774
- st = wst + (this.editor.toolbar._sticky ? toolbarH : 0);
775
- }
776
-
777
- rmt = targetRect.top - st;
778
- rmb = wwScroll.rects.bottom - targetRect.bottom - wsb - statusBarH;
779
- }
780
-
781
- // display margin
782
- rmt = (rmt > 0 ? tMargin : rmt) - toolbarH;
783
- rmb = rmb > 0 ? bMargin : rmb;
784
- }
785
-
786
- return {
787
- rmt,
788
- rmb,
789
- rt
790
- };
791
- },
792
-
793
- /**
794
- * @private
795
- * @this {OffsetThis}
796
- * @description Sets the visibility and direction of the arrow element.
797
- * - This method applies the appropriate class (`se-arrow-up` or `se-arrow-down`)
798
- * - based on the specified direction key and adjusts the visibility of the arrow.
799
- * @param {HTMLElement} arrow - The arrow element to be updated.
800
- * @param {string} key - The direction of the arrow. ("up"|"down"|"")
801
- * - Accepts `'up'` for an upward arrow, `'down'` for a downward arrow,
802
- * - or any other value to hide the arrow.
803
- */
804
- _setArrow(arrow, key) {
805
- if (key === 'up') {
806
- if (arrow) arrow.style.visibility = '';
807
- addClass(arrow, 'se-arrow-up');
808
- removeClass(arrow, 'se-arrow-down');
809
- } else if (key === 'down') {
810
- if (arrow) arrow.style.visibility = '';
811
- addClass(arrow, 'se-arrow-down');
812
- removeClass(arrow, 'se-arrow-up');
813
- } else {
814
- if (arrow) arrow.style.visibility = 'hidden';
815
- }
816
- },
817
-
818
- /**
819
- * @private
820
- * @this {OffsetThis}
821
- * @description Retrieves the current window scroll position and viewport size.
822
- * - Returns an object containing the scroll offsets, viewport dimensions, and boundary rects.
823
- * @returns {{
824
- * top: number,
825
- * left: number,
826
- * width: number,
827
- * height: number,
828
- * bottom: number,
829
- * rects: RectsInfo
830
- * }} An object with scroll and viewport information.
831
- */
832
-
833
- _getWindowScroll() {
834
- const viewPort = getClientSize(_d);
835
- return {
836
- top: _w.scrollY,
837
- left: _w.scrollX,
838
- width: viewPort.w,
839
- height: viewPort.h,
840
- bottom: _w.scrollY + viewPort.h,
841
- rects: {
842
- left: 0,
843
- top: 0,
844
- right: _w.innerWidth,
845
- bottom: _w.innerHeight,
846
- noText: true
847
- }
848
- };
849
- },
850
-
851
- /**
852
- * @private
853
- * @this {OffsetThis}
854
- * @description Removes the global scroll event listener from the editor.
855
- * - Resets related scroll tracking properties.
856
- */
857
- __removeGlobalEvent() {
858
- if (this._scrollEvent) {
859
- this._scrollEvent = this.editor.eventManager.removeGlobalEvent(this._scrollEvent);
860
- this._scrollY = 0;
861
- this._elTop = null;
862
- }
863
- },
864
-
865
- constructor: Offset
866
- };
867
-
868
- /**
869
- * @private
870
- * @this {OffsetThis}
871
- * @param {HTMLElement} element - The element to check for a specific class name.
872
- * @param {HTMLElement} e_container - The root container of the element.
873
- * @param {HTMLElement} target - The target element to position against.
874
- * @param {HTMLElement} t_container - The root container of the target element.
875
- */
876
- function FixedScroll(element, e_container, target, t_container) {
877
- const isFixed = /^fixed$/i.test(_w.getComputedStyle(t_container).position);
878
- if (!this._isFixed) {
879
- if (isFixed) {
880
- this.setRelPosition(element, e_container, target, t_container, true);
881
- }
882
- return;
883
- } else if (!isFixed) {
884
- this.setRelPosition(element, e_container, target, t_container, true);
885
- return;
886
- }
887
-
888
- element.style.top = `${this._elTop - (this._scrollY - _w.scrollY - t_container.offsetTop)}px`;
889
- }
890
-
891
- export default Offset;
1
+ /**
2
+ * @fileoverview Offset class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { getParentElement } from '../../helper/dom/domQuery';
7
+ import { isWysiwygFrame, isElement } from '../../helper/dom/domCheck';
8
+ import { hasClass, addClass, removeClass, getClientSize } from '../../helper/dom/domUtils';
9
+ import { numbers } from '../../helper';
10
+ import { _w, _d } from '../../helper/env';
11
+
12
+ /**
13
+ * @typedef {Omit<Offset & Partial<__se__EditorInjector>, 'offset'>} OffsetThis
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} RectsInfo Bounding rectangle information of the selection range.
18
+ * @property {number} rects.left - The left position of the selection.
19
+ * @property {number} rects.right - The right position of the selection.
20
+ * @property {number} rects.top - The top position of the selection.
21
+ * @property {number} rects.bottom - The bottom position of the selection.
22
+ * @property {boolean} [rects.noText] - Whether the selection contains text.
23
+ * @property {number} [rects.width] - The width of the selection.
24
+ * @property {number} [rects.height] - The height of the selection.
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object} OffsetInfo
29
+ * @property {number} top - The top position of the node relative to the entire document, including iframe offsets.
30
+ * @property {number} left - The left position of the node relative to the entire document, including iframe offsets.
31
+ */
32
+
33
+ /**
34
+ * @typedef {Object} OffsetLocalInfo
35
+ * @property {number} top - The top position of the node relative to the WYSIWYG editor.
36
+ * @property {number} left - The left position of the node relative to the WYSIWYG editor.
37
+ * @property {number} right - The right position of the node relative to the WYSIWYG editor.
38
+ * @property {number} scrollX - The horizontal scroll offset inside the WYSIWYG editor.
39
+ * @property {number} scrollY - The vertical scroll offset inside the WYSIWYG editor.
40
+ */
41
+
42
+ /**
43
+ * @typedef {Object} OffsetGlobalInfo
44
+ * @property {number} top - The top position of the element relative to the entire document.
45
+ * @property {number} left - The left position of the element relative to the entire document.
46
+ * @property {number} fixedTop - The top position within the current viewport, without taking scrolling into account.
47
+ * @property {number} fixedLeft - The left position within the current viewport, without taking scrolling into account.
48
+ * @property {number} width - The total width of the element, including its content, padding, and border.
49
+ * @property {number} height - The total height of the element, including its content, padding, and border.
50
+ */
51
+
52
+ /**
53
+ * @typedef {Object} OffsetGlobalScrollInfo
54
+ * @property {number} top - Total top scroll distance
55
+ * @property {number} left - Total left scroll distance
56
+ * @property {number} width - Total width including scrollable area
57
+ * @property {number} height - Total height including scrollable area
58
+ * @property {number} x - Horizontal offset from the top reference element
59
+ * @property {number} y - Vertical offset from the top reference element
60
+ * @property {HTMLElement|Window|null} ohOffsetEl - Element or window used as the vertical scroll reference
61
+ * @property {HTMLElement|Window|null} owOffsetEl - Element or window used as the horizontal scroll reference
62
+ * @property {number} oh - Height of the vertical scrollable area (clientHeight)
63
+ * @property {number} ow - Width of the horizontal scrollable area (clientWidth)
64
+ * @property {boolean} heightEditorRefer - Indicates if the vertical scroll reference is the editor area
65
+ * @property {boolean} widthEditorRefer - Indicates if the horizontal scroll reference is the editor area
66
+ * @property {number} ts - Top position of the height offset element relative to the viewport
67
+ * @property {number} ls - Left position of the width offset element relative to the viewport
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} OffsetWWScrollInfo
72
+ * @property {number} top - The top scroll offset inside the WYSIWYG editor.
73
+ * @property {number} left - The left scroll offset inside the WYSIWYG editor.
74
+ * @property {number} width - The total width of the WYSIWYG editor's scrollable area.
75
+ * @property {number} height - The total height of the WYSIWYG editor's scrollable area.
76
+ * @property {number} bottom - The sum of `top` and `height`, representing the bottom-most scrollable position.
77
+ * @property {RectsInfo} rects - The bounding rectangle of the editor's visible area.
78
+ */
79
+
80
+ /**
81
+ * @constructor
82
+ * @this {OffsetThis}
83
+ * @description Offset class, get the position of the element
84
+ * @param {__se__EditorCore} editor - The root editor instance
85
+ */
86
+ function Offset(editor) {
87
+ CoreInjector.call(this, editor);
88
+ }
89
+
90
+ Offset.prototype = {
91
+ /**
92
+ * @this {OffsetThis}
93
+ * @description Gets the position just outside the argument's internal editor (wysiwygFrame).
94
+ * @param {Node} node Target node.
95
+ * @returns {OffsetInfo} Position relative to the editor frame.
96
+ */
97
+ get(node) {
98
+ const wFrame = this.editor.frameContext.get('wysiwygFrame');
99
+ const iframe = /iframe/i.test(wFrame?.nodeName);
100
+ const off = this.getLocal(node);
101
+
102
+ return {
103
+ left: off.left + (iframe ? wFrame.parentElement.offsetLeft : 0),
104
+ top: off.top + (iframe ? wFrame.parentElement.offsetTop : 0)
105
+ };
106
+ },
107
+
108
+ /**
109
+ * @this {OffsetThis}
110
+ * @description Gets the position inside the internal editor of the argument.
111
+ * @param {Node} node Target node.
112
+ * @returns {OffsetLocalInfo} Position relative to the WYSIWYG editor.
113
+ */
114
+ getLocal(node) {
115
+ const target = /** @type {HTMLElement} */ (node);
116
+ let offsetLeft = 0;
117
+ let offsetTop = 0;
118
+ let l = 0;
119
+ let t = 0;
120
+ let r = 0;
121
+ let offsetElement = target.nodeType === 3 ? target.parentElement : target;
122
+ const targetWidth = target.offsetWidth;
123
+ const wysiwyg = getParentElement(target, isWysiwygFrame.bind(this));
124
+ const self = offsetElement;
125
+
126
+ while (offsetElement && !hasClass(offsetElement, 'se-wrapper') && offsetElement !== wysiwyg) {
127
+ offsetLeft += offsetElement.offsetLeft - (self !== offsetElement ? offsetElement.scrollLeft : 0);
128
+ offsetTop += offsetElement.offsetTop + (self !== offsetElement ? offsetElement.scrollTop : 0);
129
+ offsetElement = /** @type {HTMLElement} */ (offsetElement.offsetParent);
130
+ }
131
+
132
+ const wwFrame = this.editor.frameContext.get('wysiwygFrame');
133
+ if (this.editor.frameContext.get('wysiwyg').contains(target)) {
134
+ l = wwFrame.offsetLeft;
135
+ t = wwFrame.offsetTop;
136
+ r = wwFrame.parentElement.offsetWidth - (wwFrame.offsetLeft + wwFrame.offsetWidth);
137
+ }
138
+
139
+ const eventWysiwyg = this.editor.frameContext.get('eventWysiwyg');
140
+ offsetLeft += l;
141
+ offsetTop += t - (wysiwyg ? wysiwyg.scrollTop : 0);
142
+ return {
143
+ left: offsetLeft,
144
+ top: offsetTop,
145
+ right: offsetElement?.offsetWidth ? offsetElement.offsetWidth - (offsetLeft - l + targetWidth) + r : 0,
146
+ scrollX: eventWysiwyg.scrollLeft || eventWysiwyg.scrollX || 0,
147
+ scrollY: eventWysiwyg.scrollTop || eventWysiwyg.scrollY || 0
148
+ };
149
+ },
150
+
151
+ /**
152
+ * @this {OffsetThis}
153
+ * @description Returns the position of the argument relative to the global document.
154
+ * This is a refactored version using getBoundingClientRect for better performance and accuracy.
155
+ * @param {?Node=} node Target element.
156
+ * @returns {OffsetGlobalInfo} Global position and scroll values.
157
+ */
158
+ getGlobal(node) {
159
+ const topArea = this.editor.frameContext.get('topArea');
160
+ const wFrame = this.editor.frameContext.get('wysiwygFrame');
161
+
162
+ node = node || topArea;
163
+
164
+ if (!isElement(node)) {
165
+ return { top: 0, left: 0, fixedTop: 0, fixedLeft: 0, width: 0, height: 0 };
166
+ }
167
+
168
+ const element = /** @type {HTMLElement} */ (node);
169
+
170
+ const rect = element.getBoundingClientRect();
171
+
172
+ let top = rect.top;
173
+ let left = rect.left;
174
+
175
+ const isIframe = /^iframe$/i.test(wFrame.nodeName);
176
+ if (isIframe && wFrame.contentDocument.contains(element)) {
177
+ const iframeRect = wFrame.getBoundingClientRect();
178
+ top += iframeRect.top;
179
+ left += iframeRect.left;
180
+ }
181
+
182
+ let wy = 0;
183
+ let wx = 0;
184
+ if (!this.editor.frameContext.get('isFullScreen')) {
185
+ wy += _w.scrollY;
186
+ wx += _w.scrollX;
187
+ }
188
+
189
+ return {
190
+ top: top + wy,
191
+ left: left + wx,
192
+ fixedTop: top,
193
+ fixedLeft: left,
194
+ width: element.offsetWidth,
195
+ height: element.offsetHeight
196
+ };
197
+ },
198
+
199
+ /**
200
+ * @this {OffsetThis}
201
+ * @description Gets the current editor-relative scroll offset.
202
+ * @param {?Node=} node Target element.
203
+ * @returns {OffsetGlobalScrollInfo} Global scroll information.
204
+ */
205
+ getGlobalScroll(node) {
206
+ const topArea = this.editor.frameContext.get('topArea');
207
+ let isTop = false;
208
+ let targetAbs = false;
209
+ if (!node) node = topArea;
210
+ if (node === topArea) isTop = true;
211
+ if (!isTop && isElement(node)) {
212
+ targetAbs = _w.getComputedStyle(node).position === 'absolute';
213
+ }
214
+
215
+ const element = /** @type {HTMLElement} */ (node);
216
+ let t = 0,
217
+ l = 0,
218
+ h = 0,
219
+ w = 0,
220
+ x = 0,
221
+ y = 0,
222
+ oh = 0,
223
+ ow = 0,
224
+ ohOffsetEl = null,
225
+ owOffsetEl = null,
226
+ ohel = null,
227
+ owel = null,
228
+ el = element;
229
+
230
+ while (el) {
231
+ t += el.scrollTop;
232
+ l += el.scrollLeft;
233
+ h += el.scrollHeight;
234
+ w += el.scrollWidth;
235
+ if (el.scrollTop > 0) {
236
+ y += el.offsetTop;
237
+ }
238
+ if (el.scrollHeight > el.clientHeight) {
239
+ oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
240
+ ohOffsetEl = ohel || ohOffsetEl || el;
241
+ ohel = el;
242
+ }
243
+ if (el.scrollLeft > 0) {
244
+ x += el.offsetLeft;
245
+ }
246
+ if (el.scrollWidth > el.clientWidth) {
247
+ ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
248
+ owOffsetEl = owel || owOffsetEl || el;
249
+ owel = el;
250
+ }
251
+ el = el.parentElement;
252
+ }
253
+
254
+ if (!targetAbs && !isTop && /^iframe$/i.test(this.editor.frameContext.get('wysiwygFrame').nodeName)) {
255
+ el = this.editor.frameContext.get('wrapper');
256
+ ohOffsetEl = owOffsetEl = topArea;
257
+ while (el) {
258
+ t += el.scrollTop;
259
+ l += el.scrollLeft;
260
+ h += el.scrollHeight;
261
+ w += el.scrollWidth;
262
+ if (el.scrollTop > 0) {
263
+ y += el.offsetTop;
264
+ }
265
+ if (el.scrollHeight > el.clientHeight) {
266
+ oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
267
+ ohel = el;
268
+ }
269
+ if (el.scrollLeft > 0) {
270
+ x += el.offsetLeft;
271
+ }
272
+ if (el.scrollWidth > el.clientWidth) {
273
+ ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
274
+ owel = el;
275
+ }
276
+ el = el.parentElement;
277
+ }
278
+ }
279
+
280
+ el = /** @type {HTMLElement} */ (this.editor._shadowRoot?.host);
281
+ if (el) ohOffsetEl = owOffsetEl = topArea;
282
+ while (el) {
283
+ t += el.scrollTop;
284
+ l += el.scrollLeft;
285
+ h += el.scrollHeight;
286
+ w += el.scrollWidth;
287
+ if (el.scrollTop > 0) {
288
+ y += el.offsetTop;
289
+ }
290
+ if (el.scrollHeight > el.clientHeight) {
291
+ oh = /^html$/i.test(el.nodeName) ? oh || el.clientHeight : el.clientHeight + (ohel ? -ohel.clientTop : 0);
292
+ ohel = el;
293
+ }
294
+ if (el.scrollLeft > 0) {
295
+ x += el.offsetLeft;
296
+ }
297
+ if (el.scrollWidth > el.clientWidth) {
298
+ ow = /^html$/i.test(el.nodeName) ? ow || el.clientWidth : el.clientWidth + (owel ? -owel.clientLeft : 0);
299
+ owel = el;
300
+ }
301
+ el = el.parentElement;
302
+ }
303
+
304
+ const heightEditorRefer = topArea.contains(ohOffsetEl);
305
+ const widthEditorRefer = topArea.contains(owOffsetEl);
306
+ ohOffsetEl = heightEditorRefer ? topArea : ohOffsetEl;
307
+ owOffsetEl = widthEditorRefer ? topArea : owOffsetEl;
308
+ const ts = !ohOffsetEl ? 0 : ohOffsetEl.getBoundingClientRect().top + (!ohOffsetEl.parentElement || /^html$/i.test(ohOffsetEl.parentElement.nodeName) ? _w.scrollY : 0);
309
+ const ls = !owOffsetEl ? 0 : owOffsetEl.getBoundingClientRect().left + (!owOffsetEl.parentElement || /^html$/i.test(owOffsetEl.parentElement.nodeName) ? _w.scrollX : 0);
310
+
311
+ oh = heightEditorRefer ? topArea.clientHeight : oh;
312
+ ow = widthEditorRefer ? topArea.clientWidth : ow;
313
+
314
+ const clientSize = getClientSize(this.editor.frameContext.get('_wd'));
315
+ return {
316
+ top: t,
317
+ left: l,
318
+ ts: ts,
319
+ ls: ls,
320
+ width: w,
321
+ height: h,
322
+ x: x,
323
+ y: y,
324
+ ohOffsetEl: targetAbs ? window : ohOffsetEl,
325
+ owOffsetEl: targetAbs ? window : owOffsetEl,
326
+ oh: targetAbs ? clientSize.h : oh,
327
+ ow: targetAbs ? clientSize.w : ow,
328
+ heightEditorRefer: heightEditorRefer,
329
+ widthEditorRefer: widthEditorRefer
330
+ };
331
+ },
332
+
333
+ /**
334
+ * @this {OffsetThis}
335
+ * @description Get the scroll info of the WYSIWYG area.
336
+ * @returns {OffsetWWScrollInfo} Scroll information within the editor.
337
+ */
338
+ getWWScroll() {
339
+ const eventWysiwyg = this.editor.frameContext.get('wysiwyg');
340
+ const rects = this.selection.getRects(eventWysiwyg, 'start').rects;
341
+ const top = eventWysiwyg.scrollTop || eventWysiwyg.scrollY || 0;
342
+ const height = eventWysiwyg.scrollHeight || eventWysiwyg.document?.documentElement.scrollHeight || 0;
343
+
344
+ return {
345
+ top,
346
+ left: eventWysiwyg.scrollLeft || eventWysiwyg.scrollX || 0,
347
+ width: eventWysiwyg.scrollWidth || eventWysiwyg.document?.documentElement.scrollWidth || 0,
348
+ height,
349
+ bottom: top + height,
350
+ rects
351
+ };
352
+ },
353
+
354
+ /**
355
+ * @this {OffsetThis}
356
+ * @description Sets the relative position of an element
357
+ * @param {HTMLElement} element Element to position
358
+ * @param {HTMLElement} e_container Element's root container
359
+ * @param {HTMLElement} target Target element to position against
360
+ * @param {HTMLElement} t_container Target's root container
361
+ */
362
+ setRelPosition(element, e_container, target, t_container) {
363
+ const isFixedContainer = /^fixed$/i.test(_w.getComputedStyle(t_container).position);
364
+ const tGlobal = this.getGlobal(target);
365
+
366
+ if (isFixedContainer) {
367
+ element.style.position = 'fixed';
368
+ element.style.top = `${tGlobal.fixedTop + tGlobal.height}px`;
369
+ element.style.left = `${tGlobal.fixedLeft}px`;
370
+ } else {
371
+ element.style.position = '';
372
+
373
+ // top
374
+ const isSameContainer = t_container.contains(element);
375
+ const containerTop = isSameContainer ? this.getGlobal(e_container).top : 0;
376
+ const elHeight = element.offsetHeight;
377
+ const scrollTop = this.getGlobalScroll().top;
378
+ const bt = tGlobal.top;
379
+
380
+ const menuHeight_bottom = getClientSize(_d).h - (containerTop - scrollTop + bt + target.offsetHeight);
381
+ if (menuHeight_bottom < elHeight) {
382
+ let menuTop = -1 * (elHeight - bt + 3);
383
+ const insTop = containerTop - scrollTop + menuTop;
384
+ const menuHeight_top = elHeight + (insTop < 0 ? insTop : 0);
385
+
386
+ if (menuHeight_top > menuHeight_bottom) {
387
+ element.style.height = `${menuHeight_top}px`;
388
+ menuTop = -1 * (menuHeight_top - bt + 3);
389
+ } else {
390
+ element.style.height = `${menuHeight_bottom}px`;
391
+ menuTop = bt + target.offsetHeight;
392
+ }
393
+
394
+ element.style.top = `${menuTop}px`;
395
+ } else {
396
+ element.style.top = `${bt + target.offsetHeight}px`;
397
+ }
398
+
399
+ // left
400
+ const ew = element.offsetWidth;
401
+ const tw = target.offsetWidth;
402
+ const tl = tGlobal.left;
403
+ const tcleft = this.getGlobal(t_container).left;
404
+
405
+ if (this.options.get('_rtl')) {
406
+ const rtlW = ew > tw ? ew - tw : 0;
407
+ const rtlL = rtlW > 0 ? 0 : tw - ew;
408
+ element.style.left = `${tl - rtlW + rtlL + tcleft}px`;
409
+ if (tcleft > this.getGlobal(element).left) {
410
+ element.style.left = tcleft + 'px';
411
+ }
412
+ } else {
413
+ const cw = t_container.offsetWidth + tcleft;
414
+ const overLeft = cw <= ew ? 0 : cw - (tl + ew);
415
+ if (overLeft < 0) element.style.left = `${tl + overLeft + tcleft}px`;
416
+ else element.style.left = `${tl}px`;
417
+ }
418
+ }
419
+ },
420
+
421
+ /**
422
+ * @this {OffsetThis}
423
+ * @description Sets the absolute position of an element
424
+ * @param {HTMLElement} element Element to position
425
+ * @param {HTMLElement} target Target element
426
+ * @param {Object} params Position parameters
427
+ * @param {boolean} [params.isWWTarget=false] Whether the target is within the editor's WYSIWYG area
428
+ * @param {{left:number, top:number}} [params.addOffset={left:0, top:0}] Additional offset
429
+ * @param {"bottom"|"top"} [params.position="bottom"] Position ('bottom'|'top')
430
+ * @param {*} params.inst Instance object of caller
431
+ * @param {HTMLElement} [params.sibling] The sibling controller element
432
+ * @returns {{position: "top" | "bottom"} | undefined} Success -> {position: current position}
433
+ */
434
+ setAbsPosition(element, target, params) {
435
+ const addOffset = {
436
+ left: 0,
437
+ top: 0,
438
+ ...params.addOffset
439
+ };
440
+ const position = params.position || 'bottom';
441
+ const inst = params.inst;
442
+ const isLTR = !this.options.get('_rtl');
443
+
444
+ if (!isLTR) {
445
+ addOffset.left *= -1;
446
+ }
447
+
448
+ const isIframe = this.editor.frameOptions.get('iframe');
449
+ const isWWTarget = this.editor.frameContext.get('wrapper').contains(target) || params.isWWTarget || (isIframe ? this.editor.frameContext.get('wysiwyg').contains(target) : false);
450
+
451
+ const isCtrlTarget = target.nodeType === 1;
452
+ const isTargetAbs = isWWTarget && !isCtrlTarget;
453
+ const isInlineTarget = isCtrlTarget && /inline/.test(_w.getComputedStyle(target).display);
454
+ const clientSize = getClientSize(_d);
455
+ const wwScroll = isTargetAbs ? this.getWWScroll() : this._getWindowScroll();
456
+ const targetRect = !isIframe && isCtrlTarget ? target.getBoundingClientRect() : this.selection.getRects(target, 'start').rects;
457
+ const targetOffset = this.getGlobal(target);
458
+ const arrow = /** @type {HTMLElement} */ (hasClass(element.firstElementChild, 'se-arrow') ? element.firstElementChild : null);
459
+
460
+ // top ----------------------------------------------------------------------------------------------------
461
+ const siblingH = params.sibling?.offsetHeight || 0;
462
+ const ah = arrow ? arrow.offsetHeight : 0;
463
+ const elH = element.offsetHeight;
464
+ const targetH = target.offsetHeight;
465
+ // margin
466
+ const tmtw = targetRect.top;
467
+ const tmbw = clientSize.h - targetRect.bottom;
468
+ const globalTop = this.getGlobal(this.editor.frameContext.get('topArea')).top;
469
+ const wScrollY = _w.scrollY;
470
+ const th = this.context.get('toolbar.main').offsetHeight;
471
+ const containerToolbar = this.options.get('toolbar_container');
472
+ const headLess = this.editor.isBalloon || this.editor.isInline || containerToolbar;
473
+ const toolbarH = (containerToolbar && globalTop - wScrollY - th > 0) || (!this.editor.toolbar._sticky && headLess) ? 0 : th;
474
+
475
+ // check margin
476
+ const { rmt, rmb, bMargin, rt } = this._getVMargin(tmtw, tmbw, toolbarH, clientSize, targetRect, isTargetAbs, wwScroll);
477
+ if (isWWTarget && ((rmb > 0 ? bMargin : rmb) + targetH <= 0 || rmt + rt + targetH - (this.editor.toolbar._sticky && isInlineTarget ? toolbarH : 0) <= 0)) return;
478
+
479
+ const isSticky = this.editor.toolbar._sticky && this.context.get('toolbar.main').style.display !== 'none' && (!headLess || this.editor.frameContext.get('topArea').getBoundingClientRect().top <= th);
480
+ const statusBarH = this.editor.frameContext.get('statusbar')?.offsetHeight || 0;
481
+ let t = addOffset.top;
482
+ let y = 0;
483
+ let arrowDir = '';
484
+
485
+ // [bottom] position
486
+ if (position === 'bottom') {
487
+ let trmt = rmt - (isSticky && globalTop - wScrollY <= toolbarH ? toolbarH : 0);
488
+ if (isSticky && trmt + toolbarH < 0) trmt += toolbarH;
489
+ arrowDir = 'up';
490
+ t += targetRect.bottom + ah + wScrollY;
491
+ y = rmb - (elH + ah) - statusBarH;
492
+ // change to <top> position
493
+ if (y - siblingH < 0) {
494
+ arrowDir = 'down';
495
+ t -= targetH + elH + ah * 2;
496
+ y = trmt - (elH + ah);
497
+ // sticky the <top> position
498
+ if (y - siblingH < 0) {
499
+ arrowDir = '';
500
+ t -= y - siblingH - Math.max(1, y + elH + ah) + (!isSticky && trmt < 0 ? toolbarH : 0) - (isSticky ? this.context.get('toolbar.main').offsetTop : 0);
501
+ }
502
+ }
503
+ }
504
+ // <top> position
505
+ else {
506
+ arrowDir = 'down';
507
+ t += targetRect.top - elH - ah + wScrollY;
508
+ y = (isSticky ? targetRect.top - toolbarH : rmt) - elH - ah;
509
+ // change to [bottom] position
510
+ if (y - siblingH < 0) {
511
+ arrowDir = 'up';
512
+ t += targetH + elH + ah * 2;
513
+ y = (rmb > 0 ? bMargin : rmb) - (elH + ah) - statusBarH;
514
+ // sticky the [bottom] position
515
+ if (y - siblingH < 0) {
516
+ arrowDir = '';
517
+ t += y - 2;
518
+ }
519
+ }
520
+ }
521
+
522
+ this._setArrow(arrow, arrowDir);
523
+ element.style.top = `${t}px`;
524
+
525
+ // left ----------------------------------------------------------------------------------------------------
526
+ const radius = (element.nodeType === 1 ? numbers.get(_w.getComputedStyle(element).borderRadius) : 0) || 0;
527
+ const targetW = targetOffset.width;
528
+ const elW = element.offsetWidth;
529
+ const aw = arrow ? arrow.offsetWidth : 0;
530
+ // margin
531
+ const tmlw = targetRect.left;
532
+ const tmrw = clientSize.w - targetRect.right;
533
+ let rml, rmr;
534
+ if (this.editor.frameContext.get('isFullScreen')) {
535
+ rml = tmlw;
536
+ rmr = tmrw;
537
+ } else {
538
+ rml = targetRect.left;
539
+ rmr = clientSize.w - targetRect.right;
540
+ }
541
+
542
+ if (isWWTarget && (rml + targetW <= 0 || rmr + targetW <= 0)) return;
543
+ if (arrow) {
544
+ arrow.style.left = '';
545
+ arrow.style.right = '';
546
+ }
547
+
548
+ let l = addOffset.left;
549
+ let x = 0;
550
+ let ax = 0;
551
+ let awLimit = 0;
552
+ if (isLTR) {
553
+ l += targetRect.left + _w.scrollX - (rml < 0 ? rml : 0);
554
+ x = targetW + rml;
555
+ if (x < aw) {
556
+ awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
557
+ ax = awLimit;
558
+ }
559
+ x = targetW + rmr - elW;
560
+ if (x < 0) {
561
+ l += x;
562
+ awLimit = elW - 1 - (aw / 2 + (radius <= 2 ? 0 : radius - 2));
563
+ ax = -(x - aw / 2);
564
+ ax = ax > awLimit ? awLimit : ax;
565
+ }
566
+ if (arrow && ax > 0) arrow.style.left = ax + 'px';
567
+ } else {
568
+ l += targetRect.right - elW + _w.scrollX + (rmr < 0 ? rmr : 0);
569
+ x = targetW + rmr;
570
+ if (x < aw) {
571
+ awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
572
+ ax = awLimit;
573
+ }
574
+ x = targetW + rml - elW;
575
+ if (x < 0) {
576
+ l -= x;
577
+ awLimit = aw / 2 - 1 + (radius <= 2 ? 0 : radius - 2);
578
+ ax = -(x - aw / 2);
579
+ ax = ax < awLimit ? awLimit : ax > elW - awLimit ? elW - awLimit : ax;
580
+ }
581
+ if (arrow && ax > 0) arrow.style.right = ax + 'px';
582
+ }
583
+
584
+ element.style.left = `${l}px`;
585
+ inst.__offset = {
586
+ left: element.offsetLeft + wwScroll.left,
587
+ top: element.offsetTop + wwScroll.top,
588
+ addOffset: addOffset
589
+ };
590
+
591
+ return { position: arrowDir === 'up' ? 'bottom' : 'top' };
592
+ },
593
+
594
+ /**
595
+ * @this {OffsetThis}
596
+ * @description Sets the position of an element relative to a range
597
+ * @param {HTMLElement} element Element to position
598
+ * @param {?Range} range Range to position against.
599
+ * - if null, the current selection range is used
600
+ * @param {Object} [options={}] Position options
601
+ * @param {"bottom"|"top"} [options.position="bottom"] Position ('bottom'|'top')
602
+ * @param {number} [options.addTop=0] Additional top offset
603
+ * @returns {boolean} Success / Failure
604
+ */
605
+ setRangePosition(element, range, { position, addTop } = {}) {
606
+ element.style.top = '-10000px';
607
+ element.style.visibility = 'hidden';
608
+ element.style.display = 'block';
609
+
610
+ let positionTop = position === 'top';
611
+ range = range || this.selection.getRange();
612
+ const rectsObj = this.selection.getRects(range, positionTop ? 'start' : 'end');
613
+ positionTop = rectsObj.position === 'start';
614
+
615
+ const isFullScreen = this.editor.frameContext.get('isFullScreen');
616
+ const topArea = this.editor.frameContext.get('topArea');
617
+ const rects = rectsObj.rects;
618
+ const scrollLeft = isFullScreen ? 0 : rectsObj.scrollLeft;
619
+ const scrollTop = isFullScreen ? 0 : rectsObj.scrollTop;
620
+ const editorWidth = topArea.offsetWidth;
621
+ const offsets = this.getGlobal(topArea);
622
+ const editorLeft = offsets.left;
623
+ const toolbarWidth = element.offsetWidth;
624
+ const toolbarHeight = element.offsetHeight;
625
+
626
+ this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
627
+ if (this.getGlobal(element).top - offsets.top < 0) {
628
+ positionTop = !positionTop;
629
+ this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
630
+ }
631
+
632
+ if (toolbarWidth !== element.offsetWidth || toolbarHeight !== element.offsetHeight) {
633
+ this._setOffsetOnRange(positionTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop);
634
+ }
635
+
636
+ // check margin
637
+ const isTargetAbs = !this.carrierWrapper.contains(element);
638
+ const clientSize = getClientSize(_d);
639
+ const wwScroll = isTargetAbs ? this.getWWScroll() : this._getWindowScroll();
640
+ const targetH = rects.height;
641
+ const tmtw = rects.top;
642
+ const tmbw = clientSize.h - rects.bottom;
643
+ const toolbarH = !this.editor.toolbar._sticky && (this.editor.isBalloon || this.editor.isInline) ? 0 : this.context.get('toolbar.main').offsetHeight;
644
+
645
+ const { rmt, rmb, rt } = this._getVMargin(tmtw, tmbw, toolbarH, clientSize, rects, isTargetAbs, wwScroll);
646
+ if (rmb + targetH <= 0 || rmt + rt + targetH <= 0) return;
647
+
648
+ _w.setTimeout(() => {
649
+ element.style.visibility = '';
650
+ }, 0);
651
+
652
+ return true;
653
+ },
654
+
655
+ /**
656
+ * @private
657
+ * @this {OffsetThis}
658
+ * @description Sets the position of an element relative to the selection range in the editor.
659
+ * - This method calculates the top and left offsets for the element, ensuring it
660
+ * - does not overflow the editor boundaries and adjusts the arrow positioning accordingly.
661
+ * @param {boolean} isDirTop - Determines whether the element should be positioned above (`true`) or below (`false`) the target.
662
+ * @param {RectsInfo} rects - Bounding rectangle information of the selection range.
663
+ * @param {HTMLElement} element - The element to be positioned.
664
+ * @param {number} editorLeft - The left position of the editor.
665
+ * @param {number} editorWidth - The width of the editor.
666
+ * @param {number} scrollLeft - The horizontal scroll offset.
667
+ * @param {number} scrollTop - The vertical scroll offset.
668
+ * @param {number} [addTop=0] - Additional top margin adjustment.
669
+ */
670
+ _setOffsetOnRange(isDirTop, rects, element, editorLeft, editorWidth, scrollLeft, scrollTop, addTop = 0) {
671
+ const padding = 1;
672
+ const arrow = /** @type {HTMLElement} */ (element.querySelector('.se-arrow '));
673
+ const arrowMargin = Math.round(arrow.offsetWidth / 2);
674
+ const elW = element.offsetWidth;
675
+ const elH = rects.noText && !isDirTop ? 0 : element.offsetHeight;
676
+
677
+ const absoluteLeft = (isDirTop ? rects.left : rects.right) - editorLeft - elW / 2 + scrollLeft;
678
+ const overRight = absoluteLeft + elW - editorWidth;
679
+
680
+ let t = (isDirTop ? rects.top - elH - arrowMargin : rects.bottom + arrowMargin) - (rects.noText ? 0 : addTop) + scrollTop;
681
+ const l = absoluteLeft < 0 ? padding : overRight < 0 ? absoluteLeft : absoluteLeft - overRight - padding - 1;
682
+
683
+ let resetTop = false;
684
+ const space = t + (isDirTop ? this.getGlobal(this.editor.frameContext.get('topArea')).top : element.offsetHeight - this.editor.frameContext.get('wysiwyg').offsetHeight);
685
+ if (!isDirTop && space > 0 && this._getPageBottomSpace() < space) {
686
+ isDirTop = true;
687
+ resetTop = true;
688
+ } else if (isDirTop && _d.documentElement.offsetTop > space) {
689
+ isDirTop = false;
690
+ resetTop = true;
691
+ }
692
+
693
+ if (resetTop) t = (isDirTop ? rects.top - elH - arrowMargin : rects.bottom + arrowMargin) - (rects.noText ? 0 : addTop) + scrollTop;
694
+
695
+ element.style.left = Math.floor(l) + 'px';
696
+ element.style.top = Math.floor(t) + 'px';
697
+
698
+ if (isDirTop) {
699
+ removeClass(arrow, 'se-arrow-up');
700
+ addClass(arrow, 'se-arrow-down');
701
+ } else {
702
+ removeClass(arrow, 'se-arrow-down');
703
+ addClass(arrow, 'se-arrow-up');
704
+ }
705
+
706
+ const arrow_left = Math.floor(elW / 2 + (absoluteLeft - l));
707
+ arrow.style.left = (arrow_left + arrowMargin > element.offsetWidth ? element.offsetWidth - arrowMargin : arrow_left < arrowMargin ? arrowMargin : arrow_left) + 'px';
708
+ },
709
+
710
+ /**
711
+ * @private
712
+ * @this {OffsetThis}
713
+ * @description Get available space from page bottom
714
+ * @returns {number} Available space
715
+ */
716
+ _getPageBottomSpace() {
717
+ const topArea = this.editor.frameContext.get('topArea');
718
+ return _d.documentElement.scrollHeight - (this.getGlobal(topArea).top + topArea.offsetHeight);
719
+ },
720
+
721
+ /**
722
+ * @private
723
+ * @this {OffsetThis}
724
+ * @description Calculates the vertical margin offsets for the target element relative to the editor frame.
725
+ * - This method determines the top and bottom margins based on various conditions such as
726
+ * - fullscreen mode, iframe usage, toolbar height, and scroll positions.
727
+ * @param {number} tmtw Top margin to window
728
+ * @param {number} tmbw Bottom margin to window
729
+ * @param {number} toolbarH Toolbar height
730
+ * @param {{w: number, h: number}} clientSize documentElement.clientWidth, documentElement.clientHeight
731
+ * @param {RectsInfo} targetRect Target rect object
732
+ * @param {boolean} isTargetAbs Is target absolute position
733
+ * @param {OffsetWWScrollInfo} wwScroll WYSIWYG scroll info
734
+ * @returns {{rmt:number, rmb:number, rt:number, tMargin:number, bMargin:number}} Margin values
735
+ * - rmt: top margin to frame
736
+ * - rmb: bottom margin to frame
737
+ * - rt: Toolbar height offset adjustment
738
+ * - tMargin: top margin
739
+ * - bMargin: bottom margin
740
+ */
741
+ _getVMargin(tmtw, tmbw, toolbarH, clientSize, targetRect, isTargetAbs, wwScroll) {
742
+ let rmt = 0;
743
+ let rmb = 0;
744
+ let rt = 0;
745
+ let tMargin = 0;
746
+ let bMargin = 0;
747
+
748
+ if (this.editor.frameContext.get('isFullScreen')) {
749
+ rmt = tmtw - toolbarH;
750
+ rmb = tmbw;
751
+ } else {
752
+ const isIframeAbs = isTargetAbs && this.editor.frameOptions.get('iframe');
753
+ tMargin = targetRect.top;
754
+ bMargin = clientSize.h - targetRect.bottom;
755
+ const editorOffset = this.getGlobal();
756
+ const editorScroll = this.getGlobalScroll();
757
+ const statusBarH = this.editor.frameContext.get('statusbar')?.offsetHeight || 0;
758
+
759
+ if (isIframeAbs) {
760
+ const emt = editorOffset.top - editorScroll.top - editorScroll.ts;
761
+ const editorH = this.editor.frameContext.get('topArea').offsetHeight;
762
+ rmt = targetRect.top - emt;
763
+ rmb = bMargin - (editorScroll.oh - (editorH + emt) + statusBarH);
764
+ } else {
765
+ rt = !this.editor.toolbar._sticky && !this.options.get('toolbar_container') ? toolbarH : 0;
766
+ const wst = !isTargetAbs && /\d+/.test(this.editor.frameOptions.get('height')) ? editorOffset.top - _w.scrollY + rt : 0;
767
+ const wsb = !isTargetAbs && /\d+/.test(this.editor.frameOptions.get('height')) ? this.status.currentViewportHeight - (editorOffset.top + editorOffset.height - _w.scrollY) : 0;
768
+ let st = wst;
769
+ if (toolbarH > wst) {
770
+ if (this.editor.toolbar._sticky) {
771
+ st = toolbarH;
772
+ } else {
773
+ st = wst + toolbarH;
774
+ }
775
+ } else if (this.options.get('toolbar_container') && !this.editor.toolbar._sticky) {
776
+ toolbarH = 0;
777
+ } else {
778
+ st = wst + toolbarH;
779
+ }
780
+
781
+ rmt = targetRect.top - wwScroll.rects.top - st + toolbarH;
782
+ rmb = wwScroll.rects.bottom - targetRect.bottom - wsb;
783
+ // display margin
784
+ rmt = rmt > 0 ? rmt : rmt - toolbarH;
785
+ }
786
+ }
787
+
788
+ return {
789
+ rmt,
790
+ rmb,
791
+ rt,
792
+ tMargin,
793
+ bMargin
794
+ };
795
+ },
796
+
797
+ /**
798
+ * @private
799
+ * @this {OffsetThis}
800
+ * @description Sets the visibility and direction of the arrow element.
801
+ * - This method applies the appropriate class (`se-arrow-up` or `se-arrow-down`)
802
+ * - based on the specified direction key and adjusts the visibility of the arrow.
803
+ * @param {HTMLElement} arrow - The arrow element to be updated.
804
+ * @param {string} key - The direction of the arrow. ("up"|"down"|"")
805
+ * - Accepts `'up'` for an upward arrow, `'down'` for a downward arrow,
806
+ * - or any other value to hide the arrow.
807
+ */
808
+ _setArrow(arrow, key) {
809
+ if (key === 'up') {
810
+ if (arrow) arrow.style.visibility = '';
811
+ addClass(arrow, 'se-arrow-up');
812
+ removeClass(arrow, 'se-arrow-down');
813
+ } else if (key === 'down') {
814
+ if (arrow) arrow.style.visibility = '';
815
+ addClass(arrow, 'se-arrow-down');
816
+ removeClass(arrow, 'se-arrow-up');
817
+ } else {
818
+ if (arrow) arrow.style.visibility = 'hidden';
819
+ }
820
+ },
821
+
822
+ /**
823
+ * @private
824
+ * @this {OffsetThis}
825
+ * @description Retrieves the current window scroll position and viewport size.
826
+ * - Returns an object containing the scroll offsets, viewport dimensions, and boundary rects.
827
+ * @returns {{
828
+ * top: number,
829
+ * left: number,
830
+ * width: number,
831
+ * height: number,
832
+ * bottom: number,
833
+ * rects: RectsInfo
834
+ * }} An object with scroll and viewport information.
835
+ */
836
+
837
+ _getWindowScroll() {
838
+ const viewPort = getClientSize(_d);
839
+ return {
840
+ top: _w.scrollY,
841
+ left: _w.scrollX,
842
+ width: viewPort.w,
843
+ height: viewPort.h,
844
+ bottom: _w.scrollY + viewPort.h,
845
+ rects: {
846
+ left: 0,
847
+ top: 0,
848
+ right: _w.innerWidth,
849
+ bottom: _w.innerHeight,
850
+ noText: true
851
+ }
852
+ };
853
+ },
854
+
855
+ constructor: Offset
856
+ };
857
+
858
+ export default Offset;