thi-ng/umbrella

View on GitHub
packages/rdom/src/wrap.ts

Summary

Maintainability
A
25 mins
Test Coverage
import type { Fn2 } from "@thi.ng/api";
import type { IComponent, IMountWithState, NumOrElement } from "./api.js";
import { $compile } from "./compile.js";
import { $addChild, $html, $remove, $text } from "./dom.js";

export interface WrappedComponent<T> extends IMountWithState<T> {
    inner: IComponent<any>;
}

/** @internal */
const __wrapper =
    <T>(update: Fn2<HTMLElement | SVGElement, T, void>) =>
    (tag: string, attribs?: any, body?: T) =>
        <WrappedComponent<T>>{
            async mount(parent: ParentNode, index: NumOrElement, state: T) {
                this.inner = $compile([tag, attribs]);
                this.el = await (<any>this).inner.mount(parent, index);
                update(<any>this.el!, state != null ? state : body!);
                return this.el!;
            },

            async unmount() {
                this.inner.unmount();
                this.el = undefined;
            },

            update(body: T) {
                if (this.el) update(<any>this.el, body);
            },
        };

/**
 * Returns a component wrapper for a single DOM element whose TEXT body can be
 * later updated/replaced via `.update()`, similarly to setting `.innerText`.
 *
 * @param tag - element name
 * @param attribs - element attribs
 * @param body - optional initial body
 */
export const $wrapText = __wrapper($text);

/**
 * Returns a component wrapper for a single DOM element whose HTML body can be
 * later updated/replaced via `.update()`, similarly to setting `.innerHTML`.
 *
 * @remarks
 * Setting `.innerHtml` considered dangerous — please use with caution or use
 * {@link $wrapText} if the source of the HTML body given to `.update()` cannot
 * be trusted!
 *
 * @example
 * ```ts
 * import { $compile, $wrapHtml } from "@thi.ng/rdom";
 *
 * // create pre-configured updatable element
 * const title = $wrapHtml("h1", { style: { color: "red" } });
 *
 * // embed inside rdom tree
 * $compile(["div", {}, title, "world..."]).mount(document.body);
 *
 * // update element body (only after element has been mounted!)
 * title.update("<em>hello</em>");
 * ```
 *
 * @param tag - element name
 * @param attribs - element attribs
 * @param body - optional initial body
 */
export const $wrapHtml = __wrapper($html);

/**
 * {@link IComponent} wrapper for an existing DOM element. When mounted, the
 * given element will be (re)attached to the parent node provided at that time.
 *
 * @example
 * ```ts
 * import { $compile, $wrapEl } from "@thi.ng/rdom";
 *
 * const title = document.createElement("h1");
 * title.innerText = "hello";
 *
 * // embed existing DOM element inside an rdom tree
 * $compile(["div", {}, $wrapEl(title), "world..."]).mount(document.body);
 * ```
 *
 * @param el
 */
export const $wrapEl = (el: Element): IComponent => ({
    async mount(parent, idx) {
        $addChild(parent, el, idx);
        return (this.el = el);
    },
    async unmount() {
        $remove(this.el!);
        this.el = undefined;
    },
    update() {},
});