import { Context, evalGet } from ".."; import { classNames, extractAttributeName } from "../util"; interface AttributeDirectiveOptions { element: Element; context: Context; attr: Attr; } interface Is { sameNameProperty: boolean; bound: boolean; spread: boolean; componentProp: boolean; } export class AttributeDirective { element: Element; context: Context; expression: string; attr: Attr; extractedAttributeName: string; previousClasses: string[] = []; previousStyles: { [key: string]: string } = {}; is: Is = { sameNameProperty: false, bound: false, spread: false, componentProp: false, }; constructor({ element, context, attr }: AttributeDirectiveOptions) { this.element = element; this.context = context; this.expression = attr.value; this.attr = attr; this.extractedAttributeName = extractAttributeName(attr.name); this.is = { sameNameProperty: attr.name.startsWith("{") && attr.name.endsWith("}"), bound: attr.name.includes(":bind"), spread: attr.name.startsWith("..."), componentProp: false, }; if (this.is.sameNameProperty) { this.expression = this.extractedAttributeName; } if (this.is.spread) { this.expression = this.extractedAttributeName; } console.log("attribute", attr.name, "spread?", this.is.spread) element.removeAttribute(attr.name); if (this.is.bound) { context.effect(this.update.bind(this)); } else { this.update(); } } update() { let value = evalGet(this.context.scope, this.expression); if (this.is.spread && typeof value === "object") { for (const [key, val] of Object.entries(value)) { this.element.setAttribute(key, String(val)); } } else if ((typeof value === "object" || Array.isArray(value)) && this.extractedAttributeName === "class") { value = classNames(value); const next = value.split(" "); // If we now have classes that are not already on the element, add them now. // Remove classes that are no longer on the element. const diff = next.filter((c: string) => !this.previousClasses.includes(c)).filter(Boolean); const rm = this.previousClasses.filter((c) => !next.includes(c)); diff.forEach((c: string) => { this.previousClasses.push(c); this.element.classList.add(c); }); rm.forEach((c) => { this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c); this.element.classList.remove(c); }); } else if (typeof value === "object" && this.extractedAttributeName === "style") { const next = Object.keys(value); const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style)); next.forEach((style) => { this.previousStyles[style] = value[style]; // @ts-ignore this.element.style[style] = value[style]; }); rm.forEach((style) => { this.previousStyles[style] = ""; // @ts-ignore this.element.style[style] = ""; }); this.previousStyles = value; } else { this.element.setAttribute(this.extractedAttributeName, value); } } }