shadow-selection-polyfill
Version:
Polyfill for shadowRoot.getSelection in Safari 10+
129 lines (106 loc) • 4.68 kB
HTML
<!--
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
-->
<html>
<head>
<script type="module">
import * as shadow from './shadow.js';
const root1 = host1.attachShadow({mode: 'open'});
host1.style.border = 'blue 4px solid';
root1.innerHTML = `
Starts with space, but simplest text possible
<style type="ignored">x</style>
<div>
<strong>stronger</strong> <em>@</em> q<div style="background: #eee"><slot></slot></div><-- slot<div id="host2"></div>
More DOM text nodes</div>`;
const host2 = root1.getElementById('host2');
host2.style.border = 'red 4px solid';
const root2 = host2.attachShadow({mode: 'open'});
root2.innerHTML = `<div>Second layer is weird <img data-first src="data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" /><img data-second src="data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" /><em>super weird</em></div>`;
const renderTree = (node) => {
const out = [];
while (node && !(node instanceof DocumentFragment)) {
if (node.nodeType === Node.TEXT_NODE) {
out.unshift('#text')
} else {
out.unshift(node.localName);
}
node = node.parentNode;
}
return out.join('/');
};
const formatRange = (r) => {
if (!r) {
return '-';
} else if (r.startContainer === r.endContainer) {
return `"${r.toString()}" (${renderTree(r.startContainer)}:${r.startOffset}-${r.endOffset})`;
}
return `"${r.toString()}" (${renderTree(r.startContainer)}:${r.startOffset},${renderTree(r.endContainer)}:${r.endOffset})`;
};
// nb. We don't need to use "-shadow-selectionchange", but it only fires _once_, whereas
// "selectionchange" will fire multiple times due to us changing it.
document.addEventListener('-shadow-selectionchange', () => {
const ss1 = shadow.getRange(root1);
// console.info('ss1', ss1, ss1 && ss1.toString());
out1.textContent = formatRange(ss1);
const ss2 = shadow.getRange(root2);
// console.info('ss2', ss2, ss2 && ss2.toString());
out2.textContent = formatRange(ss2);
});
extendForward.addEventListener('click', () => {
window.getSelection().modify('extend', 'forward', 'character');
console.info('step');
});
extendBackward.addEventListener('click', () => {
window.getSelection().modify('extend', 'backward', 'character');
});
</script>
<style>
body {
font-family: 'Roboto', 'Open Sans', Sans-Serif;
/* white-space: pre; */
}
main {
font-size: 2em;
}
</style>
<body>
<h1>Shadow DOM selection page</h1>
<p>This page demos detecting the selection range within a shadow root, for Safari, which does not yet support <code>ShadowRoot.getSelection</code>.
(It still works in Chrome, it just uses the native version.)
Open the console to see output—the blue outline is the first root, and the red is the inner root.</p>
<h2>TODOs</h2>
<ul>
<li>Dragging right onto non-text nodes sometimes selects both, see the images below.</li>
<li>We should select the furthest possible parent when we select the extent of l/r on text nodes.</li>
<li>In Chrome, selecting just entire 2nd root shows a selection in the 1st root just over that subnode. We could support this for Safari, but it'd be fairly ugly.</li>
<li>We include all whitespace found at the l/r of nodes in the Range, but Chrome does not.</li>
</ul>
<h2>Selection</h2>
<ul>
<li>root1 selection: <span id="out1"></span></li>
<li>root2 selection: <span id="out2"></span></li>
</ul>
<button id="extendForward">Extend Forwards</button>
<button id="extendBackward">Extend Backwards</button>
<main>
<div>
Content is below me<br />
</div>
<div id="host1">
I'm some content in the DOM
</div>
^^ That's my host above
</main>
</body>
</html>