suneditor 3.0.0-rc.4 → 3.0.0
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/README.md +4 -3
- package/dist/suneditor-contents.min.css +1 -1
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +10 -6
- package/src/assets/design/color.css +14 -2
- package/src/assets/design/typography.css +5 -0
- package/src/assets/icons/defaultIcons.js +22 -4
- package/src/assets/suneditor-contents.css +1 -1
- package/src/assets/suneditor.css +312 -18
- package/src/core/config/eventManager.js +6 -9
- package/src/core/editor.js +1 -1
- package/src/core/event/actions/index.js +5 -0
- package/src/core/event/effects/keydown.registry.js +25 -0
- package/src/core/event/eventOrchestrator.js +69 -2
- package/src/core/event/handlers/handler_ww_mouse.js +1 -0
- package/src/core/event/rules/keydown.rule.backspace.js +9 -1
- package/src/core/kernel/coreKernel.js +4 -0
- package/src/core/kernel/store.js +2 -0
- package/src/core/logic/dom/char.js +11 -0
- package/src/core/logic/dom/format.js +22 -0
- package/src/core/logic/dom/html.js +126 -11
- package/src/core/logic/dom/nodeTransform.js +13 -0
- package/src/core/logic/dom/offset.js +100 -37
- package/src/core/logic/dom/selection.js +54 -22
- package/src/core/logic/panel/finder.js +982 -0
- package/src/core/logic/panel/menu.js +8 -6
- package/src/core/logic/panel/toolbar.js +112 -19
- package/src/core/logic/panel/viewer.js +214 -43
- package/src/core/logic/shell/_commandExecutor.js +7 -1
- package/src/core/logic/shell/commandDispatcher.js +1 -1
- package/src/core/logic/shell/component.js +5 -7
- package/src/core/logic/shell/history.js +24 -0
- package/src/core/logic/shell/shortcuts.js +3 -3
- package/src/core/logic/shell/ui.js +25 -26
- package/src/core/schema/frameContext.js +15 -1
- package/src/core/schema/options.js +180 -39
- package/src/core/section/constructor.js +61 -20
- package/src/core/section/documentType.js +2 -2
- package/src/events.js +12 -0
- package/src/helper/clipboard.js +1 -1
- package/src/helper/converter.js +15 -0
- package/src/helper/dom/domQuery.js +12 -0
- package/src/helper/dom/domUtils.js +26 -14
- package/src/helper/index.js +3 -0
- package/src/helper/markdown.js +876 -0
- package/src/interfaces/plugins.js +7 -5
- package/src/langs/ckb.js +9 -0
- package/src/langs/cs.js +9 -0
- package/src/langs/da.js +9 -0
- package/src/langs/de.js +9 -0
- package/src/langs/en.js +9 -0
- package/src/langs/es.js +9 -0
- package/src/langs/fa.js +9 -0
- package/src/langs/fr.js +9 -0
- package/src/langs/he.js +9 -0
- package/src/langs/hu.js +9 -0
- package/src/langs/it.js +9 -0
- package/src/langs/ja.js +9 -0
- package/src/langs/km.js +9 -0
- package/src/langs/ko.js +9 -0
- package/src/langs/lv.js +9 -0
- package/src/langs/nl.js +9 -0
- package/src/langs/pl.js +9 -0
- package/src/langs/pt_br.js +9 -0
- package/src/langs/ro.js +9 -0
- package/src/langs/ru.js +9 -0
- package/src/langs/se.js +9 -0
- package/src/langs/tr.js +9 -0
- package/src/langs/uk.js +9 -0
- package/src/langs/ur.js +9 -0
- package/src/langs/zh_cn.js +9 -0
- package/src/modules/contract/Browser.js +31 -1
- package/src/modules/contract/ColorPicker.js +6 -0
- package/src/modules/contract/Controller.js +77 -39
- package/src/modules/contract/Figure.js +57 -0
- package/src/modules/contract/Modal.js +6 -0
- package/src/modules/manager/ApiManager.js +53 -4
- package/src/modules/manager/FileManager.js +18 -1
- package/src/modules/ui/ModalAnchorEditor.js +35 -2
- package/src/modules/ui/SelectMenu.js +44 -12
- package/src/plugins/browser/fileBrowser.js +5 -2
- package/src/plugins/command/codeBlock.js +324 -0
- package/src/plugins/command/exportPDF.js +15 -3
- package/src/plugins/command/fileUpload.js +4 -1
- package/src/plugins/dropdown/backgroundColor.js +5 -1
- package/src/plugins/dropdown/blockStyle.js +8 -2
- package/src/plugins/dropdown/fontColor.js +5 -1
- package/src/plugins/dropdown/hr.js +6 -0
- package/src/plugins/dropdown/layout.js +4 -1
- package/src/plugins/dropdown/lineHeight.js +3 -0
- package/src/plugins/dropdown/paragraphStyle.js +5 -5
- package/src/plugins/dropdown/table/index.js +4 -1
- package/src/plugins/dropdown/table/render/table.html.js +1 -1
- package/src/plugins/dropdown/table/services/table.grid.js +16 -8
- package/src/plugins/dropdown/table/services/table.style.js +5 -9
- package/src/plugins/dropdown/template.js +3 -0
- package/src/plugins/dropdown/textStyle.js +5 -1
- package/src/plugins/field/mention.js +5 -1
- package/src/plugins/index.js +3 -0
- package/src/plugins/input/fontSize.js +10 -3
- package/src/plugins/modal/audio.js +7 -3
- package/src/plugins/modal/embed.js +23 -20
- package/src/plugins/modal/image/index.js +5 -1
- package/src/plugins/modal/math.js +7 -2
- package/src/plugins/modal/video/index.js +21 -4
- package/src/themes/cobalt.css +13 -4
- package/src/themes/cream.css +11 -2
- package/src/themes/dark.css +13 -4
- package/src/themes/midnight.css +13 -4
- package/src/typedef.js +4 -4
- package/types/assets/icons/defaultIcons.d.ts +12 -1
- package/types/assets/suneditor.css.d.ts +1 -1
- package/types/core/config/eventManager.d.ts +6 -8
- package/types/core/event/actions/index.d.ts +1 -0
- package/types/core/event/effects/keydown.registry.d.ts +2 -0
- package/types/core/event/eventOrchestrator.d.ts +2 -1
- package/types/core/kernel/coreKernel.d.ts +5 -0
- package/types/core/kernel/store.d.ts +5 -0
- package/types/core/logic/dom/char.d.ts +11 -0
- package/types/core/logic/dom/format.d.ts +22 -0
- package/types/core/logic/dom/html.d.ts +16 -0
- package/types/core/logic/dom/nodeTransform.d.ts +13 -0
- package/types/core/logic/dom/offset.d.ts +23 -2
- package/types/core/logic/dom/selection.d.ts +9 -3
- package/types/core/logic/panel/finder.d.ts +83 -0
- package/types/core/logic/panel/toolbar.d.ts +14 -1
- package/types/core/logic/panel/viewer.d.ts +22 -2
- package/types/core/logic/shell/shortcuts.d.ts +1 -1
- package/types/core/schema/frameContext.d.ts +22 -0
- package/types/core/schema/options.d.ts +362 -79
- package/types/events.d.ts +11 -0
- package/types/helper/converter.d.ts +15 -0
- package/types/helper/dom/domQuery.d.ts +12 -0
- package/types/helper/dom/domUtils.d.ts +23 -2
- package/types/helper/index.d.ts +5 -0
- package/types/helper/markdown.d.ts +27 -0
- package/types/interfaces/plugins.d.ts +7 -5
- package/types/langs/_Lang.d.ts +9 -0
- package/types/modules/contract/Browser.d.ts +36 -2
- package/types/modules/contract/ColorPicker.d.ts +6 -0
- package/types/modules/contract/Controller.d.ts +35 -1
- package/types/modules/contract/Figure.d.ts +57 -0
- package/types/modules/contract/Modal.d.ts +6 -0
- package/types/modules/manager/ApiManager.d.ts +26 -0
- package/types/modules/manager/FileManager.d.ts +17 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +41 -4
- package/types/modules/ui/SelectMenu.d.ts +40 -2
- package/types/plugins/browser/fileBrowser.d.ts +10 -4
- package/types/plugins/command/codeBlock.d.ts +53 -0
- package/types/plugins/command/fileUpload.d.ts +8 -2
- package/types/plugins/dropdown/backgroundColor.d.ts +10 -2
- package/types/plugins/dropdown/blockStyle.d.ts +14 -2
- package/types/plugins/dropdown/fontColor.d.ts +10 -2
- package/types/plugins/dropdown/hr.d.ts +12 -0
- package/types/plugins/dropdown/layout.d.ts +8 -2
- package/types/plugins/dropdown/lineHeight.d.ts +6 -0
- package/types/plugins/dropdown/paragraphStyle.d.ts +14 -3
- package/types/plugins/dropdown/table/index.d.ts +9 -3
- package/types/plugins/dropdown/template.d.ts +6 -0
- package/types/plugins/dropdown/textStyle.d.ts +10 -2
- package/types/plugins/field/mention.d.ts +10 -2
- package/types/plugins/index.d.ts +3 -0
- package/types/plugins/input/fontSize.d.ts +18 -4
- package/types/plugins/modal/audio.d.ts +14 -6
- package/types/plugins/modal/embed.d.ts +44 -38
- package/types/plugins/modal/image/index.d.ts +9 -1
- package/types/plugins/modal/link.d.ts +6 -2
- package/types/plugins/modal/math.d.ts +23 -5
- package/types/plugins/modal/video/index.d.ts +49 -9
- package/types/typedef.d.ts +5 -2
|
@@ -301,6 +301,8 @@ class NodeTransform {
|
|
|
301
301
|
* @description Remove nested tags without other child nodes.
|
|
302
302
|
* @param {Node} element Element object
|
|
303
303
|
* @param {?(((current: Node) => boolean)|string)} [validation] Validation function / String(`tag1|tag2..`) / If `null`, all tags are applicable.
|
|
304
|
+
* @example
|
|
305
|
+
* editor.$.nodeTransform.mergeNestedTags(parentElement, (current) => current.nodeName === 'SPAN');
|
|
304
306
|
*/
|
|
305
307
|
mergeNestedTags(element, validation) {
|
|
306
308
|
if (typeof validation === 'string') {
|
|
@@ -419,6 +421,17 @@ class NodeTransform {
|
|
|
419
421
|
* @param {SunEditor.NodeCollection} nodeArray An array of nodes to clone. The first node in the array will be the top-level parent.
|
|
420
422
|
* @param {?(current: Node) => boolean} [validate] A validate function.
|
|
421
423
|
* @returns {{ parent: Node, inner: Node }} An object containing the top-level parent node and the innermost child node.
|
|
424
|
+
* @example
|
|
425
|
+
* // [div, span, em] → <div><span><em></em></span></div> (cloned)
|
|
426
|
+
* const { parent, inner } = editor.$.nodeTransform.createNestedNode([div, span, em]);
|
|
427
|
+
* // parent = div (top), inner = em (innermost)
|
|
428
|
+
*
|
|
429
|
+
* // validate: skip nodes that fail the condition
|
|
430
|
+
* const { parent, inner } = editor.$.nodeTransform.createNestedNode(
|
|
431
|
+
* [div, span, em],
|
|
432
|
+
* (node) => node.nodeName !== 'SPAN'
|
|
433
|
+
* );
|
|
434
|
+
* // Result: <div><em></em></div> (SPAN excluded)
|
|
422
435
|
*/
|
|
423
436
|
createNestedNode(nodeArray, validate) {
|
|
424
437
|
if (typeof validate !== 'function') validate = () => true;
|
|
@@ -201,6 +201,7 @@ class Offset {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
+
* @deprecated
|
|
204
205
|
* @description Gets the current editor-relative scroll offset.
|
|
205
206
|
* @param {?Node} [node] Target element.
|
|
206
207
|
* @returns {OffsetGlobalScrollInfo} Global scroll information.
|
|
@@ -357,41 +358,67 @@ class Offset {
|
|
|
357
358
|
* @param {HTMLElement} e_container Element's root container
|
|
358
359
|
* @param {HTMLElement} target Target element to position against
|
|
359
360
|
* @param {HTMLElement} t_container Target's root container
|
|
361
|
+
* @param {Object} [opts] Options
|
|
362
|
+
* @param {boolean} [opts.preferUp=false] Open upward by default (for bottom toolbar)
|
|
360
363
|
*/
|
|
361
|
-
setRelPosition(element, e_container, target, t_container) {
|
|
364
|
+
setRelPosition(element, e_container, target, t_container, { preferUp } = {}) {
|
|
362
365
|
const isFixedContainer = /^fixed$/i.test(_w.getComputedStyle(t_container).position);
|
|
363
366
|
const tGlobal = this.getGlobal(target);
|
|
364
367
|
|
|
365
368
|
// top
|
|
366
369
|
if (isFixedContainer) {
|
|
367
370
|
element.style.position = 'fixed';
|
|
368
|
-
|
|
371
|
+
if (preferUp) {
|
|
372
|
+
element.style.top = `${tGlobal.fixedTop - element.offsetHeight}px`;
|
|
373
|
+
} else {
|
|
374
|
+
element.style.top = `${tGlobal.fixedTop + tGlobal.height}px`;
|
|
375
|
+
}
|
|
369
376
|
} else {
|
|
370
377
|
element.style.position = '';
|
|
371
378
|
|
|
372
379
|
const isSameContainer = t_container.contains(element);
|
|
373
380
|
const containerTop = isSameContainer ? this.getGlobal(e_container).top : 0;
|
|
374
381
|
const elHeight = element.offsetHeight;
|
|
375
|
-
const scrollTop =
|
|
382
|
+
const scrollTop = _w.scrollY;
|
|
376
383
|
const bt = tGlobal.top;
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
385
|
+
if (preferUp) {
|
|
386
|
+
// Try to open above
|
|
387
|
+
const menuHeight_top = containerTop - scrollTop + bt;
|
|
388
|
+
if (menuHeight_top < elHeight) {
|
|
389
|
+
// Not enough space above — try below
|
|
390
|
+
const menuHeight_bottom = getClientSize(_d).h - (containerTop - scrollTop + bt + target.offsetHeight);
|
|
391
|
+
if (menuHeight_bottom >= elHeight) {
|
|
392
|
+
element.style.top = `${bt + target.offsetHeight}px`;
|
|
393
|
+
} else if (menuHeight_bottom > menuHeight_top) {
|
|
394
|
+
element.style.height = `${menuHeight_bottom}px`;
|
|
395
|
+
element.style.top = `${bt + target.offsetHeight}px`;
|
|
396
|
+
} else {
|
|
397
|
+
element.style.height = `${menuHeight_top}px`;
|
|
398
|
+
element.style.top = `${-1 * (menuHeight_top - bt + 3)}px`;
|
|
399
|
+
}
|
|
387
400
|
} else {
|
|
388
|
-
element.style.
|
|
389
|
-
menuTop = bt + target.offsetHeight;
|
|
401
|
+
element.style.top = `${bt - elHeight}px`;
|
|
390
402
|
}
|
|
391
|
-
|
|
392
|
-
element.style.top = `${menuTop}px`;
|
|
393
403
|
} else {
|
|
394
|
-
|
|
404
|
+
const menuHeight_bottom = getClientSize(_d).h - (containerTop - scrollTop + bt + target.offsetHeight);
|
|
405
|
+
if (menuHeight_bottom < elHeight) {
|
|
406
|
+
let menuTop = -1 * (elHeight - bt + 3);
|
|
407
|
+
const insTop = containerTop - scrollTop + menuTop;
|
|
408
|
+
const menuHeight_top = elHeight + (insTop < 0 ? insTop : 0);
|
|
409
|
+
|
|
410
|
+
if (menuHeight_top > menuHeight_bottom) {
|
|
411
|
+
element.style.height = `${menuHeight_top}px`;
|
|
412
|
+
menuTop = -1 * (menuHeight_top - bt + 3);
|
|
413
|
+
} else {
|
|
414
|
+
element.style.height = `${menuHeight_bottom}px`;
|
|
415
|
+
menuTop = bt + target.offsetHeight;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
element.style.top = `${menuTop}px`;
|
|
419
|
+
} else {
|
|
420
|
+
element.style.top = `${bt + target.offsetHeight}px`;
|
|
421
|
+
}
|
|
395
422
|
}
|
|
396
423
|
}
|
|
397
424
|
|
|
@@ -425,15 +452,20 @@ class Offset {
|
|
|
425
452
|
* @param {HTMLElement} target Target element
|
|
426
453
|
* @param {Object} params Position parameters
|
|
427
454
|
* @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
|
|
455
|
+
* @param {{left:number, right:number, top:number}} [params.addOffset={left:0, right:0, top:0}] Additional offset
|
|
429
456
|
* @param {"bottom"|"top"} [params.position="bottom"] Position ('bottom'|'top')
|
|
430
457
|
* @param {*} params.inst Instance object of caller
|
|
431
458
|
* @param {HTMLElement} [params.sibling=null] The sibling controller element
|
|
432
459
|
* @returns {{position: "top" | "bottom"} | undefined} Success -> {position: current position}
|
|
460
|
+
* @example
|
|
461
|
+
* const result = editor.$.offset.setAbsPosition(controller, targetElement, {
|
|
462
|
+
* position: 'bottom', inst: this, addOffset: { left: 0, right: 0, top: 0 }
|
|
463
|
+
* });
|
|
433
464
|
*/
|
|
434
465
|
setAbsPosition(element, target, params) {
|
|
435
466
|
const addOffset = {
|
|
436
467
|
left: 0,
|
|
468
|
+
right: 0,
|
|
437
469
|
top: 0,
|
|
438
470
|
...params.addOffset,
|
|
439
471
|
};
|
|
@@ -478,7 +510,9 @@ class Offset {
|
|
|
478
510
|
const { rmt, rmb, bMargin, rt } = this.#getVMargin(tmtw, tmbw, toolbarH, clientSize, targetRect, isTextSelection, isToolbarTarget);
|
|
479
511
|
if ((isWWTarget && (rmb - statusBarH + targetH <= 0 || rmt + rt + targetH - (this.#$.toolbar.isSticky && isInlineTarget ? toolbarH : 0) <= 0)) || rmt + targetH < 0) return;
|
|
480
512
|
|
|
481
|
-
const
|
|
513
|
+
const topAreaRect = this.#frameContext.get('topArea').getBoundingClientRect();
|
|
514
|
+
const isStickyVisible = this.#store.mode.isBottom ? topAreaRect.bottom >= _w.innerHeight - th : topAreaRect.top <= th;
|
|
515
|
+
const isSticky = this.#$.toolbar.isSticky && this.#context.get('toolbar_main').style.display !== 'none' && (!headLess || isStickyVisible);
|
|
482
516
|
let t = addOffset.top;
|
|
483
517
|
let y = 0;
|
|
484
518
|
let arrowDir = '';
|
|
@@ -504,7 +538,7 @@ class Offset {
|
|
|
504
538
|
else {
|
|
505
539
|
arrowDir = 'down';
|
|
506
540
|
t += targetRect.top - elH - ah + wScrollY;
|
|
507
|
-
y = (isSticky ? targetRect.top - toolbarH : rmt) - elH - ah;
|
|
541
|
+
y = (isSticky && !this.#store.mode.isBottom ? targetRect.top - toolbarH : rmt) - elH - ah;
|
|
508
542
|
// change to [bottom] position
|
|
509
543
|
if (y - siblingH < 0) {
|
|
510
544
|
arrowDir = 'up';
|
|
@@ -536,7 +570,7 @@ class Offset {
|
|
|
536
570
|
arrow.style.right = '';
|
|
537
571
|
}
|
|
538
572
|
|
|
539
|
-
let l = addOffset.left;
|
|
573
|
+
let l = addOffset.left || (addOffset.right ? (isLTR ? addOffset.right - element.offsetWidth : element.offsetWidth - addOffset.right) : 0);
|
|
540
574
|
let x = 0;
|
|
541
575
|
let ax = 0;
|
|
542
576
|
let awLimit = 0;
|
|
@@ -591,6 +625,9 @@ class Offset {
|
|
|
591
625
|
* @param {"bottom"|"top"} [options.position="bottom"] Position ('bottom'|'top')
|
|
592
626
|
* @param {number} [options.addTop=0] Additional top offset
|
|
593
627
|
* @returns {boolean} Success / Failure
|
|
628
|
+
* @example
|
|
629
|
+
* const success = editor.$.offset.setRangePosition(toolbar, null, { position: 'bottom', addTop: 0 });
|
|
630
|
+
* if (!success) toolbar.style.display = 'none';
|
|
594
631
|
*/
|
|
595
632
|
setRangePosition(element, range, { position, addTop } = {}) {
|
|
596
633
|
element.style.top = '-10000px';
|
|
@@ -604,12 +641,13 @@ class Offset {
|
|
|
604
641
|
|
|
605
642
|
const isFullScreen = this.#frameContext.get('isFullScreen');
|
|
606
643
|
const topArea = this.#frameContext.get('topArea');
|
|
644
|
+
const isInCarrier = this.#carrierWrapper.contains(element);
|
|
607
645
|
const rects = rectsObj.rects;
|
|
608
646
|
const scrollLeft = isFullScreen ? 0 : rectsObj.scrollLeft;
|
|
609
647
|
const scrollTop = isFullScreen ? 0 : rectsObj.scrollTop;
|
|
610
|
-
const editorWidth = topArea.offsetWidth;
|
|
648
|
+
const editorWidth = isInCarrier ? getClientSize(_d).w : topArea.offsetWidth;
|
|
611
649
|
const offsets = this.getGlobal(topArea);
|
|
612
|
-
const editorLeft = offsets.left;
|
|
650
|
+
const editorLeft = isInCarrier ? 0 : offsets.left;
|
|
613
651
|
const toolbarWidth = element.offsetWidth;
|
|
614
652
|
const toolbarHeight = element.offsetHeight;
|
|
615
653
|
|
|
@@ -733,33 +771,58 @@ class Offset {
|
|
|
733
771
|
bMargin = clientSize.h - targetRect.bottom;
|
|
734
772
|
const editorOffset = this.getGlobal();
|
|
735
773
|
|
|
774
|
+
const isBottom = this.#store.mode.isBottom;
|
|
736
775
|
if (!isTextSelection) {
|
|
737
776
|
const emt = editorOffset.fixedTop > 0 ? editorOffset.fixedTop : 0;
|
|
738
777
|
const emb = _w.innerHeight - (editorOffset.fixedTop + editorOffset.height);
|
|
739
778
|
rt = !isToolbarTarget && (this.#$.toolbar.isSticky || !this.#$.toolbar.isBalloonMode) ? toolbarH : 0;
|
|
740
|
-
|
|
741
|
-
|
|
779
|
+
if (isBottom) {
|
|
780
|
+
rmt = tMargin - (!isToolbarTarget ? emt : 0);
|
|
781
|
+
rmb = bMargin - (emb > 0 ? emb : 0) - rt;
|
|
782
|
+
} else {
|
|
783
|
+
rmt = tMargin - (!isToolbarTarget ? emt : 0) - rt;
|
|
784
|
+
rmb = bMargin - (emb > 0 ? emb : 0);
|
|
785
|
+
}
|
|
742
786
|
} else {
|
|
743
787
|
rt = !isToolbarTarget && !this.#$.toolbar.isSticky && !this.#options.get('toolbar_container') ? toolbarH : 0;
|
|
744
788
|
const wst = !isIframe ? editorOffset.top - _w.scrollY + rt : 0;
|
|
745
789
|
const wsb = !isIframe ? this.#store.get('currentViewportHeight') - (editorOffset.top + editorOffset.height - _w.scrollY) : 0;
|
|
746
790
|
let st = wst;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
791
|
+
let sb = wsb;
|
|
792
|
+
if (isBottom) {
|
|
793
|
+
if (toolbarH > wsb) {
|
|
794
|
+
if (this.#$.toolbar.isSticky) {
|
|
795
|
+
sb = toolbarH;
|
|
796
|
+
} else {
|
|
797
|
+
sb = wsb + toolbarH;
|
|
798
|
+
}
|
|
799
|
+
} else if (this.#options.get('toolbar_container') && !this.#$.toolbar.isSticky) {
|
|
800
|
+
toolbarH = 0;
|
|
750
801
|
} else {
|
|
751
|
-
|
|
802
|
+
sb = wsb + toolbarH;
|
|
752
803
|
}
|
|
753
|
-
|
|
754
|
-
|
|
804
|
+
|
|
805
|
+
rmt = targetRect.top - (wwRects.top - wst);
|
|
806
|
+
rmb = wwRects.bottom - (targetRect.bottom - sb) + toolbarH;
|
|
807
|
+
rmb = rmb > 0 ? rmb : rmb - toolbarH;
|
|
755
808
|
} else {
|
|
756
|
-
|
|
757
|
-
|
|
809
|
+
if (toolbarH > wst) {
|
|
810
|
+
if (this.#$.toolbar.isSticky) {
|
|
811
|
+
st = toolbarH;
|
|
812
|
+
} else {
|
|
813
|
+
st = wst + toolbarH;
|
|
814
|
+
}
|
|
815
|
+
} else if (this.#options.get('toolbar_container') && !this.#$.toolbar.isSticky) {
|
|
816
|
+
toolbarH = 0;
|
|
817
|
+
} else {
|
|
818
|
+
st = wst + toolbarH;
|
|
819
|
+
}
|
|
758
820
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
821
|
+
rmt = targetRect.top - (wwRects.top - st) + toolbarH;
|
|
822
|
+
rmb = wwRects.bottom - (targetRect.bottom - wsb);
|
|
823
|
+
// display margin
|
|
824
|
+
rmt = rmt > 0 ? rmt : rmt - toolbarH;
|
|
825
|
+
}
|
|
763
826
|
}
|
|
764
827
|
|
|
765
828
|
return {
|
|
@@ -210,6 +210,9 @@ class Selection_ {
|
|
|
210
210
|
* - If there is no next sibling but a previous sibling exists, it returns the previous sibling with an offset of 1.
|
|
211
211
|
* @param {Node} target Target node whose neighboring range is to be determined.
|
|
212
212
|
* @returns {{container: Node, offset: number}|null} An object containing the nearest container node and its offset.
|
|
213
|
+
* @example
|
|
214
|
+
* const nearRange = editor.$.selection.getNearRange(targetNode);
|
|
215
|
+
* if (nearRange) editor.$.selection.setRange(nearRange.container, nearRange.offset, nearRange.container, nearRange.offset);
|
|
213
216
|
*/
|
|
214
217
|
getNearRange(target) {
|
|
215
218
|
const next = target.nextSibling;
|
|
@@ -249,6 +252,9 @@ class Selection_ {
|
|
|
249
252
|
/**
|
|
250
253
|
* @description Get current select node
|
|
251
254
|
* @returns {HTMLElement|Text}
|
|
255
|
+
* @example
|
|
256
|
+
* const node = editor.$.selection.getNode();
|
|
257
|
+
* const line = editor.$.format.getLine(node);
|
|
252
258
|
*/
|
|
253
259
|
getNode() {
|
|
254
260
|
if (!this.#frameContext.get('wysiwyg').contains(this.selectionNode)) this.init();
|
|
@@ -286,10 +292,9 @@ class Selection_ {
|
|
|
286
292
|
getRects(target, position) {
|
|
287
293
|
const targetAbs = dom.check.isElement(/** @type {Node} */ (target)) ? _w.getComputedStyle(target).position === 'absolute' : false;
|
|
288
294
|
target = /** @type {Range} */ (!target || dom.check.isText(/** @type {Node} */ (target)) ? this.getRange() : target);
|
|
289
|
-
const globalScroll = this.#$.offset.getGlobalScroll();
|
|
290
295
|
let isStartPosition = position === 'start';
|
|
291
|
-
let scrollLeft =
|
|
292
|
-
let scrollTop =
|
|
296
|
+
let scrollLeft = _w.scrollX;
|
|
297
|
+
let scrollTop = _w.scrollY;
|
|
293
298
|
|
|
294
299
|
let rects = /** @type {*} */ (target).getClientRects();
|
|
295
300
|
rects = rects[isStartPosition ? 0 : rects.length - 1];
|
|
@@ -373,7 +378,7 @@ class Selection_ {
|
|
|
373
378
|
/**
|
|
374
379
|
* @description Scroll to the corresponding selection or range position.
|
|
375
380
|
* @param {Selection|Range|Node} ref selection or range object
|
|
376
|
-
* @param {
|
|
381
|
+
* @param {ScrollIntoViewOptions & {noFocus?: boolean}} [scrollOption] Scroll options. Extends `ScrollIntoViewOptions` (`behavior`, `block`, `inline`) with `noFocus` to prevent focus change.
|
|
377
382
|
* @example
|
|
378
383
|
* // Scroll to current selection smoothly
|
|
379
384
|
* editor.selection.scrollTo(editor.selection.get());
|
|
@@ -389,10 +394,18 @@ class Selection_ {
|
|
|
389
394
|
* });
|
|
390
395
|
*/
|
|
391
396
|
scrollTo(ref, scrollOption) {
|
|
397
|
+
const noFocus = scrollOption?.noFocus;
|
|
398
|
+
|
|
392
399
|
if (this.#instanceCheck.isSelection(ref)) {
|
|
393
400
|
ref = ref.getRangeAt(0);
|
|
394
401
|
} else if (this.#instanceCheck.isNode(ref)) {
|
|
395
|
-
|
|
402
|
+
if (noFocus) {
|
|
403
|
+
const range = this.#frameContext.get('_wd').createRange();
|
|
404
|
+
range.selectNodeContents(ref);
|
|
405
|
+
ref = range;
|
|
406
|
+
} else {
|
|
407
|
+
ref = this.setRange(ref, 1, ref, 1);
|
|
408
|
+
}
|
|
396
409
|
} else if (typeof ref?.startContainer === 'undefined') {
|
|
397
410
|
console.warn('[SUNEDITOR.html.scrollTo.warn] "selectionRange" must be Selection or Range or Node object.', ref);
|
|
398
411
|
}
|
|
@@ -401,6 +414,7 @@ class Selection_ {
|
|
|
401
414
|
if (!el) return;
|
|
402
415
|
|
|
403
416
|
scrollOption = { behavior: 'smooth', block: 'nearest', inline: 'nearest', ...scrollOption };
|
|
417
|
+
delete scrollOption.noFocus;
|
|
404
418
|
|
|
405
419
|
const ww = this.#frameContext.get('_ww');
|
|
406
420
|
const wwFrame = this.#frameContext.get('wysiwygFrame');
|
|
@@ -408,6 +422,7 @@ class Selection_ {
|
|
|
408
422
|
const isAutoHeight = !this.#store.get('isScrollable')(this.#frameContext);
|
|
409
423
|
const viewportHeight = this.#store.get('currentViewportHeight');
|
|
410
424
|
const scrollY = isAutoHeight ? _w.scrollY : isIframe ? ww.scrollY : wwFrame.scrollTop;
|
|
425
|
+
const isBottom = this.#store.mode.isBottom;
|
|
411
426
|
const realToolbarHeight = this.#context.get('toolbar_main').offsetHeight;
|
|
412
427
|
const toolbarHeight = this.#$.toolbar.isSticky ? realToolbarHeight : 0;
|
|
413
428
|
const positionToolbarHeight = this.#$.toolbar.isSticky ? toolbarHeight + this.#options.get('toolbar_sticky') : toolbarHeight;
|
|
@@ -417,8 +432,18 @@ class Selection_ {
|
|
|
417
432
|
el?.scrollIntoView(scrollOption);
|
|
418
433
|
|
|
419
434
|
if (scrollOption?.behavior === 'auto' && scrollY !== _w.scrollY) {
|
|
420
|
-
if (positionToolbarHeight
|
|
421
|
-
|
|
435
|
+
if (positionToolbarHeight) {
|
|
436
|
+
if (isBottom) {
|
|
437
|
+
// bottom toolbar covers the bottom — scroll down more so the element clears the toolbar
|
|
438
|
+
if (scrollY < _w.scrollY) {
|
|
439
|
+
_w.scrollBy(0, positionToolbarHeight);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
// top toolbar covers the top — scroll up more so the element clears the toolbar
|
|
443
|
+
if (scrollY > _w.scrollY) {
|
|
444
|
+
_w.scrollBy(0, -positionToolbarHeight);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
422
447
|
} else if (isAutoHeight) {
|
|
423
448
|
_w.scrollBy(0, statusbarHeight);
|
|
424
449
|
}
|
|
@@ -429,55 +454,62 @@ class Selection_ {
|
|
|
429
454
|
|
|
430
455
|
// --- When there is no upper scroll and it is an iframe ---
|
|
431
456
|
const PADDING = this.#scrollMargin;
|
|
432
|
-
|
|
433
|
-
const
|
|
457
|
+
// Reduce effective viewport height by toolbar+offset when bottom toolbar is sticky
|
|
458
|
+
const viewHeight = isAutoHeight ? viewportHeight - (isBottom ? positionToolbarHeight : 0) : wwFrame.offsetHeight;
|
|
459
|
+
|
|
460
|
+
// Use range rect for accurate height — el.offsetHeight includes nested children (e.g. nested lists)
|
|
461
|
+
const refRect = ref?.getBoundingClientRect?.();
|
|
462
|
+
const elH = (refRect?.height > 0 ? refRect.height : el.offsetHeight) || 0;
|
|
434
463
|
|
|
435
464
|
const behavior = scrollOption?.behavior;
|
|
465
|
+
const topToolbarH = isBottom ? 0 : positionToolbarHeight;
|
|
436
466
|
if (isAutoHeight) {
|
|
437
467
|
if (isIframe) {
|
|
438
468
|
const rect = this.getRects(ref, 'end').rects;
|
|
439
|
-
const topMargin = rect.top + elH -
|
|
469
|
+
const topMargin = rect.top + elH - topToolbarH;
|
|
440
470
|
const bottomMargin = viewHeight - PADDING - (rect.top + elH);
|
|
441
471
|
if (topMargin >= 0 && bottomMargin >= 0) return;
|
|
442
472
|
|
|
443
473
|
const newScrollTop = scrollY - (topMargin < 0 ? -(topMargin - PADDING) : bottomMargin);
|
|
444
474
|
_w.scrollTo({
|
|
445
|
-
top: newScrollTop < scrollY ? newScrollTop -
|
|
475
|
+
top: newScrollTop < scrollY ? newScrollTop - topToolbarH : newScrollTop,
|
|
446
476
|
behavior,
|
|
447
477
|
});
|
|
448
478
|
} else {
|
|
449
479
|
const rect = this.#$.offset.getGlobal(el);
|
|
450
480
|
const scrollMargin = viewHeight + scrollY - rect.top - elH;
|
|
451
481
|
|
|
452
|
-
if (scrollMargin - PADDING > 0 && viewHeight > scrollMargin + PADDING +
|
|
482
|
+
if (scrollMargin - PADDING > 0 && viewHeight > scrollMargin + PADDING + topToolbarH) return;
|
|
453
483
|
|
|
454
484
|
const newScrollTop = scrollMargin <= PADDING ? scrollY - scrollMargin + PADDING + statusbarHeight : scrollY - scrollMargin + (viewHeight - elH - PADDING);
|
|
455
485
|
_w.scrollTo({
|
|
456
|
-
top: newScrollTop < scrollY ? newScrollTop -
|
|
486
|
+
top: newScrollTop < scrollY ? newScrollTop - topToolbarH : newScrollTop,
|
|
457
487
|
behavior,
|
|
458
488
|
});
|
|
459
489
|
}
|
|
460
490
|
} else {
|
|
461
|
-
// local scroll
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
const innerTop =
|
|
491
|
+
// local scroll — use range rect for accurate position (el.getBoundingClientRect includes nested children)
|
|
492
|
+
const hasRefRect = refRect?.height > 0;
|
|
493
|
+
const targetTop = hasRefRect ? refRect.top : el.getBoundingClientRect().top;
|
|
494
|
+
const innerTop = isIframe ? targetTop : targetTop - wwFrame.getBoundingClientRect().top;
|
|
465
495
|
|
|
466
496
|
const keepLocalScroll = innerTop - PADDING > 0 && innerTop + PADDING <= viewHeight;
|
|
467
|
-
const rectScroll = innerTop - PADDING > 0 ? innerTop + PADDING - viewHeight : innerTop - (toolbarHeight + elH);
|
|
497
|
+
const rectScroll = isBottom ? (innerTop - PADDING > 0 ? innerTop + PADDING - viewHeight + toolbarHeight : innerTop - elH) : innerTop - PADDING > 0 ? innerTop + PADDING - viewHeight : innerTop - (toolbarHeight + elH);
|
|
468
498
|
let newScrollTop = scrollY + rectScroll;
|
|
469
499
|
|
|
470
500
|
// frame scroll
|
|
471
501
|
const gy = _w.scrollY;
|
|
472
502
|
const globalRect = this.#$.offset.getGlobal();
|
|
473
|
-
const
|
|
474
|
-
const
|
|
503
|
+
const topToolbarFrame = isBottom ? 0 : realToolbarHeight;
|
|
504
|
+
const bottomToolbarFrame = isBottom ? realToolbarHeight : 0;
|
|
505
|
+
const topMargin = gy - globalRect.top + topToolbarFrame;
|
|
506
|
+
const bottomMargin = globalRect.top + globalRect.height - (gy + viewportHeight) + bottomToolbarFrame;
|
|
475
507
|
|
|
476
508
|
// set frame scroll
|
|
477
509
|
if (topMargin > 0) {
|
|
478
510
|
const newFrameY = (keepLocalScroll ? innerTop : innerTop + scrollY - newScrollTop) - elH - PADDING - topMargin;
|
|
479
511
|
if (newFrameY < 0) {
|
|
480
|
-
newScrollTop +=
|
|
512
|
+
newScrollTop += topToolbarFrame;
|
|
481
513
|
_w.scrollTo({
|
|
482
514
|
top: gy + newFrameY,
|
|
483
515
|
behavior: 'smooth',
|
|
@@ -487,7 +519,7 @@ class Selection_ {
|
|
|
487
519
|
if (bottomMargin > 0) {
|
|
488
520
|
const newFrameY = (keepLocalScroll ? innerTop : innerTop + scrollY - newScrollTop) + elH + PADDING - (globalRect.height - bottomMargin);
|
|
489
521
|
if (newFrameY > 0) {
|
|
490
|
-
newScrollTop += statusbarHeight;
|
|
522
|
+
newScrollTop += isBottom ? bottomToolbarFrame : statusbarHeight;
|
|
491
523
|
_w.scrollTo({
|
|
492
524
|
top: gy + newFrameY,
|
|
493
525
|
behavior: 'smooth',
|