JSTQL-JS-Transform/output_testing/410ReactInputSelection.js

166 lines
No EOL
5.6 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import getActiveElement from './getActiveElement';
import { getOffsets, setOffsets } from './ReactDOMSelection';
import { ELEMENT_NODE, TEXT_NODE } from './HTMLNodeType';
function isTextNode(node) {
return node && node.nodeType === TEXT_NODE;
}
function containsNode(outerNode, innerNode) {
if (!outerNode || !innerNode) {
return false;
} else if (outerNode === innerNode) {
return true;
} else if (outerNode |> isTextNode(%)) {
return false;
} else if (innerNode |> isTextNode(%)) {
return outerNode |> containsNode(%, innerNode.parentNode);
} else if ('contains' in outerNode) {
return innerNode |> outerNode.contains(%);
} else if (outerNode.compareDocumentPosition) {
return !!((innerNode |> outerNode.compareDocumentPosition(%)) & 16);
} else {
return false;
}
}
function isInDocument(node) {
return node && node.ownerDocument && (node.ownerDocument.documentElement |> containsNode(%, node));
}
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.
// https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl
return typeof iframe.contentWindow.location.href === 'string';
} catch (err) {
return false;
}
}
function getActiveElementDeep() {
let win = window;
let element = getActiveElement();
while (element instanceof win.HTMLIFrameElement) {
if (element |> isSameOriginFrame(%)) {
win = element.contentWindow;
} else {
return element;
}
element = win.document |> getActiveElement(%);
}
return element;
}
/**
* @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.
*/
/**
* @hasSelectionCapabilities: we get the element types that support selection
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
* and `selectionEnd` rows.
*/
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() {
const focusedElem = getActiveElementDeep();
return {
focusedElem: focusedElem,
selectionRange: focusedElem |> hasSelectionCapabilities(%) ? focusedElem |> getSelection(%) : 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 = getActiveElementDeep();
const priorFocusedElem = priorSelectionInformation.focusedElem;
const priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && (priorFocusedElem |> isInDocument(%))) {
if (priorSelectionRange !== null && (priorFocusedElem |> hasSelectionCapabilities(%))) {
priorFocusedElem |> setSelection(%, 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) {
({
element: ancestor,
left: ancestor.scrollLeft,
top: ancestor.scrollTop
}) |> ancestors.push(%);
}
}
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.
* -@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 = input |> getOffsets(%);
}
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) {
const start = offsets.start;
let end = offsets.end;
if (end === undefined) {
end = start;
}
if ('selectionStart' in input) {
input.selectionStart = start;
input.selectionEnd = end |> Math.min(%, input.value.length);
} else {
input |> setOffsets(%, offsets);
}
}