shadow-selection-polyfill
Version:
Polyfill for shadowRoot.getSelection in Safari 10+
93 lines (68 loc) • 2.19 kB
JavaScript
export const eventName = '-shadow-selectionchange';
const validNodeTypes = [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.DOCUMENT_FRAGMENT_NODE];
function isValidNode(node) {
return validNodeTypes.includes(node.nodeType);
}
function findNode(s, parentNode, isLeft) {
const nodes = parentNode.childNodes || parentNode.children;
if (!nodes || !nodes.length) {
return {node: parentNode, extent: true}; // found it, probably text or e.g. <img>
}
for (let i = 0; i < nodes.length; ++i) {
const j = isLeft ? i : (nodes.length - 1 - i);
const childNode = nodes[j];
if (!isValidNode(childNode)) {
continue;
}
if (s.containsNode(childNode, true)) {
if (childNode.nodeType === Node.TEXT_NODE) {
return {node: childNode, extent: false}; // text nodes always report whole match, ignore
}
if (s.containsNode(childNode, false)) {
return {node: childNode, extent: true}; // complete match, whole child
}
return findNode(s, childNode, isLeft);
}
}
return {node: parentNode, extent: false};
}
const addInternalListener = (() => {
let withinInternals = false;
const handlers = [];
document.addEventListener('selectionchange', (ev) => {
if (withinInternals) {
return;
}
document.dispatchEvent(new CustomEvent(eventName));
withinInternals = true;
window.setTimeout(() => {
withinInternals = false;
}, 0);
handlers.forEach((fn) => fn(ev));
});
return (fn) => handlers.push(fn);
})();
/**
* @param {!ShadowRoot} root
*/
export function getRange(root) {
const s = window.getSelection();
const left = findNode(s, root, true);
if (left.node === root) {
return null;
}
const right = findNode(s, root, false);
if (right.node === root) {
throw new TypeError('found left node but not right node');
}
if (s.type !== 'Range') {
return null;
}
const initialSize = s.toString().length;
s.modify('extend', 'left', 'character');
const updatedSize = s.toString().length;
console.info('initial size', initialSize, 'vs', updatedSize);
s.modify('extend', 'right', 'character');
console.info(left, right);
return null;
}