diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index e1d2e0030..de20d16e9 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -876,17 +876,22 @@ describe('TextEditorComponent', () => { }) describe('overlay decorations', () => { - it('renders overlay elements at the specified screen position unless it would overflow the window', async () => { - const {component, element, editor} = buildComponent({width: 200, height: 100, attach: false}) + function attachFakeWindow (component) { const fakeWindow = document.createElement('div') fakeWindow.style.position = 'absolute' fakeWindow.style.padding = 20 + 'px' fakeWindow.style.backgroundColor = 'blue' - fakeWindow.appendChild(element) + fakeWindow.appendChild(component.element) jasmine.attachToDOM(fakeWindow) - spyOn(component, 'getWindowInnerWidth').andCallFake(() => fakeWindow.getBoundingClientRect().width) spyOn(component, 'getWindowInnerHeight').andCallFake(() => fakeWindow.getBoundingClientRect().height) + return fakeWindow + } + + it('renders overlay elements at the specified screen position unless it would overflow the window', async () => { + const {component, element, editor} = buildComponent({width: 200, height: 100, attach: false}) + const fakeWindow = attachFakeWindow(component) + await setScrollTop(component, 50) await setScrollLeft(component, 100) @@ -949,6 +954,24 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise() expect(overlayWrapper.classList.contains('b')).toBe(false) }) + + it('does not attempt to avoid overflowing the window if `avoidOverflow` is false on the decoration', async () => { + const {component, element, editor} = buildComponent({width: 200, height: 100, attach: false}) + const fakeWindow = attachFakeWindow(component) + const overlayElement = document.createElement('div') + overlayElement.style.width = '50px' + overlayElement.style.height = '50px' + overlayElement.style.margin = '3px' + overlayElement.style.backgroundColor = 'red' + const marker = editor.markScreenPosition([4, 25]) + const decoration = editor.decorateMarker(marker, {type: 'overlay', item: overlayElement, avoidOverflow: false}) + await component.getNextUpdatePromise() + + await setScrollLeft(component, 30) + expect(overlayElement.getBoundingClientRect().right).toBeGreaterThan(fakeWindow.getBoundingClientRect().right) + await setScrollLeft(component, 280) + expect(overlayElement.getBoundingClientRect().left).toBeLessThan(fakeWindow.getBoundingClientRect().left) + }) }) describe('mouse input', () => { diff --git a/src/text-editor-component.js b/src/text-editor-component.js index ccaffc0bc..770af9afb 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -576,7 +576,10 @@ class TextEditorComponent { renderOverlayDecorations () { return this.decorationsToRender.overlays.map((overlayProps) => - $(OverlayComponent, Object.assign({didResize: this.updateSync}, overlayProps)) + $(OverlayComponent, Object.assign( + {key: overlayProps.element, didResize: this.updateSync}, + overlayProps + )) ) } @@ -728,17 +731,14 @@ class TextEditorComponent { } addOverlayDecorationToRender (decoration, marker) { - const {class: className, item, position} = decoration + const {class: className, item, position, avoidOverflow} = decoration const element = atom.views.getView(item) const screenPosition = (position === 'tail') ? marker.getTailScreenPosition() : marker.getHeadScreenPosition() this.requestHorizontalMeasurement(screenPosition.row, screenPosition.column) - this.decorationsToRender.overlays.push({ - key: element, - className, element, screenPosition - }) + this.decorationsToRender.overlays.push({className, element, avoidOverflow, screenPosition}) } updateAbsolutePositionedDecorations () { @@ -792,27 +792,28 @@ class TextEditorComponent { const contentClientRect = this.refs.content.getBoundingClientRect() for (let i = 0; i < overlayCount; i++) { const decoration = this.decorationsToRender.overlays[i] - const {element, screenPosition} = decoration + const {element, screenPosition, avoidOverflow} = decoration const {row, column} = screenPosition - const computedStyle = window.getComputedStyle(element) - let wrapperTop = contentClientRect.top + this.pixelTopForRow(row) + this.getLineHeight() - const elementHeight = element.offsetHeight - const elementTop = wrapperTop + parseInt(computedStyle.marginTop) - const elementBottom = elementTop + elementHeight - const flippedElementTop = wrapperTop - this.getLineHeight() - elementHeight - parseInt(computedStyle.marginBottom) - - if (elementBottom > windowInnerHeight && flippedElementTop >= 0) { - wrapperTop -= (elementTop - flippedElementTop) - } - let wrapperLeft = contentClientRect.left + this.pixelLeftForRowAndColumn(row, column) - const elementLeft = wrapperLeft + parseInt(computedStyle.marginLeft) - const elementRight = elementLeft + element.offsetWidth - if (elementLeft < 0) { - wrapperLeft -= elementLeft - } else if (elementRight > windowInnerWidth) { - wrapperLeft -= (elementRight - windowInnerWidth) + + if (avoidOverflow !== false) { + const computedStyle = window.getComputedStyle(element) + const elementHeight = element.offsetHeight + const elementTop = wrapperTop + parseInt(computedStyle.marginTop) + const elementBottom = elementTop + elementHeight + const flippedElementTop = wrapperTop - this.getLineHeight() - elementHeight - parseInt(computedStyle.marginBottom) + const elementLeft = wrapperLeft + parseInt(computedStyle.marginLeft) + const elementRight = elementLeft + element.offsetWidth + + if (elementBottom > windowInnerHeight && flippedElementTop >= 0) { + wrapperTop -= (elementTop - flippedElementTop) + } + if (elementLeft < 0) { + wrapperLeft -= elementLeft + } else if (elementRight > windowInnerWidth) { + wrapperLeft -= (elementRight - windowInnerWidth) + } } decoration.pixelTop = wrapperTop