in src/component/selection/expandRangeToStartOfLine.js [110:208]
function expandRangeToStartOfLine(range: Range): Range {
invariant(
range.collapsed,
'expandRangeToStartOfLine: Provided range is not collapsed.',
);
range = range.cloneRange();
let containingElement = range.startContainer;
if (containingElement.nodeType !== 1) {
containingElement = containingElement.parentNode;
}
const lineHeight = getLineHeightPx((containingElement: any));
// Imagine our text looks like:
// <div><span>once upon a time, there was a <em>boy
// who lived</em> </span><q><strong>under^ the
// stairs</strong> in a small closet.</q></div>
// where the caret represents the cursor. First, we crawl up the tree until
// the range spans multiple lines (setting the start point to before
// "<strong>", then before "<div>"), then at each level we do a search to
// find the latest point which is still on a previous line. We'll find that
// the break point is inside the span, then inside the <em>, then in its text
// node child, the actual break point before "who".
let bestContainer = range.endContainer;
let bestOffset = range.endOffset;
range.setStart(range.startContainer, 0);
while (areRectsOnOneLine(getRangeClientRects(range), lineHeight)) {
bestContainer = range.startContainer;
bestOffset = range.startOffset;
invariant(
bestContainer.parentNode,
'Found unexpected detached subtree when traversing.',
);
range.setStartBefore(bestContainer);
if (
bestContainer.nodeType === 1 &&
getComputedStyle((bestContainer: any)).display !== 'inline'
) {
// The start of the line is never in a different block-level container.
break;
}
}
// In the above example, range now spans from "<div>" to "under",
// bestContainer is <div>, and bestOffset is 1 (index of <q> inside <div>)].
// Picking out which child to recurse into here is a special case since we
// don't want to check past <q> -- once we find that the final range starts
// in <span>, we can look at all of its children (and all of their children)
// to find the break point.
// At all times, (bestContainer, bestOffset) is the latest single-line start
// point that we know of.
let currentContainer = bestContainer;
let maxIndexToConsider = bestOffset - 1;
do {
const nodeValue = currentContainer.nodeValue;
let ii = maxIndexToConsider;
for (; ii >= 0; ii--) {
if (
nodeValue != null &&
ii > 0 &&
UnicodeUtils.isSurrogatePair(nodeValue, ii - 1)
) {
// We're in the middle of a surrogate pair -- skip over so we never
// return a range with an endpoint in the middle of a code point.
continue;
}
range.setStart(currentContainer, ii);
if (areRectsOnOneLine(getRangeClientRects(range), lineHeight)) {
bestContainer = currentContainer;
bestOffset = ii;
} else {
break;
}
}
if (ii === -1 || currentContainer.childNodes.length === 0) {
// If ii === -1, then (bestContainer, bestOffset), which is equal to
// (currentContainer, 0), was a single-line start point but a start
// point before currentContainer wasn't, so the line break seems to
// have occurred immediately after currentContainer's start tag
//
// If currentContainer.childNodes.length === 0, we're already at a
// terminal node (e.g., text node) and should return our current best.
break;
}
currentContainer = currentContainer.childNodes[ii];
maxIndexToConsider = getNodeLength(currentContainer);
} while (true);
range.setStart(bestContainer, bestOffset);
return range;
}