Skip to content

Commit

Permalink
Adapt restoreSelection to work for all activeElements
Browse files Browse the repository at this point in the history
  • Loading branch information
acusti committed May 4, 2017
1 parent d906f0a commit 2c7c68c
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 33 deletions.
114 changes: 83 additions & 31 deletions src/renderers/dom/shared/ReactInputSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var focusNode = require('fbjs/lib/focusNode');
var getActiveElement = require('fbjs/lib/getActiveElement');

function isInDocument(node) {
return containsNode(document.documentElement, node);
return node.ownerDocument && containsNode(node.ownerDocument.documentElement, node);
}

function getFocusedElement() {
Expand All @@ -36,6 +36,62 @@ function getFocusedElement() {
return focusedElem;
}

function getElementsWithSelections(acc, win) {
acc = acc || [];
win = win || window;
var doc;
try {
doc = win.document;
} catch (e) {
return acc;
}
var element = null;
if (win.getSelection) {
var selection = win.getSelection();
var startNode = selection.anchorNode;
var endNode = selection.focusNode;
var startOffset = selection.anchorOffset;
var endOffset = selection.focusOffset;
if (startNode && startNode.childNodes.length) {
if (startNode.childNodes[startOffset] === endNode.childNodes[endOffset]) {
element = startNode.childNodes[startOffset];
}
} else {
element = startNode;
}
} else if (doc.selection) {
var range = doc.selection.createRange();
element = range.parentElement();
}
if (ReactInputSelection.hasSelectionCapabilities(element)) {
acc = acc.concat(element);
}
return Array.prototype.reduce.call(win.frames, getElementsWithSelections, acc);
}

function focusNodePreservingScroll(element) {
// Focusing a node can change the scroll position, which is undesirable
const ancestors = [];
let ancestor = element;
while ((ancestor = ancestor.parentNode)) {
if (ancestor.nodeType === ELEMENT_NODE) {
ancestors.push({
element: ancestor,
left: ancestor.scrollLeft,
top: ancestor.scrollTop,
});
}
}

focusNode(element);

for (let i = 0; i < ancestors.length; i++) {
const info = ancestors[i];
info.element.scrollLeft = info.left;
info.element.scrollTop = info.top;
}
}

/**
* @ReactInputSelection: React input selection module. Based on Selection.js,
* but modified to be suitable for react and has a couple of bug fixes (doesn't
Expand All @@ -54,12 +110,17 @@ var ReactInputSelection = {
},

getSelectionInformation: function() {
var focusedElem = getFocusedElement();
var focusedElement = getFocusedElement();
return {
focusedElem: focusedElem,
selectionRange: ReactInputSelection.hasSelectionCapabilities(focusedElem)
? ReactInputSelection.getSelection(focusedElem)
: null,
focusedElement: focusedElement,
activeElements: getElementsWithSelections().map(function(element) {
return {
element: element,
selectionRange: ReactInputSelection.hasSelectionCapabilities(element)
? ReactInputSelection.getSelection(element)
: null,
};
}),
};
},

Expand All @@ -69,34 +130,25 @@ var ReactInputSelection = {
* nodes and place them back in, resulting in focus being lost.
*/
restoreSelection: function(priorSelectionInformation) {
var curFocusedElem = getFocusedElement();
var priorFocusedElem = priorSelectionInformation.focusedElem;
var priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
ReactInputSelection.setSelection(priorFocusedElem, priorSelectionRange);
}

// Focusing a node can change the scroll position, which is undesirable
const ancestors = [];
let ancestor = priorFocusedElem;
while ((ancestor = ancestor.parentNode)) {
if (ancestor.nodeType === ELEMENT_NODE) {
ancestors.push({
element: ancestor,
left: ancestor.scrollLeft,
top: ancestor.scrollTop,
});
priorSelectionInformation.activeElements.forEach(function(activeElement) {
var element = activeElement.element;
if (isInDocument(element) &&
getActiveElement(element.ownerDocument) !== element) {
if (ReactInputSelection.hasSelectionCapabilities(element)) {
ReactInputSelection.setSelection(
element,
activeElement.selectionRange
);
focusNodePreservingScroll(element);
}
}
});

focusNode(priorFocusedElem);

for (let i = 0; i < ancestors.length; i++) {
const info = ancestors[i];
info.element.scrollLeft = info.left;
info.element.scrollTop = info.top;
}
var curFocusedElement = getFocusedElement();
var priorFocusedElement = priorSelectionInformation.focusedElement;
if (curFocusedElement !== priorFocusedElement &&
isInDocument(priorFocusedElement)) {
focusNodePreservingScroll(priorFocusedElement);
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ describe('ReactInputSelection', () => {
input.selectionStart = 1;
input.selectionEnd = 10;
var selectionInfo = ReactInputSelection.getSelectionInformation();
expect(selectionInfo.focusedElem).toBe(input);
expect(selectionInfo.selectionRange).toEqual({start: 1, end: 10});
expect(selectionInfo.focusedElement).toBe(input);
expect(selectionInfo.activeElements[0].element).toBe(input);
expect(selectionInfo.activeElements[0].selectionRange).toEqual({start: 1, end: 10});
expect(document.activeElement).toBe(input);
input.setSelectionRange(0, 0);
document.body.removeChild(input);
Expand Down

0 comments on commit 2c7c68c

Please sign in to comment.