Revise scrolling

This commit is contained in:
Simon Rozman 2025-02-28 11:32:21 +01:00
parent d246d07d7f
commit 51099347f2

View File

@ -73,6 +73,9 @@ class BesService {
}) })
besServices.push(this) besServices.push(this)
// Initial sync the scroll as hostElement may be scrolled by non-(0, 0) at the time of BesService registration.
this.onScroll()
} }
/** /**
@ -360,11 +363,13 @@ class BesService {
*/ */
drawMistakeMarkup(match) { drawMistakeMarkup(match) {
const range = match.range const range = match.range
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
const scrollX = canvasPanelRect.left
const scrollY = canvasPanelRect.top
match.highlights = Array.from(range.getClientRects()) match.highlights = Array.from(range.getClientRects())
if (match.highlights.length === 0) return if (match.highlights.length === 0) return
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
for (let rect of match.highlights) {
rect.x -= canvasPanelRect.x
rect.y -= canvasPanelRect.y
}
const dpr = window.devicePixelRatio const dpr = window.devicePixelRatio
this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility this.ctx.lineWidth = 2 * dpr // Use 2 for clearer visibility
const ruleId = match.match.rule.id const ruleId = match.match.rule.id
@ -386,7 +391,7 @@ class BesService {
const scale = (markerY2 - markerY1) / 18 const scale = (markerY2 - markerY1) / 18
const x = match.highlights[0].left const x = match.highlights[0].left
const y = match.highlights[0].bottom const y = match.highlights[0].bottom
this.drawMissingComma(x - scrollX, y - scrollY, scale, '?') this.drawMissingComma(x, y, scale, '?')
break break
} }
@ -413,25 +418,20 @@ class BesService {
// in another line, making a confusing UX. // in another line, making a confusing UX.
const x = match.highlights[0].left const x = match.highlights[0].left
const y = match.highlights[0].bottom const y = match.highlights[0].bottom
this.drawMissingComma(x - scrollX, y - scrollY, scale) this.drawMissingComma(x, y, scale)
} else if (/^\s+$/.test(toInsert)) { } else if (/^\s+$/.test(toInsert)) {
const x = match.highlights[0].left const x = match.highlights[0].left
const y1 = match.highlights[0].bottom - 2 * scale const y1 = match.highlights[0].bottom - 2 * scale
const y2 = match.highlights[0].top + 2 * scale const y2 = match.highlights[0].top + 2 * scale
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
const x = match.highlights[0].left - 1 * scale const x = match.highlights[0].left - 1 * scale
const y1 = match.highlights[0].bottom const y1 = match.highlights[0].bottom
const y2 = match.highlights[0].top const y2 = match.highlights[0].top
this.drawMissingText( this.drawMissingText(
x - scrollX, x,
y1 - scrollY, y1,
y2 - scrollY, y2,
scale, scale,
replacement.substr(lengthDiff).trim() replacement.substr(lengthDiff).trim()
) )
@ -446,25 +446,20 @@ class BesService {
if (toInsert === ',') { if (toInsert === ',') {
const x = match.highlights.at(-1).right const x = match.highlights.at(-1).right
const y = match.highlights.at(-1).bottom const y = match.highlights.at(-1).bottom
this.drawMissingComma(x - scrollX, y - scrollY, scale) this.drawMissingComma(x, y, scale)
} else if (/^\s+$/.test(toInsert)) { } else if (/^\s+$/.test(toInsert)) {
const x = match.highlights.at(-1).right const x = match.highlights.at(-1).right
const y1 = match.highlights.at(-1).bottom - 2 * scale const y1 = match.highlights.at(-1).bottom - 2 * scale
const y2 = match.highlights.at(-1).top + 2 * scale const y2 = match.highlights.at(-1).top + 2 * scale
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
const x = match.highlights.at(-1).right + 1 * scale const x = match.highlights.at(-1).right + 1 * scale
const y1 = match.highlights.at(-1).bottom const y1 = match.highlights.at(-1).bottom
const y2 = match.highlights.at(-1).top const y2 = match.highlights.at(-1).top
this.drawMissingText( this.drawMissingText(
x - scrollX, x,
y1 - scrollY, y1,
y2 - scrollY, y2,
scale, scale,
replacement.substr(-lengthDiff).trim() replacement.substr(-lengthDiff).trim()
) )
@ -488,12 +483,7 @@ class BesService {
const x = (rect.left + rect.right) / 2 const x = (rect.left + rect.right) / 2
const y1 = rect.top const y1 = rect.top
const y2 = rect.bottom const y2 = rect.bottom
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
for (let rect of this.makeRange( for (let rect of this.makeRange(
match.data, match.data,
@ -501,10 +491,10 @@ class BesService {
match.match.offset - lengthDiff match.match.offset - lengthDiff
)?.getClientRects()) )?.getClientRects())
this.drawExcessiveText( this.drawExcessiveText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY rect.top
) )
} }
} else if (context.substr(0, replacement.length) === replacement) { } else if (context.substr(0, replacement.length) === replacement) {
@ -523,12 +513,7 @@ class BesService {
const x = (rect.left + rect.right) / 2 const x = (rect.left + rect.right) / 2
const y1 = rect.top const y1 = rect.top
const y2 = rect.bottom const y2 = rect.bottom
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
for (let rect of this.makeRange( for (let rect of this.makeRange(
match.data, match.data,
@ -536,10 +521,10 @@ class BesService {
match.match.offset + match.match.length match.match.offset + match.match.length
)?.getClientRects()) )?.getClientRects())
this.drawExcessiveText( this.drawExcessiveText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY rect.top
) )
} }
} else { } else {
@ -559,20 +544,20 @@ class BesService {
for (let rect of match.highlights) { for (let rect of match.highlights) {
if (first) { if (first) {
this.drawWrongText( this.drawWrongText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY, rect.top,
scale, scale,
replacement replacement
) )
first = false first = false
} else { } else {
this.drawExcessiveText( this.drawExcessiveText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY rect.top
) )
} }
} }
@ -600,22 +585,11 @@ class BesService {
if (/^\s+$/.test(toInsert)) { if (/^\s+$/.test(toInsert)) {
const y1 = rects[0].bottom - 2 * scale const y1 = rects[0].bottom - 2 * scale
const y2 = rects[0].top + 2 * scale const y2 = rects[0].top + 2 * scale
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
const y1 = rects[0].bottom const y1 = rects[0].bottom
const y2 = rects[0].top const y2 = rects[0].top
this.drawMissingText( this.drawMissingText(x, y1, y2, scale, toInsert.trim())
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale,
toInsert.trim()
)
} }
} else if (lengthL + lengthR === replacement.length) { } else if (lengthL + lengthR === replacement.length) {
// Something to remove // Something to remove
@ -629,19 +603,14 @@ class BesService {
const x = (rects[0].left + rects[0].right) / 2 const x = (rects[0].left + rects[0].right) / 2
const y1 = rects[0].top const y1 = rects[0].top
const y2 = rects[0].bottom const y2 = rects[0].bottom
this.drawWrongSpacing( this.drawWrongSpacing(x, y1, y2, scale)
x - scrollX,
y1 - scrollY,
y2 - scrollY,
scale
)
} else { } else {
for (let rect of rects) for (let rect of rects)
this.drawExcessiveText( this.drawExcessiveText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY rect.top
) )
} }
} else { } else {
@ -655,20 +624,20 @@ class BesService {
for (let rect of rects) { for (let rect of rects) {
if (first) { if (first) {
this.drawWrongText( this.drawWrongText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY, rect.top,
scale, scale,
toReplace toReplace
) )
first = false first = false
} else { } else {
this.drawExcessiveText( this.drawExcessiveText(
rect.left - scrollX, rect.left,
rect.bottom - scrollY, rect.bottom,
rect.right - scrollX, rect.right,
rect.top - scrollY rect.top
) )
} }
} }
@ -684,19 +653,14 @@ class BesService {
const x2 = rect.right const x2 = rect.right
const y = rect.bottom const y = rect.bottom
const scale = (rect.bottom - rect.top) / 18 const scale = (rect.bottom - rect.top) / 18
this.drawAttentionRequired( this.drawAttentionRequired(x1, x2, y, scale)
x1 - scrollX,
x2 - scrollX,
y - scrollY,
scale
)
} }
markerY1 = Math.min(...match.highlights.map(rect => rect.top)) markerY1 = Math.min(...match.highlights.map(rect => rect.top))
markerY2 = Math.max(...match.highlights.map(rect => rect.bottom)) markerY2 = Math.max(...match.highlights.map(rect => rect.bottom))
} }
this.drawSideMarker(markerY1 - scrollY, markerY2 - scrollY) this.drawSideMarker(markerY1, markerY2)
} }
static commonPrefixLength(s1, s2) { static commonPrefixLength(s1, s2) {
@ -872,15 +836,11 @@ class BesService {
*/ */
setCorrectionPanelSize() { setCorrectionPanelSize() {
this.disableMutationObserver() this.disableMutationObserver()
const styles = window.getComputedStyle(this.hostElement) const styles = window.getComputedStyle(this.hostElement)
const hostRect = this.hostElement.getBoundingClientRect() this.textFont = styles.fontFamily
// Sync margins one by one. Firefox is not happy when syncing all at once.
this.scrollPanel.style.marginLeft = styles.marginLeft // Resize canvas if needed.
this.scrollPanel.style.marginTop = styles.marginTop
this.scrollPanel.style.marginRight = styles.marginRight
this.scrollPanel.style.marginBottom = styles.marginBottom
this.scrollPanel.style.boxSizing = styles.boxSizing
this.scrollPanel.style.scrollBehavior = styles.scrollBehavior
this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px` this.canvasPanel.style.width = `${this.hostElement.scrollWidth}px`
this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px` this.canvasPanel.style.height = `${this.hostElement.scrollHeight}px`
const dpr = window.devicePixelRatio const dpr = window.devicePixelRatio
@ -895,6 +855,14 @@ class BesService {
this.canvasPanel.height = newCanvasHeight this.canvasPanel.height = newCanvasHeight
this.redrawAllMistakeMarkup() this.redrawAllMistakeMarkup()
} }
// Note: Firefox is not happy when syncing all margins at once.
this.scrollPanel.style.marginLeft = styles.marginLeft
this.scrollPanel.style.marginTop = styles.marginTop
this.scrollPanel.style.marginRight = styles.marginRight
this.scrollPanel.style.marginBottom = styles.marginBottom
this.scrollPanel.style.boxSizing = styles.boxSizing
this.scrollPanel.style.scrollBehavior = styles.scrollBehavior
if (this.isHostElementInline()) { if (this.isHostElementInline()) {
const totalWidth = const totalWidth =
parseFloat(styles.paddingLeft) + parseFloat(styles.paddingLeft) +
@ -905,9 +873,11 @@ class BesService {
this.scrollPanel.style.width = `${totalWidth}px` this.scrollPanel.style.width = `${totalWidth}px`
this.scrollPanel.style.height = styles.height this.scrollPanel.style.height = styles.height
} else { } else {
const hostRect = this.hostElement.getBoundingClientRect()
this.scrollPanel.style.width = `${hostRect.width}px` this.scrollPanel.style.width = `${hostRect.width}px`
this.scrollPanel.style.height = `${hostRect.height}px` this.scrollPanel.style.height = `${hostRect.height}px`
} }
this.enableMutationObserver() this.enableMutationObserver()
} }
@ -934,6 +904,7 @@ class BesService {
* @param {*} match Grammar checking rule match * @param {*} match Grammar checking rule match
*/ */
highlightMistake(match) { highlightMistake(match) {
const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
match.highlights.forEach(rect => { match.highlights.forEach(rect => {
const el = document.createElement('div') const el = document.createElement('div')
el.classList.add('bes-highlight-rect') el.classList.add('bes-highlight-rect')
@ -942,8 +913,8 @@ class BesService {
? 'bes-highlight-spelling-rect' ? 'bes-highlight-spelling-rect'
: 'bes-highlight-grammar-rect' : 'bes-highlight-grammar-rect'
) )
el.style.left = `${rect.x + window.scrollX}px` el.style.left = `${rect.x + canvasPanelRect.x + window.scrollX}px`
el.style.top = `${rect.y + window.scrollY}px` el.style.top = `${rect.y + canvasPanelRect.y + window.scrollY}px`
el.style.width = `${rect.width}px` el.style.width = `${rect.width}px`
el.style.height = `${rect.height}px` el.style.height = `${rect.height}px`
document.body.appendChild(el) document.body.appendChild(el)
@ -1357,12 +1328,14 @@ class BesTreeService extends BesService {
const source = event?.detail !== 1 ? event?.detail : event const source = event?.detail !== 1 ? event?.detail : event
const el = this.getBlockParent(source.targetElement || source.target) const el = this.getBlockParent(source.targetElement || source.target)
if (!el) return if (!el) return
// TODO: Adjust source.clientX and source.clientY for scrolling offset. const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y
const pointsInRect = [] const pointsInRect = []
for (let result of this.results) { for (let result of this.results) {
for (let m of result.matches) { for (let m of result.matches) {
for (let rect of m.highlights) { for (let rect of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) { if (BesService.isPointInRect(x, y, rect)) {
pointsInRect.push({ el, match: m }) pointsInRect.push({ el, match: m })
} }
} }
@ -2022,12 +1995,14 @@ class BesPlainTextService extends BesService {
const source = event?.detail !== 1 ? event?.detail : event const source = event?.detail !== 1 ? event?.detail : event
const el = source.targetElement || source.target || this.hostElement const el = source.targetElement || source.target || this.hostElement
if (!el) return if (!el) return
// TODO: Adjust source.clientX and source.clientY for scrolling offset. const canvasPanelRect = this.canvasPanel.getBoundingClientRect()
let x = source.clientX - canvasPanelRect.x
let y = source.clientY - canvasPanelRect.y
const pointsInRect = [] const pointsInRect = []
for (let result of this.results) { for (let result of this.results) {
for (let m of result.matches) { for (let m of result.matches) {
for (let rect of m.highlights) { for (let rect of m.highlights) {
if (BesService.isPointInRect(source.clientX, source.clientY, rect)) { if (BesService.isPointInRect(x, y, rect)) {
pointsInRect.push({ el: result.range, match: m }) pointsInRect.push({ el: result.range, match: m })
} }
} }