@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.
292 lines • 11.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemplateBuilder = void 0;
const ceb_elements_core_1 = require("@tmorin/ceb-elements-core");
const ceb_templating_engine_1 = require("@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.
*/
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";
ceb_elements_core_1.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[ceb_templating_engine_1.Engine.PROP_NAME_PRESERVE_CHILDREN] = true;
}
if (this._preserveAttributes.length > 0) {
if (!el[ceb_templating_engine_1.Engine.PROP_NAME_PRESERVE_ATTRIBUTES]) {
el[ceb_templating_engine_1.Engine.PROP_NAME_PRESERVE_ATTRIBUTES] = [];
}
el[ceb_templating_engine_1.Engine.PROP_NAME_PRESERVE_ATTRIBUTES] = (_a = el[ceb_templating_engine_1.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]();
}
});
}
}
exports.TemplateBuilder = TemplateBuilder;
//# sourceMappingURL=builder.js.map