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 Mar 15, 2017
1 parent cb0e61c commit 987615f
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,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
115 changes: 80 additions & 35 deletions src/renderers/dom/shared/ReactInputSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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 === 1) {
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 @@ -53,13 +109,15 @@ 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.getSelection(element),
};
}),
};
},

Expand All @@ -69,38 +127,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 === 1) {
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

0 comments on commit 987615f

Please sign in to comment.