You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
3.1 KiB
JavaScript
132 lines
3.1 KiB
JavaScript
'use strict';
|
|
// https://dom.spec.whatwg.org/#concept-live-range
|
|
|
|
const {END, NEXT, PREV, START} = require('../shared/symbols.js');
|
|
|
|
const {SVGElement} = require('../svg/element.js');
|
|
|
|
const {getEnd, setAdjacent} = require('../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
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
exports.Range = Range
|