跳到主要内容

React input selection

一、作用

二、具有选择能力

/**
* @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.
* @ReactInputSelection:React 输入选择模块。基于 Selection.js, 但经过修改以适应
* React,并修复了一些错误(不假设按钮允许范围选择)。React 的输入选择模块。
**/
/**
* @hasSelectionCapabilities: we get the element types that support selection
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
* and `selectionEnd` rows.
* @hasSelectionCapabilities: 我们获取支持选择的元素类型
* 来源:https://html.spec.whatwg.org/#do-not-apply,查看 `selectionStart`
* 和 `selectionEnd` 行。
*/
export function hasSelectionCapabilities(elem) {
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return (
nodeName &&
((nodeName === 'input' &&
(elem.type === 'text' ||
elem.type === 'search' ||
elem.type === 'tel' ||
elem.type === 'url' ||
elem.type === 'password')) ||
nodeName === 'textarea' ||
elem.contentEditable === 'true')
);
}

三、获取选中信息

export function getSelectionInformation(containerInfo) {
const focusedElem = getActiveElementDeep(containerInfo);
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.
*
* @restoreSelection:如果有任何选择信息可能丢失,恢复它。当执行可能移除 DOM 节点并重
* 新放回的操作时,这非常有用,因为这样可能导致焦点丢失。
*/
export function restoreSelection(priorSelectionInformation, containerInfo) {
const curFocusedElem = getActiveElementDeep(containerInfo);
const priorFocusedElem = priorSelectionInformation.focusedElem;
const priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
if (
priorSelectionRange !== null &&
hasSelectionCapabilities(priorFocusedElem)
) {
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,
});
}
}

if (typeof priorFocusedElem.focus === 'function') {
priorFocusedElem.focus();
}

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

五、获取选区

备注
/**
* @getSelection: Gets the selection bounds of a focused textarea, input or
* contentEditable node.
* @getSelection:获取聚焦的 textarea、输入框或 contentEditable 节点的选区范围。
* -@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.
// 可编辑内容或旧版 IE 文本区域。
selection = 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*
* 从 get* 返回的相同形式的对象
*/
export function setSelection(input, offsets) {
const start = offsets.start;
let end = offsets.end;
if (end === undefined) {
end = start;
}

if ('selectionStart' in input) {
input.selectionStart = start;
input.selectionEnd = Math.min(end, input.value.length);
} else {
setOffsets(input, offsets);
}
}

七、工具

1. 是文本节点

function isTextNode(node) {
return node && node.nodeType === TEXT_NODE;
}

2. 包含节点

function containsNode(outerNode, innerNode) {
if (!outerNode || !innerNode) {
return false;
} else if (outerNode === innerNode) {
return true;
} else if (isTextNode(outerNode)) {
return false;
} else if (isTextNode(innerNode)) {
return containsNode(outerNode, innerNode.parentNode);
} else if ('contains' in outerNode) {
return outerNode.contains(innerNode);
} else if (outerNode.compareDocumentPosition) {
return !!(outerNode.compareDocumentPosition(innerNode) & 16);
} else {
return false;
}
}

3. 在文档中

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

4. 是否同源框架

function isSameOriginFrame(iframe) {
try {
// Accessing the contentDocument of a HTMLIframeElement can cause the browser
// to throw, e.g. if it has a cross-origin src attribute.
// Safari will show an error in the console when the access results in "Blocked a frame with origin". e.g:
// iframe.contentDocument.defaultView;
// A safety way is to access one of the cross origin properties: Window or Location
// Which might result in "SecurityError" DOM Exception and it is compatible to Safari.
//
// 访问 HTMLIframeElement 的 contentDocument 可能会导致浏览器抛出异常,
// 例如当其具有跨源 src 属性时。
// 当访问导致“Blocked a frame with origin”时,Safari 会在控制台显示错误。例如:
// iframe.contentDocument.defaultView;
// 一个安全的方法是访问跨源属性之一:Window 或 Location
// 这可能会导致“SecurityError”DOM 异常,并且与 Safari 兼容。
// https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl

return typeof iframe.contentWindow.location.href === 'string';
} catch (err) {
return false;
}
}

5. 获取深层活动元素

备注
function getActiveElementDeep(containerInfo) {
let win =
containerInfo != null &&
containerInfo.ownerDocument != null &&
containerInfo.ownerDocument.defaultView != null
? containerInfo.ownerDocument.defaultView
: window;
let element = getActiveElement(win.document);
while (element instanceof win.HTMLIFrameElement) {
if (isSameOriginFrame(element)) {
win = element.contentWindow;
} else {
return element;
}
element = getActiveElement(win.document);
}
return element;
}