react/packages/react-dom/src/client/ReactInputSelection.js

125 lines
3.6 KiB
JavaScript
Raw Normal View History

2013-05-30 03:46:11 +08:00
/**
* Copyright (c) 2013-present, Facebook, Inc.
2013-05-30 03:46:11 +08:00
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
2013-05-30 03:46:11 +08:00
*/
import containsNode from 'fbjs/lib/containsNode';
import focusNode from 'fbjs/lib/focusNode';
import getActiveElement from 'fbjs/lib/getActiveElement';
2013-10-02 08:54:30 +08:00
import * as ReactDOMSelection from './ReactDOMSelection';
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
function isInDocument(node) {
return containsNode(document.documentElement, node);
}
2013-05-30 03:46:11 +08:00
/**
* @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.
*/
export function hasSelectionCapabilities(elem) {
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return (
nodeName &&
((nodeName === 'input' && elem.type === 'text') ||
nodeName === 'textarea' ||
elem.contentEditable === 'true')
);
}
export function getSelectionInformation() {
const focusedElem = getActiveElement();
return {
focusedElem: focusedElem,
selectionRange: hasSelectionCapabilities(focusedElem)
? 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.
*/
export function restoreSelection(priorSelectionInformation) {
const curFocusedElem = getActiveElement();
const priorFocusedElem = priorSelectionInformation.focusedElem;
const priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
if (hasSelectionCapabilities(priorFocusedElem)) {
setSelection(priorFocusedElem, priorSelectionRange);
2013-05-30 03:46:11 +08:00
}
// 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,
});
}
2013-05-30 03:46:11 +08:00
}
focusNode(priorFocusedElem);
for (let i = 0; i < ancestors.length; i++) {
const info = ancestors[i];
info.element.scrollLeft = info.left;
info.element.scrollTop = info.top;
2013-05-30 03:46:11 +08:00
}
}
}
/**
* @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}
*/
export function getSelection(input) {
let selection;
if ('selectionStart' in input) {
// Modern browser with input or textarea.
selection = {
start: input.selectionStart,
end: input.selectionEnd,
};
} else {
// Content editable or old IE textarea.
selection = ReactDOMSelection.getOffsets(input);
}
2013-05-30 03:46:11 +08:00
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*
*/
export function setSelection(input, offsets) {
let {start, end} = offsets;
if (end === undefined) {
end = start;
}
if ('selectionStart' in input) {
input.selectionStart = start;
input.selectionEnd = Math.min(end, input.value.length);
} else {
ReactDOMSelection.setOffsets(input, offsets);
}
}