"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const EventTarget_1 = __importDefault(require("../../event/EventTarget")); const MutationRecord_1 = __importDefault(require("../../mutation-observer/MutationRecord")); const MutationTypeEnum_1 = __importDefault(require("../../mutation-observer/MutationTypeEnum")); const DOMException_1 = __importDefault(require("../../exception/DOMException")); const NodeListFactory_1 = __importDefault(require("./NodeListFactory")); const NodeTypeEnum_1 = __importDefault(require("./NodeTypeEnum")); const NodeDocumentPositionEnum_1 = __importDefault(require("./NodeDocumentPositionEnum")); const NodeUtility_1 = __importDefault(require("./NodeUtility")); /** * Node. */ class Node extends EventTarget_1.default { /** * Constructor. */ constructor() { super(); this.ELEMENT_NODE = NodeTypeEnum_1.default.elementNode; this.ATTRIBUTE_NODE = NodeTypeEnum_1.default.attributeNode; this.TEXT_NODE = NodeTypeEnum_1.default.textNode; this.CDATA_SECTION_NODE = NodeTypeEnum_1.default.cdataSectionNode; this.COMMENT_NODE = NodeTypeEnum_1.default.commentNode; this.DOCUMENT_NODE = NodeTypeEnum_1.default.documentNode; this.DOCUMENT_TYPE_NODE = NodeTypeEnum_1.default.documentTypeNode; this.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum_1.default.documentFragmentNode; this.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum_1.default.processingInstructionNode; this.DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum_1.default.containedBy; this.DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum_1.default.contains; this.DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum_1.default.disconnect; this.DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum_1.default.following; this.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum_1.default.implementationSpecific; this.DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum_1.default.preceding; this.ownerDocument = null; this.parentNode = null; this.childNodes = NodeListFactory_1.default.create(); this.isConnected = false; // Custom Properties (not part of HTML standard) this._rootNode = null; this._observers = []; this.ownerDocument = this.constructor._ownerDocument; } /** * Returns `Symbol.toStringTag`. * * @returns `Symbol.toStringTag`. */ get [Symbol.toStringTag]() { return this.constructor.name; } /** * Get text value of children. * * @returns Text content. */ get textContent() { // Sub-classes should implement this method. return null; } /** * Sets text content. * * @param _textContent Text content. */ set textContent(_textContent) { // Do nothing. // Sub-classes should implement this method. } /** * Node value. * * @returns Node value. */ get nodeValue() { return null; } /** * Sets node value. */ set nodeValue(_nodeValue) { // Do nothing } /** * Node name. * * @returns Node name. */ get nodeName() { return ''; } /** * Previous sibling. * * @returns Node. */ get previousSibling() { if (this.parentNode) { const index = this.parentNode.childNodes.indexOf(this); if (index > 0) { return this.parentNode.childNodes[index - 1]; } } return null; } /** * Next sibling. * * @returns Node. */ get nextSibling() { if (this.parentNode) { const index = this.parentNode.childNodes.indexOf(this); if (index > -1 && index + 1 < this.parentNode.childNodes.length) { return this.parentNode.childNodes[index + 1]; } } return null; } /** * First child. * * @returns Node. */ get firstChild() { if (this.childNodes.length > 0) { return this.childNodes[0]; } return null; } /** * Last child. * * @returns Node. */ get lastChild() { if (this.childNodes.length > 0) { return this.childNodes[this.childNodes.length - 1]; } return null; } /** * Returns parent element. * * @returns Element. */ get parentElement() { let parent = this.parentNode; while (parent && parent.nodeType !== Node.ELEMENT_NODE) { parent = parent.parentNode; } return parent; } /** * Returns base URI. * * @returns Base URI. */ get baseURI() { const base = this.ownerDocument.querySelector('base'); if (base) { return base.href; } return this.ownerDocument.defaultView.location.href; } /** * Returns "true" if the node has child nodes. * * @returns "true" if the node has child nodes. */ hasChildNodes() { return this.childNodes.length > 0; } /** * Returns "true" if this node contains the other node. * * @param otherNode Node to test with. * @returns "true" if this node contains the other node. */ contains(otherNode) { if (this === otherNode) { return true; } for (const childNode of this.childNodes) { if (childNode === otherNode || childNode.contains(otherNode)) { return true; } } return false; } /** * Returns closest root node (Document or ShadowRoot). * * @param options Options. * @param options.composed A Boolean that indicates whether the shadow root should be returned (false, the default), or a root node beyond shadow root (true). * @returns Node. */ getRootNode(options) { if (!this.isConnected) { return this; } if (this._rootNode && !options?.composed) { return this._rootNode; } return this.ownerDocument; } /** * Clones a node. * * @param [deep=false] "true" to clone deep. * @returns Cloned node. */ cloneNode(deep = false) { const clone = new this.constructor(); // Document has childNodes directly when it is created if (clone.childNodes.length) { for (const node of clone.childNodes.slice()) { node.parentNode.removeChild(node); } } if (deep) { for (const childNode of this.childNodes) { const childClone = childNode.cloneNode(true); childClone.parentNode = clone; clone.childNodes.push(childClone); } } clone.ownerDocument = this.ownerDocument; return clone; } /** * Append a child node to childNodes. * * @param node Node to append. * @returns Appended node. */ appendChild(node) { if (node === this) { throw new DOMException_1.default('Not possible to append a node as a child of itself.'); } // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node. // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { for (const child of node.childNodes.slice()) { this.appendChild(child); } return node; } // Remove the node from its previous parent if it has any. if (node.parentNode) { const index = node.parentNode.childNodes.indexOf(node); if (index !== -1) { node.parentNode.childNodes.splice(index, 1); } } if (this.isConnected) { (this.ownerDocument || this)['_cacheID']++; } this.childNodes.push(node); node._connectToNode(this); // MutationObserver if (this._observers.length > 0) { const record = new MutationRecord_1.default(); record.target = this; record.type = MutationTypeEnum_1.default.childList; record.addedNodes = [node]; for (const observer of this._observers) { if (observer.options.subtree) { node._observe(observer); } if (observer.options.childList) { observer.callback([record]); } } } return node; } /** * Remove Child element from childNodes array. * * @param node Node to remove. * @returns Removed node. */ removeChild(node) { const index = this.childNodes.indexOf(node); if (index === -1) { throw new DOMException_1.default('Failed to remove node. Node is not child of parent.'); } if (this.isConnected) { (this.ownerDocument || this)['_cacheID']++; } this.childNodes.splice(index, 1); node._connectToNode(null); // MutationObserver if (this._observers.length > 0) { const record = new MutationRecord_1.default(); record.target = this; record.type = MutationTypeEnum_1.default.childList; record.removedNodes = [node]; for (const observer of this._observers) { node._unobserve(observer); if (observer.options.childList) { observer.callback([record]); } } } return node; } /** * Inserts a node before another. * * @param newNode Node to insert. * @param referenceNode Node to insert before. * @returns Inserted node. */ insertBefore(newNode, referenceNode) { // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node. // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { for (const child of newNode.childNodes.slice()) { this.insertBefore(child, referenceNode); } return newNode; } if (referenceNode === null) { this.appendChild(newNode); return newNode; } if (referenceNode === undefined) { throw new DOMException_1.default("Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.", 'TypeError'); } const index = referenceNode ? this.childNodes.indexOf(referenceNode) : 0; if (index === -1) { throw new DOMException_1.default("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."); } if (this.isConnected) { (this.ownerDocument || this)['_cacheID']++; } if (newNode.parentNode) { const index = newNode.parentNode.childNodes.indexOf(newNode); if (index !== -1) { newNode.parentNode.childNodes.splice(index, 1); } } this.childNodes.splice(index, 0, newNode); newNode._connectToNode(this); // MutationObserver if (this._observers.length > 0) { const record = new MutationRecord_1.default(); record.target = this; record.type = MutationTypeEnum_1.default.childList; record.addedNodes = [newNode]; for (const observer of this._observers) { if (observer.options.subtree) { newNode._observe(observer); } if (observer.options.childList) { observer.callback([record]); } } } return newNode; } /** * Replaces a node with another. * * @param newChild New child. * @param oldChild Old child. * @returns Replaced node. */ replaceChild(newChild, oldChild) { this.insertBefore(newChild, oldChild); this.removeChild(oldChild); return oldChild; } /** * @override */ dispatchEvent(event) { const returnValue = super.dispatchEvent(event); if (event.bubbles && !event._propagationStopped) { if (this.parentNode) { return this.parentNode.dispatchEvent(event); } // eslint-disable-next-line if (event.composed && this.nodeType === NodeTypeEnum_1.default.documentFragmentNode && this.host) { // eslint-disable-next-line return this.host.dispatchEvent(event); } } return returnValue; } /** * Converts the node to a string. * * @param listener Listener. */ toString() { return `[object ${this.constructor.name}]`; } /** * Observeres the node. * Used by MutationObserver, but it is not part of the HTML standard. * * @param listener Listener. */ _observe(listener) { this._observers.push(listener); if (listener.options.subtree) { for (const node of this.childNodes) { node._observe(listener); } } } /** * Stops observing the node. * Used by MutationObserver, but it is not part of the HTML standard. * * @param listener Listener. */ _unobserve(listener) { const index = this._observers.indexOf(listener); if (index !== -1) { this._observers.splice(index, 1); } if (listener.options.subtree) { for (const node of this.childNodes) { node._unobserve(listener); } } } /** * Connects this element to another element. * * @param parentNode Parent node. */ _connectToNode(parentNode = null) { const isConnected = !!parentNode && parentNode.isConnected; if (this.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) { this.parentNode = parentNode; this._rootNode = isConnected && parentNode ? parentNode._rootNode : null; } if (this.isConnected !== isConnected) { this.isConnected = isConnected; if (isConnected && this.connectedCallback) { this.connectedCallback(); } else if (!isConnected && this.disconnectedCallback) { if (this.ownerDocument['_activeElement'] === this) { this.ownerDocument['_activeElement'] = null; } this.disconnectedCallback(); } for (const child of this.childNodes) { child._connectToNode(this); } // eslint-disable-next-line if (this._shadowRoot) { // eslint-disable-next-line this._shadowRoot._connectToNode(this); } } } /** * Reports the position of its argument node relative to the node on which it is called. * * @see https://dom.spec.whatwg.org/#dom-node-comparedocumentposition * @param otherNode Other node. */ compareDocumentPosition(otherNode) { /** * 1. If this is other, then return zero. */ if (this === otherNode) { return 0; } /** * 2. Let node1 be other and node2 be this. */ let node1 = otherNode; let node2 = this; /** * 3. Let attr1 and attr2 be null. */ let attr1 = null; let attr2 = null; /** * 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element. */ if (node1.nodeType === Node.ATTRIBUTE_NODE) { attr1 = node1; node1 = attr1.ownerElement; } /** * 5. If node2 is an attribute, then: * 5.1. Set attr2 to node2 and node2 to attr2’s element. */ if (node2.nodeType === Node.ATTRIBUTE_NODE) { attr2 = node2; node2 = attr2.ownerElement; /** * 5.2. If attr1 and node1 are non-null, and node2 is node1, then: */ if (attr1 !== null && node1 !== null && node2 === node1) { /** * 5.2.1. For each attr in node2’s attribute list: */ for (const attr of Object.values(node2.attributes)) { /** * 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING. */ if (NodeUtility_1.default.isEqualNode(attr, attr1)) { return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING); } /** * 5.2.1.2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING. */ if (NodeUtility_1.default.isEqualNode(attr, attr2)) { return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING); } } } } const node2Ancestors = []; let node2Ancestor = node2; while (node2Ancestor) { /** * 7. If node1 is an ancestor of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING. */ if (node2Ancestor === node1) { return Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING; } node2Ancestors.push(node2Ancestor); node2Ancestor = node2Ancestor.parentNode; } const node1Ancestors = []; let node1Ancestor = node1; while (node1Ancestor) { /** * 8. If node1 is a descendant of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING. */ if (node1Ancestor === node2) { return Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING; } node1Ancestors.push(node1Ancestor); node1Ancestor = node1Ancestor.parentNode; } const reverseArrayIndex = (array, reverseIndex) => { return array[array.length - 1 - reverseIndex]; }; const root = reverseArrayIndex(node2Ancestors, 0); /** * 6. If node1 or node2 is null, or node1’s root is not node2’s root, then return the result of adding * DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either * DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together. */ if (!root || root !== reverseArrayIndex(node1Ancestors, 0)) { return (Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING); } // Find the lowest common ancestor let commonAncestorIndex = 0; const ancestorsMinLength = Math.min(node2Ancestors.length, node1Ancestors.length); for (let i = 0; i < ancestorsMinLength; ++i) { const node2Ancestor = reverseArrayIndex(node2Ancestors, i); const node1Ancestor = reverseArrayIndex(node1Ancestors, i); if (node2Ancestor !== node1Ancestor) { break; } commonAncestorIndex = i; } const commonAncestor = reverseArrayIndex(node2Ancestors, commonAncestorIndex); // Indexes within the common ancestor let indexes = 0; let node2Index = -1; let node1Index = -1; const node2Node = reverseArrayIndex(node2Ancestors, commonAncestorIndex + 1); const node1Node = reverseArrayIndex(node1Ancestors, commonAncestorIndex + 1); const computeNodeIndexes = (nodes) => { for (const childNode of nodes) { computeNodeIndexes(childNode.childNodes); if (childNode === node2Node) { node2Index = indexes; } else if (childNode === node1Node) { node1Index = indexes; } if (node2Index !== -1 && node1Index !== -1) { break; } indexes++; } }; computeNodeIndexes(commonAncestor.childNodes); /** * 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING. * 10. Return DOCUMENT_POSITION_FOLLOWING. */ return node1Index < node2Index ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING; } } exports.default = Node; // Owner document is set when the Node is created by the Document Node._ownerDocument = null; // Public properties Node.ELEMENT_NODE = NodeTypeEnum_1.default.elementNode; Node.ATTRIBUTE_NODE = NodeTypeEnum_1.default.attributeNode; Node.TEXT_NODE = NodeTypeEnum_1.default.textNode; Node.CDATA_SECTION_NODE = NodeTypeEnum_1.default.cdataSectionNode; Node.COMMENT_NODE = NodeTypeEnum_1.default.commentNode; Node.DOCUMENT_NODE = NodeTypeEnum_1.default.documentNode; Node.DOCUMENT_TYPE_NODE = NodeTypeEnum_1.default.documentTypeNode; Node.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum_1.default.documentFragmentNode; Node.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum_1.default.processingInstructionNode; Node.DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum_1.default.containedBy; Node.DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum_1.default.contains; Node.DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum_1.default.disconnect; Node.DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum_1.default.following; Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum_1.default.implementationSpecific; Node.DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum_1.default.preceding; //# sourceMappingURL=Node.js.map