122 lines
4.2 KiB
JavaScript
122 lines
4.2 KiB
JavaScript
|
/**
|
||
|
* Copyright 2013-present, Facebook, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This source code is licensed under the BSD-style license found in the
|
||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var ReactDOMSelection = require('./ReactDOMSelection');
|
||
|
|
||
|
var containsNode = require('fbjs/lib/containsNode');
|
||
|
var focusNode = require('fbjs/lib/focusNode');
|
||
|
var getActiveElement = require('fbjs/lib/getActiveElement');
|
||
|
|
||
|
function isInDocument(node) {
|
||
|
return containsNode(document.documentElement, node);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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
|
||
|
* assume buttons have range selections allowed).
|
||
|
* Input selection module for React.
|
||
|
*/
|
||
|
var ReactInputSelection = {
|
||
|
hasSelectionCapabilities: function (elem) {
|
||
|
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
|
||
|
return nodeName && (nodeName === 'input' && elem.type === 'text' || nodeName === 'textarea' || elem.contentEditable === 'true');
|
||
|
},
|
||
|
|
||
|
getSelectionInformation: function () {
|
||
|
var focusedElem = getActiveElement();
|
||
|
return {
|
||
|
focusedElem: focusedElem,
|
||
|
selectionRange: ReactInputSelection.hasSelectionCapabilities(focusedElem) ? ReactInputSelection.getSelection(focusedElem) : null
|
||
|
};
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @restoreSelection: If any selection information was potentially lost,
|
||
|
* restore it. This is useful when performing operations that could remove dom
|
||
|
* nodes and place them back in, resulting in focus being lost.
|
||
|
*/
|
||
|
restoreSelection: function (priorSelectionInformation) {
|
||
|
var curFocusedElem = getActiveElement();
|
||
|
var priorFocusedElem = priorSelectionInformation.focusedElem;
|
||
|
var priorSelectionRange = priorSelectionInformation.selectionRange;
|
||
|
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
|
||
|
if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
|
||
|
ReactInputSelection.setSelection(priorFocusedElem, priorSelectionRange);
|
||
|
}
|
||
|
focusNode(priorFocusedElem);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @getSelection: Gets the selection bounds of a focused textarea, input or
|
||
|
* contentEditable node.
|
||
|
* -@input: Look up selection bounds of this input
|
||
|
* -@return {start: selectionStart, end: selectionEnd}
|
||
|
*/
|
||
|
getSelection: function (input) {
|
||
|
var selection;
|
||
|
|
||
|
if ('selectionStart' in input) {
|
||
|
// Modern browser with input or textarea.
|
||
|
selection = {
|
||
|
start: input.selectionStart,
|
||
|
end: input.selectionEnd
|
||
|
};
|
||
|
} else if (document.selection && input.nodeName && input.nodeName.toLowerCase() === 'input') {
|
||
|
// IE8 input.
|
||
|
var range = document.selection.createRange();
|
||
|
// There can only be one selection per document in IE, so it must
|
||
|
// be in our element.
|
||
|
if (range.parentElement() === input) {
|
||
|
selection = {
|
||
|
start: -range.moveStart('character', -input.value.length),
|
||
|
end: -range.moveEnd('character', -input.value.length)
|
||
|
};
|
||
|
}
|
||
|
} else {
|
||
|
// Content editable or old IE textarea.
|
||
|
selection = ReactDOMSelection.getOffsets(input);
|
||
|
}
|
||
|
|
||
|
return selection || { start: 0, end: 0 };
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @setSelection: Sets the selection bounds of a textarea or input and focuses
|
||
|
* the input.
|
||
|
* -@input Set selection bounds of this input or textarea
|
||
|
* -@offsets Object of same form that is returned from get*
|
||
|
*/
|
||
|
setSelection: function (input, offsets) {
|
||
|
var start = offsets.start;
|
||
|
var end = offsets.end;
|
||
|
if (end === undefined) {
|
||
|
end = start;
|
||
|
}
|
||
|
|
||
|
if ('selectionStart' in input) {
|
||
|
input.selectionStart = start;
|
||
|
input.selectionEnd = Math.min(end, input.value.length);
|
||
|
} else if (document.selection && input.nodeName && input.nodeName.toLowerCase() === 'input') {
|
||
|
var range = input.createTextRange();
|
||
|
range.collapse(true);
|
||
|
range.moveStart('character', start);
|
||
|
range.moveEnd('character', end - start);
|
||
|
range.select();
|
||
|
} else {
|
||
|
ReactDOMSelection.setOffsets(input, offsets);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
module.exports = ReactInputSelection;
|