nehan
Version:
Html layout engine for paged-media written in Typescript
363 lines • 10.9 kB
JavaScript
import { CssStyleDeclaration, DomTokenList, SelectorLexer, SelectorParser, WhiteSpace, ReplacedElement, PhysicalSize, CssLoader, } from "./public-api";
export class NehanElement {
constructor(node, root) {
this.$node = node;
this.$dom = undefined;
this.tagName = this.getTagName();
this.childNodes = [];
this.parent = null;
this.root = root;
this.style = new CssStyleDeclaration();
this.computedStyle = new CssStyleDeclaration();
this.id = this.$node instanceof Element ? this.$node.id : "";
this.classList = this.createClassList();
this.setupChildren(this.$node, root);
}
acceptChildFilter(visitor) {
this.childNodes = this.childNodes.filter(node => visitor.visit(node));
return this;
}
acceptEffector(visitor) {
visitor.visit(this);
return this;
}
acceptEffectorAll(visitor) {
visitor.visit(this);
this.childNodes.forEach(node => node.acceptEffectorAll(visitor));
return this;
}
get nextSibling() {
if (!this.parent) {
return null;
}
const index = this.parent.childNodes.indexOf(this);
return (index < 0 || index + 1 >= this.parent.childNodes.length) ? null : this.parent.childNodes[index + 1];
}
get previousSibling() {
if (!this.parent) {
return null;
}
const index = this.parent.childNodes.indexOf(this);
return (index > 0) ? this.parent.childNodes[index - 1] : null;
}
clone(deep = false) {
const element = this.ownerDocument.createNehanElement(this.$node.cloneNode(deep));
element.style = this.style;
element.computedStyle = this.computedStyle;
return element;
}
createClassList() {
if (this.$node instanceof Element) {
let items = [];
for (let i = 0; i < this.$node.classList.length; i++) {
let item = this.$node.classList.item(i);
if (item !== null) {
items.push(item);
}
}
return new DomTokenList(items);
}
return new DomTokenList([]);
}
setupChildren($node, root) {
if ($node instanceof Element) {
for (let i = 0; i < $node.childNodes.length; i++) {
let child = $node.childNodes.item(i);
let child_element = root.createNehanElement(child);
this.appendChild(child_element);
}
}
}
set innerHTML(html) {
this.$node.innerHTML = html;
this.childNodes = [];
this.setupChildren(this.$node, this.root);
CssLoader.loadAll(this);
}
get innerHTML() {
if (this.isTextElement()) {
return this.textContent;
}
return this.$node.innerHTML;
}
get className() {
return this.classList.values().join(" ");
}
set className(class_names) {
let tokens = class_names.split(" ").filter(cls => cls !== "");
this.classList = new DomTokenList(tokens);
}
get pureTagName() {
return this.tagName.replace("::", "");
}
get attributes() {
if (this.$node instanceof Element) {
return this.$node.attributes;
}
return null;
}
get dataset() {
if (this.$node instanceof Element) {
return this.$node.dataset;
}
throw new Error("dataset is not defined(not HTMLElement)");
}
get textContent() {
return this.$node.textContent || "";
}
getTagName() {
if (this.$node instanceof Text) {
return "(text)";
}
if (this.$node instanceof Element) {
return this.$node.tagName.toLowerCase();
}
console.info("unsupported node type:%o", this);
return "???";
}
getNodeName() {
let str = this.tagName;
if (this.id) {
str += "#" + this.id;
}
for (let i = 0; i < this.classList.length; i++) {
str += "." + this.classList.item(i);
}
return str;
}
getPath(with_parent = false) {
let str = this.getNodeName();
if (!with_parent) {
return str;
}
let parent = this.parent;
while (parent) {
str = parent.getNodeName() + ">" + str;
parent = parent.parent;
}
return str;
}
toString(with_index = false) {
let str = this.getPath(true);
if (!with_index) {
return str;
}
let index = this.indexOfType;
str += "(" + index + ")";
return str;
}
querySelectorAll(query) {
const lexer = new SelectorLexer(query);
const selector = new SelectorParser(lexer).parse();
const elements = selector.querySelectorAll(this);
return elements;
}
querySelector(query) {
const lexer = new SelectorLexer(query);
const selector = new SelectorParser(lexer).parse();
const element = selector.querySelector(this);
return element;
}
queryLeafs(selector) {
return this.root.getSelectorCache(selector).filter(leaf => {
let parent = leaf.parent;
while (parent) {
if (parent === this) {
return true;
}
parent = parent.parent;
}
return false;
});
}
appendChild(element) {
element.parent = this;
this.childNodes.push(element);
return this;
}
replaceChild(new_child, old_child) {
if (old_child) {
const index = this.childNodes.indexOf(old_child);
if (index >= 0) {
new_child.parent = this;
this.childNodes[index] = new_child;
}
}
return new_child;
}
removeChild(target_child) {
const index = this.childNodes.indexOf(target_child);
if (index >= 0) {
this.childNodes.splice(index, 1);
}
return target_child;
}
insertBefore(new_node, ref_node) {
if (!ref_node) {
this.appendChild(new_node);
return null;
}
if (ref_node.parent !== this) {
throw new Error("reference node is not included in this element");
}
new_node.parent = this;
const index = this.childNodes.indexOf(ref_node);
if (index >= 0) {
this.childNodes.splice(index, 0, new_node);
}
return ref_node.nextSibling ? new_node : null;
}
hasAttribute(name) {
if (this.$node instanceof Element) {
return this.$node.hasAttribute(name);
}
return false;
}
isOnlyChild() {
return this.siblings.length === 1;
}
isOnlyOfType() {
const siblings = this.siblings.filter(sib => sib.tagName === this.tagName);
return siblings.length === 1;
}
isFirstChild() {
if (!this.parent) {
return true;
}
return this.parent.childNodes.indexOf(this) === 0;
}
isLastChild() {
if (!this.parent) {
return true;
}
const children = this.parent.childNodes;
return children[children.length - 1] === this;
}
isFirstElementChild() {
if (!this.parent) {
return true;
}
return this.parent.children[0] === this;
}
isLastElementChild() {
if (!this.parent) {
return true;
}
const children = this.parent.children;
return children[children.length - 1] === this;
}
isNthChild(nth) {
return this.index === Math.max(nth - 1, 0);
}
isElement() {
return this.$node instanceof HTMLElement;
}
isTextElement() {
return this.$node instanceof Text;
}
setAttribute(name, value) {
if (this.$node instanceof Element) {
this.$node.setAttribute(name, value);
}
}
get ownerDocument() {
return this.root;
}
get firstChild() {
return this.childNodes[0] || null;
}
get firstTextElement() {
const firstChild = this.firstChild;
if (!firstChild) {
return null;
}
if (firstChild.isTextElement()) {
return firstChild;
}
const nextChild = firstChild.nextSibling;
if (!nextChild) {
return null;
}
return nextChild.firstTextElement;
}
get lastChild() {
return this.childNodes[this.childNodes.length - 1] || null;
}
get lastElementChild() {
const children = this.children;
return children[children.length - 1] || null;
}
get firstElementChild() {
return this.children[0] || null;
}
get nextElementSibling() {
let next = this.nextSibling;
while (next) {
if (next.isElement()) {
break;
}
next = next.nextSibling;
}
return next;
}
get previousElementSibling() {
let prev = this.previousSibling;
while (prev) {
if (prev.isElement()) {
break;
}
prev = prev.previousSibling;
}
return prev;
}
get firstAtomChild() {
for (let i = 0; i < this.childNodes.length; i++) {
const child = this.childNodes[i];
if (child.isTextElement() && !WhiteSpace.isWhiteSpaceElement(child)) {
return child;
}
if (ReplacedElement.isReplacedElement(child) && !PhysicalSize.load(child).hasZero()) {
return child;
}
const firstAtomChild = child.firstAtomChild;
if (firstAtomChild) {
return firstAtomChild;
}
}
return null;
}
get siblings() {
if (!this.parent) {
return [];
}
return this.parent.childNodes;
}
get index() {
if (!this.parent) {
return 0;
}
return this.parent.childNodes.indexOf(this);
}
get indexOfType() {
if (!this.parent) {
return 0;
}
return this.parent.childNodes.filter(child => child.tagName === this.tagName).indexOf(this);
}
get indexOfElement() {
if (!this.parent) {
return 0;
}
return this.parent.children.indexOf(this);
}
get children() {
return this.childNodes.filter(element => !element.isTextElement());
}
getAttribute(name) {
if (this.$node instanceof Element) {
return this.$node.getAttribute(name);
}
return null;
}
}
//# sourceMappingURL=nehan-element.js.map