UNPKG

@tmorin/ceb-templating-builder

Version:

The package is part of the `<ceb/>` library. It provides a builder which enhances the definition of Custom Elements (v1) with a templating solution.

288 lines 11.2 kB
import { ElementBuilder } from "@tmorin/ceb-elements-core"; import { Engine } from "@tmorin/ceb-templating-engine"; /** * The builder handles the integration of a templating solution to update the content of the Custom Element. * * Firstly, the integration of the templating solution has to be defined in a method, by default the method name is `render`. * The implementation of the method (i.e. the method `render`) must return an instance of {@link Template}. * * Secondly, the render method is wrapped by the builder in order to render the {@link Template} once returned. * That means, each time the render method is invoked, the returned {@link Template} is rendered automatically. * The wrapping is also responsible to select the right destination of the template: Light DOM vs Shadow DOM. * * By default, the template is rendered into the Light DOM of the Custom Element. * However, the builder can render the template into the Shadow DOM with {@link TemplateBuilder.shadow}. * Another switch, {@link TemplateBuilder.grey} can be used to force the rendering into a _scope_. * * If the _scope_ is not required, the Custom Element content can be simply preserve from ancestral rendering process with {@link TemplateBuilder.preserveContent}. * * Attributes can also be preserved from ancestral rendering process with {@link TemplateBuilder.preserveAttributes}. * * By default, the name of the wrapped method is `render`. * However, the name can be changed with {@link TemplateBuilder.method}. * * When a {@link Template} is rendered, template parameters can be provided to the {@link Template.render} method. * The template parameters can be set with {@link TemplateBuilder.parameters}. * * The library provides a built-in template solution: {@link html}. * * Finally, the builder can be registered using the method {@link ElementBuilder.builder} of the main builder (i.e. {@link ElementBuilder}). * However, it can also be registered with the decorative style using the decorator {@link TemplateBuilder.decorate}. * * @template E the type of the Custom Element * @template P The type of the template parameters. */ export class TemplateBuilder { constructor(_isGrey = false, _preserveContent = false, _preserveAttributes = [], _isShadow = false, _isFocusDelegation, _methName = "render", _parameters) { this._isGrey = _isGrey; this._preserveContent = _preserveContent; this._preserveAttributes = _preserveAttributes; this._isShadow = _isShadow; this._isFocusDelegation = _isFocusDelegation; this._methName = _methName; this._parameters = _parameters; } /** * Provides a fresh builder. * @template E the type of the Custom Element * @template P The type of the template parameters. */ static get() { return new TemplateBuilder(); } /** * Forces the rendering into the Shadow DOM. * * {@link TemplateBuilder.shadow} and {@link TemplateBuilder.grey} are exclusives. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * class HelloWorld extends HTMLElement { * value = "World" * render() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get().shadow() * ).register() * ``` * * @param focus when true the focus will be delegated to the shadow DOM */ shadow(focus) { this._isGrey = false; this._isShadow = true; this._isFocusDelegation = focus; return this; } /** * Forces the rendering into the Grey DOM. * * {@link TemplateBuilder.shadow} and {@link TemplateBuilder.grey} are exclusives. * {@link TemplateBuilder.preserveContent} and {@link TemplateBuilder.grey} are exclusives. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * class HelloWorld extends HTMLElement { * value = "World" * render() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get().grey() * ).register() * ``` */ grey() { this._isGrey = true; this._isShadow = false; this._isFocusDelegation = undefined; return this; } /** * Prevent mutations on the Custom Element content from rendering processes coming from ancestors. * * {@link TemplateBuilder.preserveContent} and {@link TemplateBuilder.grey} are exclusives. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * class HelloWorld extends HTMLElement { * value = "World" * render() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get().preserveContent() * ).register() * ``` */ preserveContent() { this._preserveContent = true; return this; } /** * Preserve attributes of the Custom Element from rendering processes coming from ancestors. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * class HelloWorld extends HTMLElement { * value = "World" * render() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get().preserveAttributes("class", "id") * ).register() * ``` * * @param names the names of the attribute */ preserveAttributes(...names) { this._preserveAttributes = this._preserveAttributes.concat(names); return this; } /** * Overrides the default render method name. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * class HelloWorld extends HTMLElement { * value = "World" * doRender() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get().method("doRender") * ).register() * ``` * * @param methName the render method name */ method(methName) { this._methName = methName; return this; } /** * Set render parameters. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * import {UpdateParameters} from "@tmorin/ceb-templating-engine" * class HelloWorld extends HTMLElement { * value = "World" * doRender() { * return html`Hello, ${this.value}!` * } * } * ElementBuilder.get().builder( * TemplateBuilder.get<UpdateParameters>() * .parameters({ greyDom: true }) * ).register() * ``` * * @param parameters the parameters */ parameters(parameters) { this._parameters = parameters; return this; } /** * Decorates the render method. * * @example * ```typescript * import {ElementBuilder} from "@tmorin/ceb-elements-core" * import {html} from "@tmorin/ceb-templating-literal" * import {TemplateBuilder} from "@tmorin/ceb-templating-builder" * @ElementBuilder.get<HelloWorld>().decorate() * class HelloWorld extends HTMLElement { * value = "World" * @TemplateBuilder.get().decorate() * render() { * return html`Hello, ${this.value}!` * } * } * ``` */ // eslint-disable-next-line @typescript-eslint/no-unused-vars decorate() { return (target, methName) => { if (!this._methName) { this._methName = methName.toString(); } const id = "template"; ElementBuilder.getOrSet(target, this, id); }; } /** * This API is dedicated for developer of Builders. * @protected */ build(Constructor, hooks) { hooks.before("constructorCallback", (el) => { var _a; if (this._preserveContent) { el[Engine.PROP_NAME_PRESERVE_CHILDREN] = true; } if (this._preserveAttributes.length > 0) { if (!el[Engine.PROP_NAME_PRESERVE_ATTRIBUTES]) { el[Engine.PROP_NAME_PRESERVE_ATTRIBUTES] = []; } el[Engine.PROP_NAME_PRESERVE_ATTRIBUTES] = (_a = el[Engine.PROP_NAME_PRESERVE_ATTRIBUTES]) === null || _a === void 0 ? void 0 : _a.concat(this._preserveAttributes); } // wrap the default render function to render the template in call if (typeof el[this._methName] === "function") { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/ban-types const _original = el[this._methName]; const _isShadow = this._isShadow; const _greyDom = this._isGrey; const _parameters = this._parameters; Object.defineProperty(el, this._methName, { configurable: true, enumerable: true, writable: false, value: function () { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,prefer-rest-params const template = _original.apply(el, arguments); template.render(_isShadow && el.shadowRoot ? el.shadowRoot : el, Object.assign({ greyDom: _greyDom }, _parameters)); return template; }, }); } if (this._isShadow && !el.shadowRoot) { // creates and initializes the shadow root el.attachShadow({ mode: "open", delegatesFocus: this._isFocusDelegation }); } }); hooks.before("connectedCallback", (el) => { if (typeof el[this._methName] === "function") { // eslint-disable-next-line @typescript-eslint/no-unsafe-call el[this._methName](); } }); } } //# sourceMappingURL=builder.js.map