suneditor 3.1.2 → 3.1.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suneditor",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "Vanilla JavaScript based WYSIWYG web editor",
5
5
  "author": "Yi JiHong",
6
6
  "license": "MIT",
@@ -49,16 +49,19 @@
49
49
  "engines": {
50
50
  "node": ">=14.0.0"
51
51
  },
52
+ "overrides": {
53
+ "uuid": ">=14.0.0"
54
+ },
52
55
  "scripts": {
53
56
  "dev": "webpack-dev-server --config webpack/dev.js",
54
57
  "start": "npm run dev",
55
58
  "build:dev": "cross-env NODE_ENV=development webpack --config webpack/builder.js && cross-env NODE_ENV=development webpack --config webpack/builder-contents.js && rm -f dist/_suneditor-contents.js",
56
59
  "build:prod": "cross-env NODE_ENV=production webpack --config webpack/builder.js && cross-env NODE_ENV=production webpack --config webpack/builder-contents.js && rm -f dist/_suneditor-contents.js",
57
60
  "lint:type": "npx tsc --noEmit",
58
- "lint:fix-js": "npx eslint \"src/**/*.js\" --fix",
61
+ "lint:fix-js": "npx eslint src/ --fix",
59
62
  "lint:fix-ts": "npx eslint types/ --fix",
60
- "lint:fix-all": "npx eslint \"src/**/*.js\" types/ --fix",
61
- "lint": "npx eslint \"src/**/*.js\" types/ && npm run lint:type && npm run check:arch",
63
+ "lint:fix-all": "npx eslint src/ types/ --fix",
64
+ "lint": "npx eslint src/ types/ && npm run lint:type && npm run check:arch",
62
65
  "ts-build": "npm run check:inject && (npx tsc || true) && barrelsby --config .barrelsby.json && node scripts/ts-build/format-index.cjs && node scripts/ts-build/fix-langs.cjs && node scripts/ts-build/wrap-dts.cjs && node scripts/ts-build/rename-index.cjs && node scripts/ts-build/interfaces-convert.cjs && node scripts/ts-build/gen-options-dts.cjs && node scripts/ts-build/gen-css-dts.cjs && node scripts/ts-build/inject-typedef-import.cjs && npm run lint:fix-ts",
63
66
  "test": "jest --silent",
64
67
  "test:watch": "jest --watch",
@@ -125,13 +128,13 @@
125
128
  "webpack": "^5.102.0",
126
129
  "webpack-bundle-analyzer": "^4.10.2",
127
130
  "webpack-cli": "^6.0.1",
128
- "webpack-dev-server": "^5.2.2",
131
+ "webpack-dev-server": "^5.2.3",
129
132
  "webpack-merge": "^6.0.1"
130
133
  },
131
134
  "browserslist": [
132
135
  "chrome >= 119",
133
136
  "edge >= 119",
134
- "firefox >= 121",
137
+ "firefox >= 125",
135
138
  "safari >= 17.2",
136
139
  "opera >= 105",
137
140
  "samsung >= 23",
@@ -1149,6 +1149,26 @@
1149
1149
  z-index: 2147483646;
1150
1150
  }
1151
1151
 
1152
+ /** --- popover UA override ---------------------------------------------------- */
1153
+ .sun-editor .se-controller[popover],
1154
+ .sun-editor.sun-editor-carrier-wrapper .se-drag-cursor[popover] {
1155
+ inset: unset;
1156
+ margin: 0;
1157
+ }
1158
+
1159
+ .sun-editor .se-modal-area[popover],
1160
+ .sun-editor .se-back-wrapper[popover],
1161
+ .sun-editor .se-loading-box[popover],
1162
+ .sun-editor .se-toast[popover],
1163
+ .sun-editor .se-browser[popover],
1164
+ .sun-editor .se-menu-tray[popover] {
1165
+ background: none;
1166
+ border: none;
1167
+ padding: 0;
1168
+ overflow: visible;
1169
+ color: inherit;
1170
+ }
1171
+
1152
1172
  /** --- dropdown layer ---------------------------------------------------------- */
1153
1173
  .sun-editor .se-dropdown {
1154
1174
  overflow-x: hidden;
@@ -2568,6 +2588,12 @@
2568
2588
  display: none;
2569
2589
  overflow: visible;
2570
2590
  z-index: 2147483646;
2591
+ }
2592
+
2593
+ .sun-editor .se-controller[popover]:popover-open {
2594
+ position: absolute;
2595
+ inset: unset;
2596
+ margin: 0;
2571
2597
  padding: 2px 2px 0 2px;
2572
2598
  margin: 0;
2573
2599
  border: 1px solid var(--se-controller-border-color);
@@ -4553,8 +4579,8 @@
4553
4579
  .sun-editor-editable[contenteditable='true'] .se-page-break::after {
4554
4580
  content: '⎯⎯⎯⎯⎯⎯';
4555
4581
  position: absolute;
4556
- top: 1px;
4557
- background: var(--se-main-background-color);
4582
+ top: 0px;
4583
+ background: transparent;
4558
4584
  padding: 0 5px;
4559
4585
  color: var(--se-doc-info-page-background-color);
4560
4586
  font-size: var(--se-main-font-size);
@@ -46,7 +46,17 @@ export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar
46
46
  dragCursor.style.top = `${rect.top + _w.scrollY + _offset.y - 5 + frameY}px`;
47
47
  dragCursor.style.height = `${rect.height + 10}px`;
48
48
  dragCursor.style.display = 'block';
49
+ try {
50
+ dragCursor.showPopover?.();
51
+ } catch {
52
+ // ignore
53
+ }
49
54
  } else {
55
+ try {
56
+ dragCursor.hidePopover?.();
57
+ } catch {
58
+ // ignore
59
+ }
50
60
  dragCursor.style.display = 'none';
51
61
  }
52
62
  }
@@ -56,6 +66,11 @@ export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar
56
66
  * @param {HTMLElement} dragCursor - Drag cursor element
57
67
  */
58
68
  export function OnDragEnd_wysiwyg(dragCursor) {
69
+ try {
70
+ dragCursor.hidePopover?.();
71
+ } catch {
72
+ // ignore
73
+ }
59
74
  dragCursor.style.display = 'none';
60
75
  }
61
76
 
@@ -109,6 +124,11 @@ export function OnDrop_wysiwyg(fc, dragCursor, e) {
109
124
  this.$.selection.setRange(sc, so, ec, eo);
110
125
  return this._dataTransferAction('drop', e, dataTransfer, fc);
111
126
  } finally {
127
+ try {
128
+ dragCursor.hidePopover?.();
129
+ } catch {
130
+ // ignore
131
+ }
112
132
  dragCursor.style.display = 'none';
113
133
  }
114
134
  }
@@ -122,6 +122,7 @@ class Menu {
122
122
  this.#setMenuPosition(btnEl, menu);
123
123
  }
124
124
 
125
+ this.#context.get('menuTray').showPopover?.();
125
126
  this.#bindClose_dropdown_mouse = this.#eventManager.addGlobalEvent('mousedown', this.#globalEventHandler.mousedown, false);
126
127
  if (this.#dropdownCommands.includes(dropdownName)) {
127
128
  this.menus = converter.nodeListToArray(menu.querySelectorAll('[data-command]'));
@@ -162,6 +163,7 @@ class Menu {
162
163
  }
163
164
  this.currentDropdownActiveButton = null;
164
165
  this.#$.ui.preventToolbarHide(false);
166
+ this.#context.get('menuTray').hidePopover?.();
165
167
  }
166
168
 
167
169
  this.#store.set('_preventBlur', false);
@@ -209,6 +211,7 @@ class Menu {
209
211
  this.#setMenuPosition(button, this.currentContainer);
210
212
  }
211
213
 
214
+ this.#context.get('menuTray').showPopover?.();
212
215
  this.#bindClose_cons_mouse = this.#eventManager.addGlobalEvent('mousedown', this.#globalEventHandler.containerDown, false);
213
216
 
214
217
  if (this.#$.plugins[containerName].on) this.#$.plugins[containerName].on(button);
@@ -229,6 +232,7 @@ class Menu {
229
232
  dom.utils.removeClass(this.currentContainerActiveButton, 'on');
230
233
  this.currentContainerActiveButton = null;
231
234
  this.#$.ui.preventToolbarHide(false);
235
+ this.#context.get('menuTray').hidePopover?.();
232
236
  }
233
237
 
234
238
  this.#store.set('_preventBlur', false);
@@ -89,6 +89,7 @@ class UIManager {
89
89
 
90
90
  // toast
91
91
  const toastPopup = CreateToastHTML();
92
+ toastPopup.setAttribute('popover', 'manual');
92
93
  this.toastPopup = toastPopup;
93
94
  this.toastContainer = toastPopup.querySelector('.se-toast-container');
94
95
  this.toastMessage = toastPopup.querySelector('span');
@@ -396,7 +397,9 @@ class UIManager {
396
397
  * @param {string} [rootKey] Root key
397
398
  */
398
399
  showLoading(rootKey) {
399
- /** @type {HTMLElement} */ ((rootKey ? this.#frameRoots.get(rootKey).get('container') : this.#carrierWrapper).querySelector('.se-loading-box')).style.display = 'block';
400
+ const el = /** @type {HTMLElement} */ ((rootKey ? this.#frameRoots.get(rootKey).get('container') : this.#carrierWrapper).querySelector('.se-loading-box'));
401
+ el.style.display = 'block';
402
+ el.showPopover?.();
400
403
  }
401
404
 
402
405
  /**
@@ -404,7 +407,9 @@ class UIManager {
404
407
  * @param {string} [rootKey] Root key
405
408
  */
406
409
  hideLoading(rootKey) {
407
- /** @type {HTMLElement} */ ((rootKey ? this.#frameRoots.get(rootKey).get('container') : this.#carrierWrapper).querySelector('.se-loading-box')).style.display = 'none';
410
+ const el = /** @type {HTMLElement} */ ((rootKey ? this.#frameRoots.get(rootKey).get('container') : this.#carrierWrapper).querySelector('.se-loading-box'));
411
+ el.hidePopover?.();
412
+ el.style.display = 'none';
408
413
  }
409
414
 
410
415
  /**
@@ -423,6 +428,7 @@ class UIManager {
423
428
  this.#bindClose = this.#eventManager.addGlobalEvent('keydown', this.#closeListener[0]);
424
429
 
425
430
  this.#alertArea.style.display = 'block';
431
+ this.#alertArea.showPopover?.();
426
432
  dom.utils.addClass(this.alertModal, 'se-modal-show');
427
433
  }
428
434
 
@@ -432,6 +438,7 @@ class UIManager {
432
438
  alertClose() {
433
439
  dom.utils.removeClass(this.alertModal, 'se-modal-show');
434
440
  dom.utils.removeClass(this.alertModal, 'se-alert-*');
441
+ this.#alertArea.hidePopover?.();
435
442
  this.#alertArea.style.display = 'none';
436
443
  this.#bindAlertClick &&= this.#eventManager.removeEvent(this.#bindAlertClick);
437
444
  this.#bindClose &&= this.#eventManager.removeGlobalEvent(this.#bindClose);
@@ -452,6 +459,7 @@ class UIManager {
452
459
  if (type) dom.utils.addClass(this.toastPopup, `se-toast-${type}`);
453
460
 
454
461
  this.toastPopup.style.display = 'block';
462
+ this.toastPopup.showPopover?.();
455
463
  this.toastMessage.textContent = message;
456
464
  dom.utils.addClass(this.toastContainer, 'se-toast-show');
457
465
 
@@ -468,6 +476,7 @@ class UIManager {
468
476
  if (this.#toastToggle) _w.clearTimeout(this.#toastToggle);
469
477
  this.#toastToggle = null;
470
478
  dom.utils.removeClass(this.toastContainer, 'se-toast-show');
479
+ this.toastPopup.hidePopover?.();
471
480
  this.toastPopup.style.display = 'none';
472
481
  }
473
482
 
@@ -509,12 +518,14 @@ class UIManager {
509
518
  enableBackWrapper(cursor) {
510
519
  this.#backWrapper.style.cursor = cursor;
511
520
  this.#backWrapper.style.display = 'block';
521
+ this.#backWrapper.showPopover?.();
512
522
  }
513
523
 
514
524
  /**
515
525
  * @description Disabled background `div`
516
526
  */
517
527
  disableBackWrapper() {
528
+ this.#backWrapper.hidePopover?.();
518
529
  this.#backWrapper.style.display = 'none';
519
530
  this.#backWrapper.style.cursor = 'default';
520
531
  }
@@ -363,7 +363,7 @@ export const DEFAULTS = {
363
363
  * attributeWhitelist: {
364
364
  * a: 'href|target',
365
365
  * img: 'src|alt',
366
- * '*': 'id|data-*'
366
+ * '*': 'id|data-[^\s]+'
367
367
  * }
368
368
  * }
369
369
  * ```
@@ -6,6 +6,7 @@ import { dom, numbers, converter, env } from '../../helper';
6
6
  import { DEFAULTS } from '../schema/options';
7
7
 
8
8
  const _d = env._d;
9
+ let editorInstanceId = 0;
9
10
 
10
11
  /**
11
12
  * @typedef {import('../schema/options').AllBaseOptions} AllBaseOptions_constructor
@@ -57,16 +58,21 @@ function Constructor(editorTargets, options) {
57
58
  const icons = optionMap.i;
58
59
  const lang = optionMap.l;
59
60
  const loadingBox = dom.utils.createElement('DIV', { class: 'se-loading-box sun-editor-common' }, '<div class="se-loading-effect"></div>');
61
+ const editorFormFieldPrefix = 'suneditor-' + ++editorInstanceId;
60
62
 
61
63
  /** --- carrier wrapper --------------------------------------------------------------- */
62
64
  const editor_carrier_wrapper = dom.utils.createElement('DIV', { class: 'sun-editor sun-editor-carrier-wrapper sun-editor-common' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
63
65
  // menuTray
64
- const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray' });
66
+ const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray', popover: 'manual' });
65
67
  editor_carrier_wrapper.appendChild(menuTray);
66
68
  // focus temp element
67
69
  const focusTemp = /** @type {HTMLInputElement} */ (
68
70
  dom.utils.createElement('INPUT', {
71
+ type: 'text',
72
+ id: editorFormFieldPrefix + '-focus-temp',
69
73
  class: '__se__focus__temp__',
74
+ autocomplete: 'off',
75
+ 'aria-hidden': 'true',
70
76
  style: 'position: fixed !important; top: -10000px !important; left: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;',
71
77
  })
72
78
  );
@@ -74,7 +80,7 @@ function Constructor(editorTargets, options) {
74
80
  editor_carrier_wrapper.appendChild(focusTemp);
75
81
 
76
82
  // modal
77
- const modal = dom.utils.createElement('DIV', { class: 'se-modal se-modal-area sun-editor-common' });
83
+ const modal = dom.utils.createElement('DIV', { class: 'se-modal se-modal-area sun-editor-common', popover: 'manual' });
78
84
  const modal_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
79
85
  const modal_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
80
86
  modal.appendChild(modal_back);
@@ -82,7 +88,7 @@ function Constructor(editorTargets, options) {
82
88
  editor_carrier_wrapper.appendChild(modal);
83
89
 
84
90
  // alert
85
- const alert = dom.utils.createElement('DIV', { class: 'se-alert se-modal-area sun-editor-common', style: 'display: none;' });
91
+ const alert = dom.utils.createElement('DIV', { class: 'se-alert se-modal-area sun-editor-common', style: 'display: none;', popover: 'manual' });
86
92
  const alert_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
87
93
  const alert_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
88
94
  alert.appendChild(alert_back);
@@ -90,11 +96,13 @@ function Constructor(editorTargets, options) {
90
96
  editor_carrier_wrapper.appendChild(alert);
91
97
 
92
98
  // loding box, resizing back
93
- editor_carrier_wrapper.appendChild(dom.utils.createElement('DIV', { class: 'se-back-wrapper' }));
94
- editor_carrier_wrapper.appendChild(loadingBox.cloneNode(true));
99
+ editor_carrier_wrapper.appendChild(dom.utils.createElement('DIV', { class: 'se-back-wrapper', popover: 'manual' }));
100
+ const loadingBoxEl = /** @type {HTMLElement} */ (loadingBox.cloneNode(true));
101
+ loadingBoxEl.setAttribute('popover', 'manual');
102
+ editor_carrier_wrapper.appendChild(loadingBoxEl);
95
103
 
96
104
  // drag cursor
97
- const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor' });
105
+ const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor', popover: 'manual' });
98
106
  editor_carrier_wrapper.appendChild(dragCursor);
99
107
 
100
108
  // set carrier wrapper
@@ -146,7 +154,7 @@ function Constructor(editorTargets, options) {
146
154
  container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
147
155
 
148
156
  // init element
149
- const initElements = _initTargetElements(editTarget.key, o, top_div, to);
157
+ const initElements = _initTargetElements(editTarget.key, o, top_div, to, editorFormFieldPrefix);
150
158
  const bottomBar = initElements.bottomBar;
151
159
  const statusbar = bottomBar.statusbar;
152
160
  const wysiwyg_div = initElements.wysiwygFrame;
@@ -195,7 +203,11 @@ function Constructor(editorTargets, options) {
195
203
  // not used code mirror
196
204
  if (textarea === codeMirrorEl) {
197
205
  // add line nubers
198
- const codeNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-code-view-line', readonly: 'true' }, null);
206
+ const codeNumbers = dom.utils.createElement(
207
+ 'TEXTAREA',
208
+ { id: editorFormFieldPrefix + '-code-view-line-' + (key || 'default'), class: 'se-code-view-line', readonly: 'true', autocomplete: 'off', 'aria-hidden': 'true', tabindex: '-1' },
209
+ null,
210
+ );
199
211
  codeWrapper.insertBefore(codeNumbers, textarea);
200
212
  } else {
201
213
  textarea = codeMirrorEl;
@@ -207,7 +219,11 @@ function Constructor(editorTargets, options) {
207
219
  let markdownTextarea = null;
208
220
  if (o.get('buttons').has('markdownView')) {
209
221
  markdownTextarea = initElements.markdownView;
210
- const markdownNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-markdown-view-line', readonly: 'true' }, null);
222
+ const markdownNumbers = dom.utils.createElement(
223
+ 'TEXTAREA',
224
+ { id: editorFormFieldPrefix + '-markdown-view-line-' + (key || 'default'), class: 'se-markdown-view-line', readonly: 'true', autocomplete: 'off', 'aria-hidden': 'true', tabindex: '-1' },
225
+ null,
226
+ );
211
227
  markdownWrapper = dom.utils.createElement('DIV', { class: 'se-markdown-wrapper' });
212
228
  markdownWrapper.appendChild(markdownNumbers);
213
229
  markdownWrapper.appendChild(markdownTextarea);
@@ -934,9 +950,10 @@ function InitFrameOptions(o, origin) {
934
950
  * @param {Map<string, *>} options - Options
935
951
  * @param {HTMLElement} topDiv - Top div
936
952
  * @param {SunEditor.FrameOptions} targetOptions - `editor.frameOptions`
953
+ * @param {string} formFieldPrefix - Prefix for generated form field ids
937
954
  * @returns {{bottomBar: ReturnType<CreateStatusbar>, wysiwygFrame: HTMLElement, codeView: HTMLElement, markdownView: HTMLElement, placeholder: HTMLElement}}
938
955
  */
939
- function _initTargetElements(key, options, topDiv, targetOptions) {
956
+ function _initTargetElements(key, options, topDiv, targetOptions, formFieldPrefix) {
940
957
  const editorStyles = targetOptions.get('_defaultStyles');
941
958
  /** top div */
942
959
  topDiv.style.cssText = editorStyles.top;
@@ -990,10 +1007,10 @@ function _initTargetElements(key, options, topDiv, targetOptions) {
990
1007
  }
991
1008
 
992
1009
  // textarea for code view
993
- const textarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame });
1010
+ const textarea = dom.utils.createElement('TEXTAREA', { id: formFieldPrefix + '-code-viewer-' + (key || 'default'), class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame, autocomplete: 'off' });
994
1011
 
995
1012
  // textarea for markdown view
996
- const markdownTextarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-markdown-viewer', style: editorStyles.frame });
1013
+ const markdownTextarea = dom.utils.createElement('TEXTAREA', { id: formFieldPrefix + '-markdown-viewer-' + (key || 'default'), class: 'se-wrapper-inner se-markdown-viewer', style: editorStyles.frame, autocomplete: 'off' });
997
1014
 
998
1015
  const placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
999
1016
  if (targetOptions.get('placeholder')) {
@@ -162,28 +162,19 @@ class DocumentType {
162
162
 
163
163
  // page break
164
164
  let pageBreakHeight = 0;
165
- let lastBreakPosition = 0;
166
- let additionalPages = 0;
165
+ const breakPoints = [];
167
166
  if (pageBreaks.length > 0) {
168
167
  pageBreakHeight = pageBreaks[0].offsetHeight;
169
168
  for (let i = 0; i < pageBreaks.length; i++) {
170
- const breakPosition = pageBreaks[i].offsetTop;
171
- const sectionHeight = breakPosition - lastBreakPosition;
172
- if (sectionHeight % A4_PAGE_HEIGHT !== 0) additionalPages++;
173
- lastBreakPosition = breakPosition;
169
+ breakPoints.push({ top: pageBreaks[i].offsetTop, end: pageBreaks[i].offsetTop + pageBreakHeight / 2 });
174
170
  }
175
-
176
- const lastSectionHeight = mirrorHeight - lastBreakPosition;
177
- if (lastSectionHeight > 0 && lastSectionHeight % A4_PAGE_HEIGHT !== 0) additionalPages++;
178
171
  }
179
172
 
180
173
  const scrollTop = !this.#isScrollable(this.#fc) ? 0 : this._getWWScrollTop();
181
- const totalPages = Math.ceil(mirrorHeight / A4_PAGE_HEIGHT) + additionalPages;
182
- const wwWidth = this.#wwFrame.offsetWidth + 1;
183
- const pages = [];
174
+ const pages = [{ number: 0, top: 0 }];
184
175
 
185
- for (let i = 0; i < pageBreaks.length; i++) {
186
- pages.push({ number: i, top: pageBreaks[i].offsetTop + pageBreakHeight / 2 });
176
+ for (let i = 0; i < breakPoints.length; i++) {
177
+ pages.push({ number: 0, top: breakPoints[i].top + pageBreakHeight / 2, isBreak: true });
187
178
  }
188
179
 
189
180
  this.#mirrorCache = 0;
@@ -191,14 +182,27 @@ class DocumentType {
191
182
  const mChr = this.#mirror.children;
192
183
  this._initializeCache(mChr);
193
184
 
194
- pages.push({ number: 0, top: 0 });
185
+ // Calculate page positions per section (between page breaks)
186
+ const sectionStarts = [0, ...breakPoints.map((b) => b.end)];
187
+ const sectionEnds = [...breakPoints.map((b) => b.top), mirrorHeight];
188
+
189
+ for (let s = 0; s < sectionStarts.length; s++) {
190
+ const sStart = sectionStarts[s];
191
+ const sEnd = sectionEnds[s];
192
+ let t = sStart;
193
+ let isFirst = s === 0;
195
194
 
196
- for (let i = 1, t = 0; i < totalPages; i++) {
197
- t += A4_PAGE_HEIGHT + (i === 1 ? this.#paddingTop + this.#paddingBottom : this.#paddingTop);
198
- if (!pages.some((p) => Math.abs(p.top - t) < 3)) {
199
- const top = this._calcPageBreakTop(t, chr, mChr);
200
- if (top === null) break;
201
- pages.push({ number: i, top });
195
+ while (true) {
196
+ t += A4_PAGE_HEIGHT + (isFirst ? this.#paddingTop + this.#paddingBottom : this.#paddingTop);
197
+ isFirst = false;
198
+
199
+ if (t >= sEnd) break;
200
+
201
+ if (!pages.some((p) => Math.abs(p.top - t) < 3)) {
202
+ const top = this._calcPageBreakTop(t, chr, mChr, pages);
203
+ if (top === null) break;
204
+ pages.push({ number: 0, top });
205
+ }
202
206
  }
203
207
  }
204
208
 
@@ -214,8 +218,8 @@ class DocumentType {
214
218
  this.#page.innerHTML = '';
215
219
  this.#pages = [];
216
220
 
217
- for (let i = 0, t; i < totalPages; i++) {
218
- if (!pages[i]) continue;
221
+ const wwWidth = this.#wwFrame.offsetWidth + 1;
222
+ for (let i = 0, t; i < pages.length; i++) {
219
223
  t = pages[i].top;
220
224
  if (mirrorHeight < t) break;
221
225
 
@@ -249,10 +253,11 @@ class DocumentType {
249
253
  * @param {number} t - The initial top position value to be adjusted.
250
254
  * @param {HTMLCollection} chr - The elements array in the current (main) page.
251
255
  * @param {HTMLCollection} mChr - The elements array in the mirrored page.
256
+ * @param {Array.<{number: number, top: number, isBreak?: boolean}>} [pages] - The pages array containing page break info.
252
257
  * @returns {number|null} The adjusted top value.
253
258
  */
254
- _calcPageBreakTop(t, chr, mChr) {
255
- const { ci } = this._getElementAtPosition(t, mChr);
259
+ _calcPageBreakTop(t, chr, mChr, pages) {
260
+ const { ci } = this._getElementAtPosition(t, mChr, pages);
256
261
  const mel = /** @type {HTMLElement} */ (mChr[ci]);
257
262
  const el = /** @type {HTMLElement} */ (chr[ci]);
258
263
  if (!mel || !el) return null;
@@ -290,22 +295,39 @@ class DocumentType {
290
295
  * @description Retrieves the element at a given position.
291
296
  * @param {number} pageTop - The vertical position to check.
292
297
  * @param {HTMLCollection} mChr - List of mirrored elements.
298
+ * @param {Array.<{number: number, top: number, isBreak?: boolean}>} [pages] - The pages array containing page break info for skipping break elements.
293
299
  * @returns {{ci: number, cm: number, ch: number}} The closest element and its related data.
294
300
  * - ci: The index of the closest element.
295
301
  * - cm: The distance between the top of the closest element and the given position.
296
302
  * - ch: The height of the closest element.
297
303
  */
298
- _getElementAtPosition(pageTop, mChr) {
304
+ _getElementAtPosition(pageTop, mChr, pages) {
299
305
  let start = this.#mirrorCache;
300
306
  let end = mChr.length - 1;
301
307
 
308
+ // Reset cache if target position is before cached position (crossing section boundaries)
309
+ if (start > 0) {
310
+ const cachedPos = this.#positionCache.get(start);
311
+ if (cachedPos && pageTop < cachedPos.top) {
312
+ start = 0;
313
+ }
314
+ }
315
+
302
316
  while (start <= end) {
303
317
  const mid = Math.floor((start + end) / 2);
304
318
  const { top, height, bottom } = this.#positionCache.get(mid);
305
319
 
306
320
  if (pageTop >= top && pageTop <= bottom) {
307
- this.#mirrorCache = mid;
308
- return { ci: mid, cm: pageTop - bottom, ch: height };
321
+ let ci = mid;
322
+ // Skip page break elements use adjacent content element
323
+ if (pages && dom.utils.hasClass(mChr[ci], 'se-page-break')) {
324
+ ci = ci + 1 < mChr.length ? ci + 1 : Math.max(0, ci - 1);
325
+ const adjPos = this.#positionCache.get(ci);
326
+ this.#mirrorCache = ci;
327
+ return { ci, cm: pageTop - adjPos.bottom, ch: adjPos.height };
328
+ }
329
+ this.#mirrorCache = ci;
330
+ return { ci, cm: pageTop - bottom, ch: height };
309
331
  }
310
332
 
311
333
  if (pageTop < top) {
@@ -315,7 +337,11 @@ class DocumentType {
315
337
  }
316
338
  }
317
339
 
318
- const closestIndex = mChr[start] ? start : end;
340
+ let closestIndex = mChr[start] ? start : end;
341
+ // Skip page break elements for closest match
342
+ if (pages && dom.utils.hasClass(mChr[closestIndex], 'se-page-break')) {
343
+ closestIndex = closestIndex + 1 < mChr.length ? closestIndex + 1 : Math.max(0, closestIndex - 1);
344
+ }
319
345
  this.#mirrorCache = closestIndex;
320
346
  const iElement = this.#positionCache.get(closestIndex);
321
347
  return { ci: closestIndex, cm: pageTop - iElement.bottom, ch: iElement.height };
@@ -151,6 +151,7 @@ class Browser {
151
151
  // init
152
152
  browserFrame.appendChild(dom.utils.createElement('DIV', { class: 'se-browser-back' }));
153
153
  browserFrame.appendChild(content);
154
+ browserFrame.setAttribute('popover', 'manual');
154
155
  this.#$.contextProvider.carrierWrapper.appendChild(browserFrame);
155
156
 
156
157
  this.#$.eventManager.addEvent(this.tagArea, 'click', this.#OnClickTag.bind(this));
@@ -197,6 +198,7 @@ class Browser {
197
198
 
198
199
  this.titleArea.textContent = params.title || this.title;
199
200
  this.area.style.display = 'block';
201
+ this.area.showPopover?.();
200
202
  this.#$.ui.opendBrowser = this;
201
203
  this.closeArrow = this.#$.options.get('_rtl') ? this.#$.icons.menu_arrow_left : this.#$.icons.menu_arrow_right;
202
204
 
@@ -217,6 +219,7 @@ class Browser {
217
219
  this.#removeGlobalEvent();
218
220
  this.apiManager.cancel();
219
221
 
222
+ this.area.hidePopover?.();
220
223
  this.area.style.display = 'none';
221
224
  this.selectedTags = [];
222
225
  this.items = [];
@@ -110,6 +110,7 @@ class Controller {
110
110
  }
111
111
 
112
112
  // add element
113
+ this.form.setAttribute('popover', 'manual');
113
114
  this.#$.contextProvider.carrierWrapper.appendChild(element);
114
115
 
115
116
  // init
@@ -394,6 +395,7 @@ class Controller {
394
395
  * @description Hide controller at editor area (link button, image resize button..)
395
396
  */
396
397
  #controllerOff() {
398
+ this.form.hidePopover?.();
397
399
  this.form.style.display = 'none';
398
400
  this.#$.ui.opendControllers = this.#$.ui.opendControllers.filter((v) => v.form !== this.form);
399
401
  if (this.#$.ui.currentControllerName !== this.kind && this.#$.ui.opendControllers.length > 0) return;
@@ -464,6 +466,7 @@ class Controller {
464
466
 
465
467
  controller.style.zIndex = this.toTop ? INDEX_0 : this.#reserveIndex ? INDEX_S_1 : INDEX_1;
466
468
  controller.style.visibility = '';
469
+ controller.showPopover?.();
467
470
  return true;
468
471
  }
469
472
 
@@ -282,10 +282,18 @@ class Figure {
282
282
  const cover = dom.query.getParentElement(element, 'FIGURE', 2);
283
283
  const inlineCover = dom.query.getParentElement(element, 'SPAN', 2);
284
284
  const anyCover = cover || inlineCover;
285
- const target = dom.query.getParentElement(element, (current) => current.parentElement === anyCover, 0) || /** @type {HTMLElement} */ (element);
285
+ let target = dom.query.getParentElement(element, (current) => current.parentElement === anyCover, 0) || element;
286
+
287
+ // When image is wrapped by anchor, target becomes <a> instead of <img>
288
+ if (dom.check.isAnchor(target)) {
289
+ const imgEl = target.querySelector(':scope > img');
290
+ if (imgEl) {
291
+ target = imgEl;
292
+ }
293
+ }
286
294
 
287
295
  return {
288
- target,
296
+ target: /** @type {HTMLElement} */ (target),
289
297
  container: dom.query.getParentElement(target, Figure.is, 3) || cover,
290
298
  cover: cover,
291
299
  inlineCover: dom.utils.hasClass(inlineCover, 'se-inline-component') ? /** @type {HTMLElement} */ (inlineCover) : null,
@@ -734,12 +742,23 @@ class Figure {
734
742
  const { container, inlineCover, target } = Figure.GetContainer(targetNode);
735
743
  const { w, h } = this.getSize(target);
736
744
 
745
+ // Check if target is wrapped by an anchor
746
+ const anchorEl = dom.check.isAnchor(target.parentNode) ? target.parentNode : null;
747
+
737
748
  const newTarget = /** @type {HTMLElement} */ (target.cloneNode(false));
738
749
  newTarget.style.width = '';
739
750
  newTarget.style.height = '';
740
751
  newTarget.removeAttribute('width');
741
752
  newTarget.removeAttribute('height');
742
753
 
754
+ // Preserve anchor wrapper if exists
755
+ let elementToInsert = newTarget;
756
+ if (anchorEl) {
757
+ const newAnchor = /** @type {HTMLElement} */ (anchorEl.cloneNode(false));
758
+ newAnchor.appendChild(newTarget);
759
+ elementToInsert = newAnchor;
760
+ }
761
+
743
762
  switch (formatStyle) {
744
763
  case 'inline': {
745
764
  if (inlineCover) break;
@@ -748,7 +767,7 @@ class Figure {
748
767
  const next = container.nextElementSibling;
749
768
  const parent = container.parentElement;
750
769
 
751
- const figure = Figure.CreateInlineContainer(newTarget);
770
+ const figure = Figure.CreateInlineContainer(elementToInsert);
752
771
  dom.utils.addClass(
753
772
  figure.container,
754
773
  container.className
@@ -777,7 +796,7 @@ class Figure {
777
796
  dom.utils.removeItem(s.previousElementSibling);
778
797
  }
779
798
 
780
- const figure = Figure.CreateContainer(newTarget);
799
+ const figure = Figure.CreateContainer(elementToInsert);
781
800
  dom.utils.addClass(
782
801
  figure.container,
783
802
  container.className
@@ -162,6 +162,7 @@ class Modal {
162
162
 
163
163
  dom.utils.addClass(this.#modalArea, 'se-backdrop-show');
164
164
  dom.utils.addClass(this.form, 'se-modal-show');
165
+ this.#modalArea.showPopover?.();
165
166
 
166
167
  if (this.#resizeBody) {
167
168
  const offset = this.#saveOffset();
@@ -197,6 +198,7 @@ class Modal {
197
198
  this.#bindClose &&= this.#$.eventManager.removeGlobalEvent(this.#bindClose);
198
199
 
199
200
  // close
201
+ this.#modalArea.hidePopover?.();
200
202
  dom.utils.removeClass(this.#modalArea, 'se-backdrop-show');
201
203
  dom.utils.removeClass(this.form, 'se-modal-show');
202
204