Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix excessive reviveSelector execution in spacialTree after splitting the annotatable ranges #65

Open
oleksandr-danylchenko opened this issue Feb 27, 2024 · 2 comments

Comments

@oleksandr-danylchenko
Copy link
Contributor

oleksandr-danylchenko commented Feb 27, 2024

Current State

In the #34 PR, I added the splitAnnotatableRanges method that "Splits a DOM Range into one or more ranges that span annotatable content only". It works properly and the ranges it captures skip the .not-annotatable elements.

Issue

The problem is that the ranges are generated with the .setStartAfter & .setEndBefore that don't precisely capture the text nodes:

// From the start of the range to the not annotatable element.
// If selection starts on the non-annotatable element, a collapsed range will be created and ignored.
if (!prevNotAnnotatable) {
subRange = range.cloneRange();
subRange.setEndBefore(notAnnotatable);
} else {
// From the previous not annotatable element to the current not annotatable element
subRange = document.createRange();
subRange.setStartAfter(prevNotAnnotatable);
subRange.setEndBefore(notAnnotatable);
}

Instead, they can capture the preceding/following divs as the start/end containers:
image

However, the spacialTree doesn't consider such ranges as "valid"! When it generates the client rects, it'll try to revive the range again from its positions:

const isValidRange =
s.range instanceof Range &&
!s.range.collapsed &&
s.range.startContainer.nodeType === Node.TEXT_NODE &&
s.range.endContainer.nodeType === Node.TEXT_NODE;
const revivedRange = isValidRange ? s.range : reviveSelector(s, container).range;

It feels like an excessive computational step, considering that the provided range already covers only the annotatable area and it certainly doesn't improve the performance 🤷🏻‍♂️

Issue Demo

image

Screen.Recording.2024-02-27.at.10.45.42.mov

Possible Solutions

Don't run the trimRange right on the selection range:

const selectionRange = sel.getRangeAt(0);
const trimmedRange = trimRange(selectionRange.cloneRange())
const annotatableRanges = splitAnnotatableRanges(trimmedRange);

But on the ranges created after splitting

@oleksandr-danylchenko
Copy link
Contributor Author

Unfortunately, just running the trimRange on the split ranges doesn't work properly out of the box and creates collapsed ranges:
image

@oleksandr-danylchenko
Copy link
Contributor Author

oleksandr-danylchenko commented Feb 27, 2024

Also, notice how the startContainer wasn't trimmed 👁️
image

Which brings us back to the #23 issue. Where I pointed out that trimRange uses setEnd instead of setStart:

if (startContainer.nodeType !== Node.TEXT_NODE) {
const nextContainer = startContainer.nextSibling || startContainer.parentNode;
// Get first text child in next container
const nextText = nextContainer?.nodeType === Node.TEXT_NODE ?
nextContainer :
Array.from(nextContainer!.childNodes).filter(n => n.nodeType === Node.TEXT_NODE).shift();
range.setEnd(nextText!, 0);
}
if (endContainer.nodeType !== Node.TEXT_NODE) {
const prevContainer = endContainer.previousSibling || endContainer.parentNode;
// Get last text child in previous container
const prevText = prevContainer?.nodeType === Node.TEXT_NODE ?
prevContainer :
Array.from(prevContainer!.childNodes).filter(n => n.nodeType === Node.TEXT_NODE).pop();
range.setEnd(prevText!, prevText?.textContent?.length || 0);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant