mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-10 10:17:11 +03:00
Handle scrolling of the dummy scrollbars directly
This commit is contained in:
parent
8720dbc862
commit
0999d0bf02
@ -38,8 +38,7 @@ describe('TextEditorComponent', () => {
|
||||
expect(element.querySelectorAll('.line-number').length).toBe(9)
|
||||
expect(element.querySelectorAll('.line').length).toBe(9)
|
||||
|
||||
component.setScrollTop(5 * component.getLineHeight())
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, 5 * component.getLineHeight())
|
||||
|
||||
// After scrolling down beyond > 3 rows, the order of line numbers and lines
|
||||
// in the DOM is a bit weird because the first tile is recycled to the bottom
|
||||
@ -59,8 +58,7 @@ describe('TextEditorComponent', () => {
|
||||
editor.lineTextForScreenRow(8)
|
||||
])
|
||||
|
||||
component.setScrollTop(2.5 * component.getLineHeight())
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, 2.5 * component.getLineHeight())
|
||||
expect(Array.from(element.querySelectorAll('.line-number')).map(element => element.textContent.trim())).toEqual([
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9'
|
||||
])
|
||||
@ -95,8 +93,7 @@ describe('TextEditorComponent', () => {
|
||||
await setEditorHeightInLines(component, 6)
|
||||
|
||||
// scroll to end
|
||||
component.setScrollTop(scrollContainer.scrollHeight - scrollContainer.clientHeight)
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, scrollContainer.scrollHeight - scrollContainer.clientHeight)
|
||||
expect(component.getFirstVisibleRow()).toBe(editor.getScreenLineCount() - 3)
|
||||
|
||||
editor.update({scrollPastEnd: false})
|
||||
@ -106,8 +103,7 @@ describe('TextEditorComponent', () => {
|
||||
// Always allows at least 3 lines worth of overscroll if the editor is short
|
||||
await setEditorHeightInLines(component, 2)
|
||||
await editor.update({scrollPastEnd: true})
|
||||
component.setScrollTop(scrollContainer.scrollHeight - scrollContainer.clientHeight)
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, scrollContainer.scrollHeight - scrollContainer.clientHeight)
|
||||
expect(component.getFirstVisibleRow()).toBe(editor.getScreenLineCount() + 1)
|
||||
})
|
||||
|
||||
@ -125,10 +121,73 @@ describe('TextEditorComponent', () => {
|
||||
expect(gutterElement.firstChild.style.contain).toBe('strict')
|
||||
})
|
||||
|
||||
it('renders dummy vertical and horizontal scrollbars when content overflows', async () => {
|
||||
const {component, element, editor} = buildComponent({height: 100, width: 100})
|
||||
const verticalScrollbar = component.refs.verticalScrollbar.element
|
||||
const horizontalScrollbar = component.refs.horizontalScrollbar.element
|
||||
expect(verticalScrollbar.scrollHeight).toBe(component.getContentHeight())
|
||||
expect(horizontalScrollbar.scrollWidth).toBe(component.getContentWidth())
|
||||
expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0)
|
||||
expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0)
|
||||
expect(verticalScrollbar.style.bottom).toBe(getVerticalScrollbarWidth(component) + 'px')
|
||||
expect(horizontalScrollbar.style.right).toBe(getHorizontalScrollbarHeight(component) + 'px')
|
||||
expect(component.refs.scrollbarCorner).toBeDefined()
|
||||
|
||||
setScrollTop(component, 100)
|
||||
await setScrollLeft(component, 100)
|
||||
expect(verticalScrollbar.scrollTop).toBe(100)
|
||||
expect(horizontalScrollbar.scrollLeft).toBe(100)
|
||||
|
||||
verticalScrollbar.scrollTop = 120
|
||||
horizontalScrollbar.scrollLeft = 120
|
||||
await component.getNextUpdatePromise()
|
||||
expect(component.getScrollTop()).toBe(120)
|
||||
expect(component.getScrollLeft()).toBe(120)
|
||||
|
||||
editor.setText('a\n'.repeat(15))
|
||||
await component.getNextUpdatePromise()
|
||||
expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(0)
|
||||
expect(getHorizontalScrollbarHeight(component)).toBe(0)
|
||||
expect(verticalScrollbar.style.bottom).toBe('0px')
|
||||
expect(component.refs.scrollbarCorner).toBeUndefined()
|
||||
|
||||
editor.setText('a'.repeat(100))
|
||||
await component.getNextUpdatePromise()
|
||||
expect(getVerticalScrollbarWidth(component)).toBe(0)
|
||||
expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(0)
|
||||
expect(horizontalScrollbar.style.right).toBe('0px')
|
||||
expect(component.refs.scrollbarCorner).toBeUndefined()
|
||||
|
||||
editor.setText('')
|
||||
await component.getNextUpdatePromise()
|
||||
expect(getVerticalScrollbarWidth(component)).toBe(0)
|
||||
expect(getHorizontalScrollbarHeight(component)).toBe(0)
|
||||
expect(component.refs.scrollbarCorner).toBeUndefined()
|
||||
})
|
||||
|
||||
it('updates the bottom/right of dummy scrollbars and client height/width measurements when scrollbar styles change', async () => {
|
||||
const {component, element, editor} = buildComponent({height: 100, width: 100})
|
||||
expect(getHorizontalScrollbarHeight(component)).toBeGreaterThan(10)
|
||||
expect(getVerticalScrollbarWidth(component)).toBeGreaterThan(10)
|
||||
|
||||
const style = document.createElement('style')
|
||||
style.textContent = '::-webkit-scrollbar { height: 10px; width: 10px; }'
|
||||
jasmine.attachToDOM(style)
|
||||
|
||||
TextEditor.didUpdateScrollbarStyles()
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
expect(getHorizontalScrollbarHeight(component)).toBe(10)
|
||||
expect(getVerticalScrollbarWidth(component)).toBe(10)
|
||||
expect(component.refs.horizontalScrollbar.element.style.right).toBe('10px')
|
||||
expect(component.refs.verticalScrollbar.element.style.bottom).toBe('10px')
|
||||
expect(component.getScrollContainerClientHeight()).toBe(100 - 10)
|
||||
expect(component.getScrollContainerClientWidth()).toBe(100 - component.getGutterContainerWidth() - 10)
|
||||
})
|
||||
|
||||
it('renders cursors within the visible row range', async () => {
|
||||
const {component, element, editor} = buildComponent({height: 40, rowsPerTile: 2})
|
||||
component.setScrollTop(100)
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, 100)
|
||||
|
||||
expect(component.getRenderedStartRow()).toBe(4)
|
||||
expect(component.getRenderedEndRow()).toBe(10)
|
||||
@ -171,9 +230,8 @@ describe('TextEditorComponent', () => {
|
||||
it('places the hidden input element at the location of the last cursor if it is visible', async () => {
|
||||
const {component, element, editor} = buildComponent({height: 60, width: 120, rowsPerTile: 2})
|
||||
const {hiddenInput} = component.refs
|
||||
component.setScrollTop(100)
|
||||
component.setScrollLeft(40)
|
||||
await component.getNextUpdatePromise()
|
||||
setScrollTop(component, 100)
|
||||
await setScrollLeft(component, 40)
|
||||
|
||||
expect(component.getRenderedStartRow()).toBe(4)
|
||||
expect(component.getRenderedEndRow()).toBe(12)
|
||||
@ -718,8 +776,7 @@ describe('TextEditorComponent', () => {
|
||||
)
|
||||
|
||||
// Don't flash on next update if another flash wasn't requested
|
||||
component.setScrollTop(100)
|
||||
await component.getNextUpdatePromise()
|
||||
await setScrollTop(component, 100)
|
||||
expect(highlights[0].classList.contains('b')).toBe(false)
|
||||
expect(highlights[1].classList.contains('b')).toBe(false)
|
||||
|
||||
@ -1184,9 +1241,8 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
const maxScrollTop = component.getMaxScrollTop()
|
||||
const maxScrollLeft = component.getMaxScrollLeft()
|
||||
component.setScrollTop(maxScrollTop)
|
||||
component.setScrollLeft(maxScrollLeft)
|
||||
await component.getNextUpdatePromise()
|
||||
setScrollTop(component, maxScrollTop)
|
||||
await setScrollLeft(component, maxScrollLeft)
|
||||
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
@ -1413,9 +1469,8 @@ describe('TextEditorComponent', () => {
|
||||
|
||||
const maxScrollTop = component.getMaxScrollTop()
|
||||
const maxScrollLeft = component.getMaxScrollLeft()
|
||||
component.setScrollTop(maxScrollTop)
|
||||
component.setScrollLeft(maxScrollLeft)
|
||||
await component.getNextUpdatePromise()
|
||||
setScrollTop(component, maxScrollTop)
|
||||
await setScrollLeft(component, maxScrollLeft)
|
||||
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
didDrag({clientX: 199, clientY: 199})
|
||||
@ -1518,6 +1573,28 @@ function textNodesForScreenRow (component, row) {
|
||||
return component.textNodesByScreenLineId.get(screenLine.id)
|
||||
}
|
||||
|
||||
function setScrollTop (component, scrollTop) {
|
||||
component.setScrollTop(scrollTop)
|
||||
component.scheduleUpdate()
|
||||
return component.getNextUpdatePromise()
|
||||
}
|
||||
|
||||
function setScrollLeft (component, scrollTop) {
|
||||
component.setScrollLeft(scrollTop)
|
||||
component.scheduleUpdate()
|
||||
return component.getNextUpdatePromise()
|
||||
}
|
||||
|
||||
function getHorizontalScrollbarHeight (component) {
|
||||
const element = component.refs.horizontalScrollbar.element
|
||||
return element.offsetHeight - element.clientHeight
|
||||
}
|
||||
|
||||
function getVerticalScrollbarWidth (component) {
|
||||
const element = component.refs.verticalScrollbar.element
|
||||
return element.offsetWidth - element.clientWidth
|
||||
}
|
||||
|
||||
function assertDocumentFocused () {
|
||||
if (!document.hasFocus()) {
|
||||
throw new Error('The document needs to be focused to run this test')
|
||||
|
@ -55,9 +55,12 @@ class TextEditorComponent {
|
||||
this.horizontalPixelPositionsByScreenLineId = new Map() // Values are maps from column to horiontal pixel positions
|
||||
this.lineNodesByScreenLineId = new Map()
|
||||
this.textNodesByScreenLineId = new Map()
|
||||
this.didScrollDummyScrollbar = this.didScrollDummyScrollbar.bind(this)
|
||||
this.scrollbarsVisible = true
|
||||
this.refreshScrollbarStyling = false
|
||||
this.pendingAutoscroll = null
|
||||
this.scrollTopPending = false
|
||||
this.scrollLeftPending = false
|
||||
this.scrollTop = 0
|
||||
this.scrollLeft = 0
|
||||
this.previousScrollWidth = 0
|
||||
@ -134,7 +137,8 @@ class TextEditorComponent {
|
||||
etch.updateSync(this)
|
||||
|
||||
this.currentFrameLineNumberGutterProps = null
|
||||
|
||||
this.scrollTopPending = false
|
||||
this.scrollLeftPending = false
|
||||
if (this.refreshScrollbarStyling) {
|
||||
this.measureScrollbarDimensions()
|
||||
this.refreshScrollbarStyling = false
|
||||
@ -529,11 +533,13 @@ class TextEditorComponent {
|
||||
$(DummyScrollbarComponent, {
|
||||
ref: 'verticalScrollbar',
|
||||
orientation: 'vertical',
|
||||
didScroll: this.didScrollDummyScrollbar,
|
||||
scrollHeight, scrollTop, horizontalScrollbarHeight, forceScrollbarVisible
|
||||
}),
|
||||
$(DummyScrollbarComponent, {
|
||||
ref: 'horizontalScrollbar',
|
||||
orientation: 'horizontal',
|
||||
didScroll: this.didScrollDummyScrollbar,
|
||||
scrollWidth, scrollLeft, verticalScrollbarWidth, forceScrollbarVisible
|
||||
})
|
||||
]
|
||||
@ -543,6 +549,7 @@ class TextEditorComponent {
|
||||
if (verticalScrollbarWidth > 0 && horizontalScrollbarHeight > 0) {
|
||||
elements.push($.div(
|
||||
{
|
||||
ref: 'scrollbarCorner',
|
||||
style: {
|
||||
position: 'absolute',
|
||||
height: '20px',
|
||||
@ -869,6 +876,18 @@ class TextEditorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
didScrollDummyScrollbar () {
|
||||
let scrollTopChanged = false
|
||||
let scrollLeftChanged = false
|
||||
if (!this.scrollTopPending) {
|
||||
scrollTopChanged = this.setScrollTop(this.refs.verticalScrollbar.element.scrollTop)
|
||||
}
|
||||
if (!this.scrollLeftPending) {
|
||||
scrollLeftChanged = this.setScrollLeft(this.refs.horizontalScrollbar.element.scrollLeft)
|
||||
}
|
||||
if (scrollTopChanged || scrollLeftChanged) this.updateSync()
|
||||
}
|
||||
|
||||
didUpdateScrollbarStyles () {
|
||||
this.refreshScrollbarStyling = true
|
||||
this.scheduleUpdate()
|
||||
@ -1197,17 +1216,17 @@ class TextEditorComponent {
|
||||
|
||||
if (!options || options.reversed !== false) {
|
||||
if (desiredScrollBottom > this.getScrollBottom()) {
|
||||
return this.setScrollBottom(desiredScrollBottom, true)
|
||||
this.setScrollBottom(desiredScrollBottom)
|
||||
}
|
||||
if (desiredScrollTop < this.getScrollTop()) {
|
||||
return this.setScrollTop(desiredScrollTop, true)
|
||||
this.setScrollTop(desiredScrollTop)
|
||||
}
|
||||
} else {
|
||||
if (desiredScrollTop < this.getScrollTop()) {
|
||||
return this.setScrollTop(desiredScrollTop, true)
|
||||
this.setScrollTop(desiredScrollTop)
|
||||
}
|
||||
if (desiredScrollBottom > this.getScrollBottom()) {
|
||||
return this.setScrollBottom(desiredScrollBottom, true)
|
||||
this.setScrollBottom(desiredScrollBottom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1226,17 +1245,17 @@ class TextEditorComponent {
|
||||
|
||||
if (!options || options.reversed !== false) {
|
||||
if (desiredScrollRight > this.getScrollRight()) {
|
||||
this.setScrollRight(desiredScrollRight, true)
|
||||
this.setScrollRight(desiredScrollRight)
|
||||
}
|
||||
if (desiredScrollLeft < this.getScrollLeft()) {
|
||||
this.setScrollLeft(desiredScrollLeft, true)
|
||||
this.setScrollLeft(desiredScrollLeft)
|
||||
}
|
||||
} else {
|
||||
if (desiredScrollLeft < this.getScrollLeft()) {
|
||||
this.setScrollLeft(desiredScrollLeft, true)
|
||||
this.setScrollLeft(desiredScrollLeft)
|
||||
}
|
||||
if (desiredScrollRight > this.getScrollRight()) {
|
||||
this.setScrollRight(desiredScrollRight, true)
|
||||
this.setScrollRight(desiredScrollRight)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1691,11 +1710,11 @@ class TextEditorComponent {
|
||||
return this.scrollTop
|
||||
}
|
||||
|
||||
setScrollTop (scrollTop, suppressUpdate = false) {
|
||||
setScrollTop (scrollTop) {
|
||||
scrollTop = Math.round(Math.max(0, Math.min(this.getMaxScrollTop(), scrollTop)))
|
||||
if (scrollTop !== this.scrollTop) {
|
||||
this.scrollTopPending = true
|
||||
this.scrollTop = scrollTop
|
||||
if (!suppressUpdate) this.scheduleUpdate()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -1710,8 +1729,8 @@ class TextEditorComponent {
|
||||
return this.getScrollTop() + this.getScrollContainerClientHeight()
|
||||
}
|
||||
|
||||
setScrollBottom (scrollBottom, suppressUpdate = false) {
|
||||
return this.setScrollTop(scrollBottom - this.getScrollContainerClientHeight(), suppressUpdate)
|
||||
setScrollBottom (scrollBottom) {
|
||||
return this.setScrollTop(scrollBottom - this.getScrollContainerClientHeight())
|
||||
}
|
||||
|
||||
getScrollLeft () {
|
||||
@ -1719,11 +1738,11 @@ class TextEditorComponent {
|
||||
return this.scrollLeft
|
||||
}
|
||||
|
||||
setScrollLeft (scrollLeft, suppressUpdate = false) {
|
||||
setScrollLeft (scrollLeft) {
|
||||
scrollLeft = Math.round(Math.max(0, Math.min(this.getMaxScrollLeft(), scrollLeft)))
|
||||
if (scrollLeft !== this.scrollLeft) {
|
||||
this.scrollLeftPending = true
|
||||
this.scrollLeft = scrollLeft
|
||||
if (!suppressUpdate) this.scheduleUpdate()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -1738,8 +1757,8 @@ class TextEditorComponent {
|
||||
return this.getScrollLeft() + this.getScrollContainerClientWidth()
|
||||
}
|
||||
|
||||
setScrollRight (scrollRight, suppressUpdate = false) {
|
||||
return this.setScrollLeft(scrollRight - this.getScrollContainerClientWidth(), suppressUpdate)
|
||||
setScrollRight (scrollRight) {
|
||||
return this.setScrollLeft(scrollRight - this.getScrollContainerClientWidth())
|
||||
}
|
||||
|
||||
// Ensure the spatial index is populated with rows that are currently
|
||||
@ -1807,7 +1826,13 @@ class DummyScrollbarComponent {
|
||||
innerStyle.height = (this.props.scrollHeight || 0) + 'px'
|
||||
}
|
||||
|
||||
return $.div({style: outerStyle, scrollTop, scrollLeft},
|
||||
return $.div(
|
||||
{
|
||||
style: outerStyle,
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
on: {scroll: this.props.didScroll}
|
||||
},
|
||||
$.div({style: innerStyle})
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user