diff --git a/blots/scroll.js b/blots/scroll.js index e9accc9aa7..eab6a4e2b1 100644 --- a/blots/scroll.js +++ b/blots/scroll.js @@ -15,6 +15,7 @@ class Scroll extends Parchment.Scroll { constructor(domNode, config) { super(domNode); this.emitter = config.emitter; + this.scrollingContainer = config.scrollingContainer; if (Array.isArray(config.whitelist)) { this.whitelist = config.whitelist.reduce(function(whitelist, format) { whitelist[format] = true; diff --git a/core/quill.js b/core/quill.js index 5fc40ea4dd..f637949d00 100644 --- a/core/quill.js +++ b/core/quill.js @@ -59,7 +59,6 @@ class Quill { constructor(container, options = {}) { this.options = expandConfig(container, options); this.container = this.options.container; - this.scrollingContainer = this.options.scrollingContainer || document.body; if (this.container == null) { return debug.error('Invalid Quill container', container); } @@ -72,9 +71,11 @@ class Quill { this.container.__quill = this; this.root = this.addContainer('ql-editor'); this.root.classList.add('ql-blank'); + this.scrollingContainer = this.options.scrollingContainer || this.root; this.emitter = new Emitter(); this.scroll = Parchment.create(this.root, { emitter: this.emitter, + scrollingContainer: this.scrollingContainer, whitelist: this.options.formats }); this.editor = new Editor(this.scroll); @@ -180,11 +181,21 @@ class Quill { } getBounds(index, length = 0) { + let bounds; if (typeof index === 'number') { - return this.selection.getBounds(index, length); + bounds = this.selection.getBounds(index, length); } else { - return this.selection.getBounds(index.index, index.length); + bounds = this.selection.getBounds(index.index, index.length); } + let containerBounds = this.container.getBoundingClientRect(); + return { + bottom: bounds.bottom - containerBounds.top, + height: bounds.height, + left: bounds.left - containerBounds.left, + right: bounds.right - containerBounds.left, + top: bounds.top - containerBounds.top, + width: bounds.width + }; } getContents(index = 0, length = this.getLength() - index) { diff --git a/core/selection.js b/core/selection.js index cefd4f9239..d3fd19e0bb 100644 --- a/core/selection.js +++ b/core/selection.js @@ -89,7 +89,7 @@ class Selection { let scrollLength = this.scroll.length(); index = Math.min(index, scrollLength - 1); length = Math.min(index + length, scrollLength - 1) - index; - let bounds, node, [leaf, offset] = this.scroll.leaf(index); + let node, [leaf, offset] = this.scroll.leaf(index); if (leaf == null) return null; [node, offset] = leaf.position(offset, true); let range = document.createRange(); @@ -99,7 +99,7 @@ class Selection { if (leaf == null) return null; [node, offset] = leaf.position(offset, true); range.setEnd(node, offset); - bounds = range.getBoundingClientRect(); + return range.getBoundingClientRect(); } else { let side = 'left'; let rect; @@ -117,22 +117,15 @@ class Selection { rect = leaf.domNode.getBoundingClientRect(); if (offset > 0) side = 'right'; } - bounds = { + return { + bottom: rect.top + rect.height, height: rect.height, left: rect[side], - width: 0, - top: rect.top + right: rect[side], + top: rect.top, + width: 0 }; } - let containerBounds = this.root.parentNode.getBoundingClientRect(); - return { - left: bounds.left - containerBounds.left, - right: bounds.left + bounds.width - containerBounds.left, - top: bounds.top - containerBounds.top, - bottom: bounds.top + bounds.height - containerBounds.top, - height: bounds.height, - width: bounds.width - }; } getNativeRange() { @@ -200,12 +193,19 @@ class Selection { if (range == null) return; let bounds = this.getBounds(range.index, range.length); if (bounds == null) return; - if (this.root.offsetHeight < bounds.bottom) { - let [line, ] = this.scroll.line(Math.min(range.index + range.length, this.scroll.length()-1)); - this.root.scrollTop = line.domNode.offsetTop + line.domNode.offsetHeight - this.root.offsetHeight; - } else if (bounds.top < 0) { - let [line, ] = this.scroll.line(Math.min(range.index, this.scroll.length()-1)); - this.root.scrollTop = line.domNode.offsetTop; + let limit = this.scroll.length()-1; + let [first, ] = this.scroll.line(Math.min(range.index, limit)); + let last = first; + if (range.length > 0) { + [last, ] = this.scroll.line(Math.min(range.index + range.length, limit)); + } + if (first == null || last == null) return; + let scroller = this.scroll.scrollingContainer; + let scrollBounds = scroller.getBoundingClientRect(); + if (bounds.top < scrollBounds.top) { + scroller.scrollTop -= (scrollBounds.top - bounds.top); + } else if (bounds.bottom > scrollBounds.bottom) { + scroller.scrollTop += (bounds.bottom - scrollBounds.bottom); } } diff --git a/test/unit/core/quill.js b/test/unit/core/quill.js index 0642ef537e..6edfab22a9 100644 --- a/test/unit/core/quill.js +++ b/test/unit/core/quill.js @@ -101,13 +101,11 @@ describe('Quill', function() { }); it('getBounds() index', function() { - let bounds = this.quill.selection.getBounds(1); - expect(this.quill.getBounds(1)).toEqual(bounds); + expect(this.quill.getBounds(1)).toBeTruthy(); }); it('getBounds() range', function() { - let bounds = this.quill.selection.getBounds(3, 4); - expect(this.quill.getBounds(new Range(3, 4))).toEqual(bounds); + expect(this.quill.getBounds(new Range(3, 4))).toBeTruthy(); }); it('getFormat()', function() { diff --git a/test/unit/core/selection.js b/test/unit/core/selection.js index ef5be67ffe..a9a157b3d0 100644 --- a/test/unit/core/selection.js +++ b/test/unit/core/selection.js @@ -354,7 +354,6 @@ describe('Selection', function() { this.container.classList.add('ql-editor'); this.container.style.fontFamily = 'monospace'; this.container.style.lineHeight = /Trident/i.test(navigator.userAgent) ? '18px' : 'initial'; - this.container.style.position = 'relative'; this.initialize(HTMLElement, '
 
'); this.div = this.container.firstChild; this.div.style.border = '1px solid #777'; @@ -367,12 +366,13 @@ describe('Selection', function() { this.initialize(HTMLElement, '

0

', this.div); let span = this.div.firstChild.firstChild; span.style.display = 'inline-block'; // IE11 needs this to respect line height + let bounds = span.getBoundingClientRect(); this.reference = { - height: span.offsetHeight, - left: span.offsetLeft, + height: bounds.height, + left: bounds.left, lineHeight: span.parentNode.offsetHeight, - width: span.offsetWidth, - top: span.offsetTop, + width: bounds.width, + top: bounds.top }; this.initialize(HTMLElement, '', this.div); }); diff --git a/themes/base.js b/themes/base.js index 64d4fa4a74..e21fa394d8 100644 --- a/themes/base.js +++ b/themes/base.js @@ -192,9 +192,9 @@ class BaseTooltip extends Tooltip { } restoreFocus() { - let scrollTop = this.quill.root.scrollTop; + let scrollTop = this.quill.scrollingContainer.scrollTop; this.quill.focus(); - this.quill.root.scrollTop = scrollTop; + this.quill.scrollingContainer.scrollTop = scrollTop; } save() { diff --git a/ui/tooltip.js b/ui/tooltip.js index f5e1c925f4..3a72736186 100644 --- a/ui/tooltip.js +++ b/ui/tooltip.js @@ -4,9 +4,11 @@ class Tooltip { this.boundsContainer = boundsContainer || document.body; this.root = quill.addContainer('ql-tooltip'); this.root.innerHTML = this.constructor.TEMPLATE; - this.quill.root.addEventListener('scroll', () => { - this.root.style.marginTop = (-1*this.quill.root.scrollTop) + 'px'; - }); + if (this.quill.root === this.quill.scrollingContainer) { + this.quill.root.addEventListener('scroll', () => { + this.root.style.marginTop = (-1*this.quill.root.scrollTop) + 'px'; + }); + } this.hide(); } @@ -16,6 +18,7 @@ class Tooltip { position(reference) { let left = reference.left + reference.width/2 - this.root.offsetWidth/2; + // root.scrollTop should be 0 if scrollContainer !== root let top = reference.bottom + this.quill.root.scrollTop; this.root.style.left = left + 'px'; this.root.style.top = top + 'px'; @@ -33,8 +36,8 @@ class Tooltip { } if (rootBounds.bottom > containerBounds.bottom) { let height = rootBounds.bottom - rootBounds.top; - let verticalShift = containerBounds.bottom - rootBounds.bottom - height; - this.root.style.top = (top + verticalShift) + 'px'; + let verticalShift = reference.bottom - reference.top + height; + this.root.style.top = (top - verticalShift) + 'px'; this.root.classList.add('ql-flip'); } return shift;