// https://dom.spec.whatwg.org/#concept-live-range import {END, NEXT, PREV, START} from '../shared/symbols.js'; import {SVGElement} from '../svg/element.js'; import {getEnd, setAdjacent} from '../shared/utils.js'; const deleteContents = ({[START]: start, [END]: end}, fragment = null) => { setAdjacent(start[PREV], end[NEXT]); do { const after = getEnd(start); const next = after === end ? after : after[NEXT]; if (fragment) fragment.insertBefore(start, fragment[END]); else start.remove(); start = next; } while (start !== end); }; /** * @implements globalThis.Range */ export class Range { constructor() { this[START] = null; this[END] = null; this.commonAncestorContainer = null; } /* TODO: this is more complicated than it looks setStart(node, offset) { this[START] = node.childNodes[offset]; } setEnd(node, offset) { this[END] = getEnd(node.childNodes[offset]); } //*/ insertNode(newNode) { this[END].parentNode.insertBefore(newNode, this[START]); } selectNode(node) { this[START] = node; this[END] = getEnd(node); } // TODO: SVG elements should then create contextual fragments // that return SVG nodes selectNodeContents(node) { this.selectNode(node); this.commonAncestorContainer = node; } surroundContents(parentNode) { parentNode.replaceChildren(this.extractContents()); } setStartBefore(node) { this[START] = node; } setStartAfter(node) { this[START] = node.nextSibling; } setEndBefore(node) { this[END] = getEnd(node.previousSibling); } setEndAfter(node) { this[END] = getEnd(node); } cloneContents() { let {[START]: start, [END]: end} = this; const fragment = start.ownerDocument.createDocumentFragment(); while (start !== end) { fragment.insertBefore(start.cloneNode(true), fragment[END]); start = getEnd(start); if (start !== end) start = start[NEXT]; } return fragment; } deleteContents() { deleteContents(this); } extractContents() { const fragment = this[START].ownerDocument.createDocumentFragment(); deleteContents(this, fragment); return fragment; } createContextualFragment(html) { const { commonAncestorContainer: doc } = this; const isSVG = 'ownerSVGElement' in doc; const document = isSVG ? doc.ownerDocument : doc; const template = document.createElement('template'); template.innerHTML = html; let {content} = template; if (isSVG) { const childNodes = [...content.childNodes]; content = document.createDocumentFragment(); Object.setPrototypeOf(content, SVGElement.prototype); content.ownerSVGElement = document; for (const child of childNodes) { Object.setPrototypeOf(child, SVGElement.prototype); child.ownerSVGElement = document; content.appendChild(child); } } else this.selectNode(content); return content; } cloneRange() { const range = new Range; range[START] = this[START]; range[END] = this[END]; return range; } }