vasille
Version:
The same framework which is designed to build bulletproof frontends (core library).
175 lines (174 loc) • 5.79 kB
JavaScript
import { TextNode as AbstractTextNode, DebugNode as AbstractDebugNode, Tag as AbstractTag, IValue, safe, } from "../../index.js";
import { internalError } from "../../core/errors.js";
import { AttributeBinding } from "./binding/attribute.js";
import { addClass, DynamicalClassBinding, removeClass, StaticClassBinding } from "./binding/class.js";
import { PropertyBinding } from "./binding/property.js";
import { stringifyStyleValue, StyleBinding } from "./binding/style.js";
export class TextNode extends AbstractTextNode {
node;
compose() {
const text = this.data;
this.node = this.runner.document.createTextNode((text instanceof IValue ? text.V : text)?.toString() ?? "");
if (text instanceof IValue) {
this.handler = (v) => {
this.node.replaceData(0, -1, v?.toString() ?? "");
};
text.on(this.handler);
}
this.parent.appendNode(this.node);
}
destroy() {
this.node.remove();
super.destroy();
}
findFirstChild() {
return this.node;
}
}
export class DebugNode extends AbstractDebugNode {
node;
compose() {
const text = this.data;
this.node = this.runner.document.createComment(text.V?.toString() ?? "");
this.handler = (v) => {
this.node.replaceData(0, -1, v?.toString() ?? "");
};
text.on(this.handler);
this.parent.appendNode(this.node);
}
destroy() {
this.node.remove();
super.destroy();
}
findFirstChild() {
return this.node;
}
}
export class Tag extends AbstractTag {
compose() {
if (!this.name) {
throw internalError("wrong Tag constructor call");
}
const node = this.runner.document.createElement(this.name);
this.node = node;
this.applyOptions(this.options);
this.parent.appendNode(node);
this.options.callback?.(this.node);
this.options.slot?.(this);
}
destroy() {
this.node.remove();
super.destroy();
}
applyOptions(options) {
if (options.attr) {
for (const name in options.attr) {
const value = options.attr[name];
if (value instanceof IValue) {
this.bind(new AttributeBinding(this, name, value));
}
else {
/* istanbul ignore else */
if (typeof value === "boolean") {
/* istanbul ignore else */
if (value) {
this.node.setAttribute(name, "");
}
}
else if (value !== null && value !== undefined) {
this.node.setAttribute(name, `${value}`);
}
}
}
}
if (options.class) {
options.class.forEach(item => {
if (item instanceof IValue) {
this.bind(new DynamicalClassBinding(this, item));
}
else if (typeof item == "string") {
addClass(this, item);
}
else {
for (const name in item) {
const value = item[name];
if (value instanceof IValue) {
this.bind(new StaticClassBinding(this, name, value));
}
else if (value) {
addClass(this, name);
}
else {
removeClass(this, name);
}
}
}
});
}
if (options.style && this.node instanceof HTMLElement) {
for (const name in options.style) {
const value = options.style[name];
if (value instanceof IValue) {
this.bind(new StyleBinding(this, name, value));
}
else {
this.node.style.setProperty(name, stringifyStyleValue(value));
}
}
}
if (options.events) {
for (const name in options.events) {
const event = options.events[name];
if (event instanceof Array) {
this.node.addEventListener(name, safe(event[0]), event[1]);
}
else {
this.node.addEventListener(name, safe(event));
}
}
}
if (options.bind) {
const node = this.node;
for (const k in options.bind) {
const value = options.bind[k];
if (!(value instanceof IValue)) {
node[k] = value;
}
else {
node[k] = value.V;
this.bind(new PropertyBinding(this, k, value));
}
}
}
}
}
export class Runner {
debugUi;
document;
constructor(debugUi, document) {
this.debugUi = debugUi;
this.document = document;
}
insertBefore(node, before) {
const parent = before.parentElement;
/* istanbul ignore else */
if (parent) {
parent.insertBefore(node, before);
}
}
appendChild(node, child) {
node.appendChild(child);
}
textNode(text) {
return new TextNode({ text }, this);
}
debugNode(text) {
return new DebugNode({ text }, this);
}
tag(tagName, input, cb) {
if (cb) {
input.slot = cb;
}
return new Tag(input, this, tagName);
}
}