Improve the logic for repositioning grammar mistake highlights in response to DOM changes

Refactor the click detection logic to check if the click was inside the rectangle of the highlight element, rather than relying on stored rectangle coordinates.
Additionally, this update eliminates the need to attach event listeners to the entire window or the parent elements of the host element, simplifying the event handling and potentially improving performance.
This commit is contained in:
Aljaž Grilc 2024-05-24 16:48:02 +02:00
parent 94847d44f2
commit 159cd51684
2 changed files with 54 additions and 79 deletions

View File

@ -8,10 +8,6 @@
*/
let besServices = []
window.addEventListener('scroll', () =>
besServices.forEach(service => service.onScroll())
)
// TODO: Window resize may cause host element(s) to move. That needs correction panel and status icon
// repositioning. Also, should any parent element of our service host element move, we should reposition
// correction panel and status icon. How to do this? Alas there is no PlacementObserver to monitor host
@ -153,15 +149,7 @@ class BesService {
this.scrollPanel.style.top = `${-this.hostElement.scrollTop}px`
// Why do we need to set left to -scrollLeft? It destroys the position of highlights
// this.scrollPanel.style.left = `${-this.hostElement.scrollLeft}px`
// Markup is in a "position:absolute" <div> element requiring repositioning when scrolling host element or window.
// It is defered to reduce stress in a flood of scroll events.
if (this.scrollTimeout) clearTimeout(this.scrollTimeout)
this.scrollTimeout = setTimeout(() => {
this.updateRects()
delete this.scrollTimeout
}, 500)
this.scrollPanel.style.left = `${-this.hostElement.scrollLeft}px`
}
/**
@ -207,15 +195,6 @@ class BesService {
return { clientRects, highlights }
}
updateRects() {
this.results.forEach(result => {
result.matches.forEach(match => {
const clientRects = match.range.getClientRects()
match.rects = clientRects
})
})
}
/**
* Tests if given coordinate is inside of a rectangle.
*
@ -711,20 +690,24 @@ class BesTreeService extends BesService {
const el = this.getBlockParent(source.targetElement || source.target)
if (!el) return
const result = this.results.find(child =>
BesTreeService.isSameParagraph(child.element, el)
)
if (result) {
for (let m of result.matches) {
for (let r of m.rects) {
if (BesService.isPointInRect(source.clientX, source.clientY, r)) {
this.popupCorrectionPanel(el, m, source)
return
}
}
let resultMatch
for (let result of this.results) {
let match = result.matches.find(match => {
// This way we can check if the click was inside the rectangle of the highlight even if some content was removed or added.
// We used to check if the click was inside the stored rectangle coordinates which is not reliable because the content can change.
// But this way we can check if the click was inside the rectangle of the highlight even if some content was changed.
// TODO: what to do with rectLists inside match.rects?
let rect = match.highlights[0].getBoundingClientRect()
return BesService.isPointInRect(source.clientX, source.clientY, rect)
})
if (match) {
resultMatch = match
break
}
}
BesPopup.hide()
if (resultMatch) this.popupCorrectionPanel(el, resultMatch, source)
else BesPopup.hide()
}
}

View File

@ -4,64 +4,32 @@
.bes-typo-mistake {
border-bottom: 2px solid red;
position: absolute;
z-index: 2;
z-index: 3;
cursor: text;
pointer-events: none;
/* pointer-events: none; */
}
.bes-correction-panel-parent {
z-index: 3;
top: 0;
left: 0;
width: 0;
height: 0;
display: inline;
float: left;
position: relative;
overflow: visible;
visibility: visible;
float: left;
display: inline;
width: 0px;
height: 0px;
border: none;
z-index: 1;
}
.bes-correction-panel {
background: transparent;
border: none;
color: transparent;
cursor: initial;
display: block;
float: initial;
margin: 0;
max-height: initial;
min-height: initial;
max-width: initial;
min-width: initial;
outline: initial;
overflow: hidden;
padding: 0;
pointer-events: none;
position: relative;
overflow: hidden;
border-color: transparent;
color: transparent;
pointer-events: none;
}
.bes-correction-panel-scroll {
pointer-events: none;
box-shadow: initial;
box-sizing: initial;
cursor: initial;
display: block;
float: initial;
max-height: initial;
min-height: initial;
max-width: initial;
min-width: initial;
position: absolute;
height: initial;
width: initial;
animation: auto ease 0s 1 normal none running none;
background: transparent;
border-width: initial;
border-style: none;
margin: 0px;
outline: initial;
padding: 0px;
position: relative;
}
.bes-status-div {
@ -95,3 +63,27 @@
.bes-status-icon.bes-status-mistakes {
background-image: url('images/mistake-svgrepo-com.svg');
}
/* TODO: Styles below should be removed after testing period is over */
.resizable {
overflow-x: scroll;
position: relative;
display: inline-block;
border: 1px solid #ccc;
width: 300px;
height: 400px;
border-radius: 3px;
background-color: #f9f9f9;
}
.mock-content {
width: 1000px;
height: 600px;
background-color: #f9f9f9;
}
.flex {
display: flex;
justify-content: center;
align-items: center;
}