is
// selected and the user presses the right arrow, Firefox keeps the selection
// on the
. If we allow subsequent keypresses to insert characters
// natively, they will be inserted into a browser-created text node to the
// right of that
. This is obviously undesirable.
//
// With the `needsRecovery` flag, we inform the caller that it is responsible
// for manually setting the selection state on the rendered document to
// ensure proper selection state maintenance.
if (anchorIsTextNode) {
anchorPoint = {
key: nullthrows(findAncestorOffsetKey(anchorNode)),
offset: anchorOffset
};
focusPoint = getPointForNonTextNode(root, focusNode, focusOffset);
} else if (focusIsTextNode) {
focusPoint = {
key: nullthrows(findAncestorOffsetKey(focusNode)),
offset: focusOffset
};
anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
} else {
anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
focusPoint = getPointForNonTextNode(root, focusNode, focusOffset); // If the selection is collapsed on an empty block, don't force recovery.
// This way, on arrow key selection changes, the browser can move the
// cursor from a non-zero offset on one block, through empty blocks,
// to a matching non-zero offset on other text blocks.
if (anchorNode === focusNode && anchorOffset === focusOffset) {
needsRecovery = !!anchorNode.firstChild && anchorNode.firstChild.nodeName !== 'BR';
}
}
return {
selectionState: getUpdatedSelectionState(editorState, anchorPoint.key, anchorPoint.offset, focusPoint.key, focusPoint.offset),
needsRecovery: needsRecovery
};
}
/**
* Identify the first leaf descendant for the given node.
*/
function getFirstLeaf(node) {
while (node.firstChild && ( // data-blocks has no offset
isElement(node.firstChild) && node.firstChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.firstChild))) {
node = node.firstChild;
}
return node;
}
/**
* Identify the last leaf descendant for the given node.
*/
function getLastLeaf(node) {
while (node.lastChild && ( // data-blocks has no offset
isElement(node.lastChild) && node.lastChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.lastChild))) {
node = node.lastChild;
}
return node;
}
function getPointForNonTextNode(editorRoot, startNode, childOffset) {
var node = startNode;
var offsetKey = findAncestorOffsetKey(node);
!(offsetKey != null || editorRoot && (editorRoot === node || editorRoot.firstChild === node)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Unknown node in selection range.') : invariant(false) : void 0; // If the editorRoot is the selection, step downward into the content
// wrapper.
if (editorRoot === node) {
node = node.firstChild;
!isElement(node) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents node.') : invariant(false) : void 0;
var castedNode = node; // assignment only added for flow :/
// otherwise it throws in line 200 saying that node can be null or undefined
node = castedNode;
!(node.getAttribute('data-contents') === 'true') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Invalid DraftEditorContents structure.') : invariant(false) : void 0;
if (childOffset > 0) {
childOffset = node.childNodes.length;
}
} // If the child offset is zero and we have an offset key, we're done.
// If there's no offset key because the entire editor is selected,
// find the leftmost ("first") leaf in the tree and use that as the offset
// key.
if (childOffset === 0) {
var key = null;
if (offsetKey != null) {
key = offsetKey;
} else {
var firstLeaf = getFirstLeaf(node);
key = nullthrows(getSelectionOffsetKeyForNode(firstLeaf));
}
return {
key: key,
offset: 0
};
}
var nodeBeforeCursor = node.childNodes[childOffset - 1];
var leafKey = null;
var textLength = null;
if (!getSelectionOffsetKeyForNode(nodeBeforeCursor)) {
// Our target node may be a leaf or a text node, in which case we're
// already where we want to be and can just use the child's length as
// our offset.
leafKey = nullthrows(offsetKey);
textLength = getTextContentLength(nodeBeforeCursor);
} else {
// Otherwise, we'll look at the child to the left of the cursor and find
// the last leaf node in its subtree.
var lastLeaf = getLastLeaf(nodeBeforeCursor);
leafKey = nullthrows(getSelectionOffsetKeyForNode(lastLeaf));
textLength = getTextContentLength(lastLeaf);
}
return {
key: leafKey,
offset: textLength
};
}
/**
* Return the length of a node's textContent, regarding single newline
* characters as zero-length. This allows us to avoid problems with identifying
* the correct selection offset for empty blocks in IE, in which we
* render newlines instead of break tags.
*/
function getTextContentLength(node) {
var textContent = node.textContent;
return textContent === '\n' ? 0 : textContent.length;
}
module.exports = getDraftEditorSelectionWithNodes;