wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
576 lines (575 loc) • 21.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _drawingStatus, _isAttached, _isRendering, _originalVisibility, _pristine, _WJElement_instances, enqueueUpdate_fn, refresh_fn, resolveRender_fn;
import { UniversalService } from "./universal-service.js";
import { Permissions } from "./permissions.js";
import { WjElementUtils } from "./element-utils.js";
import { event } from "./event.js";
import { store, defaultStoreActions } from "./wje-store.js";
const template = document.createElement("template");
template.innerHTML = ``;
const _WJElement = class _WJElement extends HTMLElement {
/**
* Initializes a new instance of the WJElement class.
*/
constructor() {
super();
__privateAdd(this, _WJElement_instances);
__privateAdd(this, _drawingStatus);
__privateAdd(this, _isAttached);
__privateAdd(this, _isRendering);
__privateAdd(this, _originalVisibility);
__privateAdd(this, _pristine);
/**
* Initializes the component, setting up attributes and rendering.
* @param [force] Whether to force initialization.
* @returns A promise that resolves when initialization is complete.
*/
__publicField(this, "initWjElement", (force = false) => {
return new Promise(async (resolve, reject) => {
var _a;
__privateSet(this, _drawingStatus, this.drawingStatuses.BEGINING);
(_a = this.setupAttributes) == null ? void 0 : _a.call(this);
if (this.hasShadowRoot) {
if (!this.shadowRoot) this.attachShadow({ mode: this.shadowType || "open" });
}
this.setUpAccessors();
__privateSet(this, _drawingStatus, this.drawingStatuses.START);
await this.display(force);
const sheet = new CSSStyleSheet();
sheet.replaceSync(this.constructor.cssStyleSheet);
this.context.adoptedStyleSheets = [sheet];
resolve();
});
});
__privateSet(this, _isAttached, false);
this.service = new UniversalService({
store
});
this.defineDependencies();
__privateSet(this, _isRendering, false);
this._dependencies = {};
this.drawingStatuses = {
CREATED: 0,
ATTACHED: 1,
BEGINING: 2,
START: 3,
DRAWING: 4,
DONE: 5,
DISCONNECTED: 6
};
__privateSet(this, _drawingStatus, this.drawingStatuses.CREATED);
__privateSet(this, _pristine, true);
__privateSet(this, _isRendering, false);
this.rafId = null;
__privateSet(this, _originalVisibility, null);
this.params = {};
this.updateComplete = new Promise((resolve, reject) => {
this.finisPromise = resolve;
this.rejectPromise = reject;
});
}
get drawingStatus() {
return __privateGet(this, _drawingStatus);
}
/**
* Sets the value of the 'permission' attribute.
* @param {string[]} value The value to set for the 'permission' attribute.
*/
set permission(value) {
this.setAttribute("permission", value.join(","));
}
/**
* Gets the value of the 'permission-check' attribute.
* @returns {string[]} The value of the 'permission' attribute.
*/
get permission() {
var _a;
return ((_a = this.getAttribute("permission")) == null ? void 0 : _a.split(",")) || [];
}
/**
* Sets the 'permission-check' attribute.
* @param {boolean} value The value to set for the 'permission-check' attribute.
*/
set isPermissionCheck(value) {
if (value) this.setAttribute("permission-check", "");
else this.removeAttribute("permission-check");
}
/**
* Checks if the 'permission-check' attribute is present.
* @returns {boolean} True if the 'permission-check' attribute is present.
*/
get isPermissionCheck() {
return this.hasAttribute("permission-check");
}
set noShow(value) {
if (value) this.setAttribute("no-show", "");
else this.removeAttribute("no-show");
}
/**
* Checks if the 'show' attribute is present.
* @returns {boolean} True if the 'show' attribute is present.
*/
get noShow() {
return this.hasAttribute("no-show");
}
/**
* Sets the 'shadow' attribute.
* @param {string} value The value to set for the 'shadow' attribute.
*/
set isShadowRoot(value) {
return this.setAttribute("shadow", value);
}
get isShadowRoot() {
return this.getAttribute("shadow");
}
/**
* Checks if the 'shadow' attribute is present.
* @returns {boolean} True if the 'shadow' attribute is present.
*/
get hasShadowRoot() {
return this.hasAttribute("shadow");
}
/**
* Gets the value of the 'shadow' attribute or 'open' if not set.
* @returns {string} The value of the 'shadow' attribute or 'open'.
*/
get shadowType() {
return this.getAttribute("shadow") || "open";
}
/**
* Gets the rendering context, either the shadow root or the component itself.
* @returns The rendering context.
*/
get context() {
if (this.hasShadowRoot) {
return this.shadowRoot;
} else {
return this;
}
}
/**
* Gets the store instance.
* @returns {object} The store instance.
*/
get store() {
return store;
}
/**
* @typedef {object} ArrayActions
* @property {Function} addAction - Adds an item to the array.
* @property {Function} deleteAction - Deletes an item from the array.
* @property {Function} loadAction - Loads an array.
* @property {Function} updateAction - Updates an item in the array.
* @property {Function} addManyAction - Adds many items to the array.
*/
/**
* @typedef {object} ObjectActions
* @property {Function} addAction - Replace old object with new object
* @property {Function} deleteAction - Delete item based on key
* @property {Function} updateAction - Update item based on key
*/
/**
* Gets the default store actions.
* @returns The default store actions for arrays and objects.
*/
get defaultStoreActions() {
return defaultStoreActions;
}
/**
* Gets the classes to be removed after the component is connected.
* @returns An array of class names to remove.
*/
get removeClassAfterConnect() {
var _a;
return (_a = this.getAttribute("remove-class-after-connect")) == null ? void 0 : _a.split(" ");
}
/**
* Sets the component dependencies.
* @param value The dependencies to set.
*/
set dependencies(value) {
this._dependencies = value;
}
/**
* Gets the component dependencies.
* @returns The component dependencies.
*/
get dependencies() {
return this._dependencies;
}
/**
* Defines a custom element if not already defined.
* @param name The name of the custom element.
* @param [elementConstructor] The constructor for the custom element.
* @param [options] Additional options for defining the element.
*/
static define(name, elementConstructor = this, options = {}) {
const definedElement = customElements.get(name);
if (!definedElement) {
customElements.define(name, elementConstructor, options);
}
}
/**
* Defines component dependencies by registering custom elements.
*/
defineDependencies() {
if (this.dependencies)
Object.entries(this.dependencies).forEach((name, component) => _WJElement.define(name, component));
}
/**
* Hook for extending behavior before drawing the component.
* @param context The rendering context, usually the element's shadow root or main DOM element.
* @param appStoreObj The global application store for managing state.
* @param params Additional parameters or attributes for rendering the component.
*/
beforeDraw(context, appStoreObj, params) {
}
/**
* Renders the component within the provided context.
* @param context The rendering context, usually the element's shadow root or main DOM element.
* @param appStoreObj
* @param params Additional parameters or attributes for rendering the component.
* @returns This implementation does not render anything and returns `null`.
* @description
* The `draw` method is responsible for rendering the component's content.
* Override this method in subclasses to define custom rendering logic.
* @example
* class MyComponent extends WJElement {
* draw(context, appStoreObj, params) {
* const div = document.createElement('div');
* div.textContent = 'Hello, world!';
* context.appendChild(div);
* }
* }
*/
draw(context, appStoreObj, params) {
return null;
}
/**
* Hook for extending behavior after drawing the component.
* @param context The rendering context, usually the element's shadow root or main DOM element.
* @param appStoreObj The global application store for managing state.
* @param params Additional parameters or attributes for rendering the component.
*/
afterDraw(context, appStoreObj, params) {
}
/**
* Lifecycle method invoked when the component is connected to the DOM.
*/
connectedCallback() {
var _a;
if (!__privateGet(this, _isRendering)) {
__privateSet(this, _originalVisibility, __privateGet(this, _originalVisibility) ?? this.style.visibility);
this.style.visibility = "hidden";
(_a = this.setupAttributes) == null ? void 0 : _a.call(this);
this.setUpAccessors();
__privateSet(this, _drawingStatus, this.drawingStatuses.ATTACHED);
__privateSet(this, _pristine, false);
__privateMethod(this, _WJElement_instances, enqueueUpdate_fn).call(this);
}
}
/**
* Sets up attributes and event listeners for the component.
* This method retrieves all custom events defined for the component
* and adds event listeners for each of them. When an event is triggered,
* it calls the corresponding method on the host element.
*/
setupAttributes() {
let allEvents = WjElementUtils.getEvents(this);
allEvents.forEach((customEvent, domEvent) => {
this.addEventListener(domEvent, (e) => {
var _a, _b;
(_b = (_a = this.getRootNode().host)[customEvent]) == null ? void 0 : _b.call(_a);
});
});
}
/**
* Hook for extending behavior before disconnecting the component.
*/
beforeDisconnect() {
}
/**
* Hook for extending behavior after disconnecting the component.
*/
afterDisconnect() {
}
/**
* Hook for extending behavior before redrawing the component.
*/
beforeRedraw() {
}
/**
* Cleans up resources and event listeners for the component.
*/
componentCleanup() {
}
/**
* Lifecycle method invoked when the component is disconnected from the DOM.
*/
disconnectedCallback() {
var _a, _b;
if (__privateGet(this, _isAttached)) {
(_a = this.beforeDisconnect) == null ? void 0 : _a.call(this);
this.context.innerHTML = "";
(_b = this.afterDisconnect) == null ? void 0 : _b.call(this);
__privateSet(this, _isAttached, false);
this.style.visibility = __privateGet(this, _originalVisibility);
__privateSet(this, _originalVisibility, null);
}
if (__privateGet(this, _isRendering)) {
this.stopRenderLoop();
}
__privateSet(this, _drawingStatus, this.drawingStatuses.DISCONNECTED);
this.componentCleanup();
}
/**
* Lifecycle method invoked when an observed attribute changes.
* @param name The name of the attribute that changed.
* @param old The old value of the attribute.
* @param newName The new value of the attribute.
*/
attributeChangedCallback(name, old, newName) {
if (old !== newName) {
__privateSet(this, _pristine, false);
__privateMethod(this, _WJElement_instances, enqueueUpdate_fn).call(this);
}
}
refresh() {
this.updateComplete = new Promise((resolve, reject) => {
this.finisPromise = resolve;
this.rejectPromise = reject;
});
__privateSet(this, _pristine, false);
__privateMethod(this, _WJElement_instances, enqueueUpdate_fn).call(this);
}
stopRenderLoop() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
/**
* Renders the component within the provided context.
* @param context The rendering context, usually the element's shadow root or main DOM element.
* @param appStore The global application store for managing state.
* @param params Additional parameters or attributes for rendering the component.
* @returns This implementation does not render anything and returns `null`.
* @description
* The `draw` method is responsible for rendering the component's content.
* Override this method in subclasses to define custom rendering logic.
* @example
* class MyComponent extends WJElement {
* draw(context, appStore, params) {
* const div = document.createElement('div');
* div.textContent = 'Hello, world!';
* context.appendChild(div);
* }
* }
*/
draw(context, appStore, params) {
return null;
}
/**
* Displays the component's content, optionally forcing a re-render.
* @param [force] Whether to force a re-render.
* @returns A promise that resolves when the display is complete.
*/
display(force = false) {
this.template = this.constructor.customTemplate || document.createElement("template");
if (force) {
[...this.context.childNodes].forEach(this.context.removeChild.bind(this.context));
}
this.context.append(this.template.content.cloneNode(true));
if (this.noShow || this.isPermissionCheck && !Permissions.isPermissionFulfilled(this.permission)) {
this.remove();
return Promise.resolve();
}
return __privateMethod(this, _WJElement_instances, resolveRender_fn).call(this);
}
/**
* Renders the component's content.
*/
async render() {
__privateSet(this, _drawingStatus, this.drawingStatuses.DRAWING);
let _draw = this.draw(this.context, this.store, WjElementUtils.getAttributes(this));
if (_draw instanceof Promise || (_draw == null ? void 0 : _draw.constructor.name) === "Promise") {
_draw = await _draw;
}
let rend = _draw;
let element;
if (rend instanceof HTMLElement || rend instanceof DocumentFragment) {
element = rend;
} else {
let inputTemplate = document.createElement("template");
inputTemplate.innerHTML = rend;
element = inputTemplate.content.cloneNode(true);
}
let rendered = element;
this.context.appendChild(rendered);
}
/**
* Sanitizes a given name by converting it from kebab-case to camelCase.
* @param {string} name The name in kebab-case format (e.g., "example-name").
* @returns {string} The sanitized name in camelCase format (e.g., "exampleName").
* @example
* sanitizeName('example-name');
* @example
* sanitizeName('my-custom-component');
*/
sanitizeName(name) {
let parts = name.split("-");
return [parts.shift(), ...parts.map((n) => n[0].toUpperCase() + n.slice(1))].join("");
}
/**
* Checks if a property on an object has a getter or setter method defined.
* @param {object} obj The object on which the property is defined.
* @param {string} property The name of the property to check.
* @returns {object} An object indicating the presence of getter and setter methods.
* @property {Function|null} hasGetter The getter function if it exists, otherwise `null`.
* @property {Function|null} hasSetter The setter function if it exists, otherwise `null`.
* @example
* const obj = {
* get name() { return 'value'; },
* set name(val) { console.log(val); }
* };
* checkGetterSetter(obj, 'name');
* @example
* const obj = { prop: 42 };
* checkGetterSetter(obj, 'prop');
*/
checkGetterSetter(obj, property) {
let descriptor = Object.getOwnPropertyDescriptor(obj, property);
if (descriptor) {
return {
hasGetter: typeof descriptor.get === "function" ? descriptor.get : null,
hasSetter: typeof descriptor.set === "function" ? descriptor.set : null
};
}
let proto = Object.getPrototypeOf(obj);
if (proto) {
return this.checkGetterSetter(proto, property);
}
return { hasGetter: null, hasSetter: null };
}
/**
* Sets up property accessors for the component's attributes.
*/
setUpAccessors() {
let attrs = this.getAttributeNames();
attrs.forEach((name) => {
const sanitizedName = this.sanitizeName(name);
const { hasGetter, hasSetter } = this.checkGetterSetter(this, sanitizedName);
Object.defineProperty(this, sanitizedName, {
set: hasSetter ?? ((value) => this.setAttribute(name, value)),
get: hasGetter ?? (() => this.getAttribute(name))
});
});
}
};
_drawingStatus = new WeakMap();
_isAttached = new WeakMap();
_isRendering = new WeakMap();
_originalVisibility = new WeakMap();
_pristine = new WeakMap();
_WJElement_instances = new WeakSet();
/**
* Enqueues an update for the component.
* This method processes the current render promise and then refreshes the component.
*/
enqueueUpdate_fn = function() {
if (!__privateGet(this, _isRendering)) {
this.rafId = requestAnimationFrame(() => __privateMethod(this, _WJElement_instances, refresh_fn).call(this));
}
};
refresh_fn = async function() {
var _a, _b, _c;
if (__privateGet(this, _isRendering)) {
this.rafId = requestAnimationFrame(() => __privateMethod(this, _WJElement_instances, refresh_fn).call(this));
return;
}
if (!__privateGet(this, _pristine)) {
__privateSet(this, _pristine, true);
__privateSet(this, _isRendering, true);
if (__privateGet(this, _isAttached)) {
(_a = this.beforeRedraw) == null ? void 0 : _a.call(this);
(_b = this.beforeDisconnect) == null ? void 0 : _b.call(this);
(_c = this.afterDisconnect) == null ? void 0 : _c.call(this);
} else {
this.stopRenderLoop();
}
try {
await this.initWjElement(true);
} catch (error) {
console.error("Render error:", error);
} finally {
__privateSet(this, _isRendering, false);
if (!__privateGet(this, _pristine)) {
__privateSet(this, _pristine, false);
__privateMethod(this, _WJElement_instances, enqueueUpdate_fn).call(this);
} else {
this.finisPromise();
this.style.visibility = __privateGet(this, _originalVisibility);
}
}
}
};
/**
* Resolves the rendering process of the component.
* @returns A promise that resolves when rendering is complete.
* @private
*/
resolveRender_fn = function() {
this.params = WjElementUtils.getAttributes(this);
return new Promise(async (resolve, reject) => {
var _a;
const __beforeDraw = this.beforeDraw(this.context, this.store, WjElementUtils.getAttributes(this));
if (__beforeDraw instanceof Promise || (__beforeDraw == null ? void 0 : __beforeDraw.constructor.name) === "Promise") {
await __beforeDraw;
}
await this.render();
const __afterDraw = (_a = this.afterDraw) == null ? void 0 : _a.call(this, this.context, this.store, WjElementUtils.getAttributes(this));
if (__afterDraw instanceof Promise || (__afterDraw == null ? void 0 : __afterDraw.constructor.name) === "Promise") {
await __afterDraw;
}
__privateSet(this, _isRendering, false);
__privateSet(this, _isAttached, true);
if (this.removeClassAfterConnect) {
this.classList.remove(...this.removeClassAfterConnect);
}
__privateSet(this, _drawingStatus, this.drawingStatuses.DONE);
resolve();
}).catch((e) => {
console.log(e);
});
};
/**
* Processes and combines two templates into one.
* @param pTemplate The primary template.
* @param inputTemplate The secondary template.
* @returns The combined template.
*/
__publicField(_WJElement, "processTemplates", (pTemplate, inputTemplate) => {
const newTemplate = document.createElement("template");
newTemplate.innerHTML = [inputTemplate.innerHTML, (pTemplate == null ? void 0 : pTemplate.innerHTML) || ""].join("");
return newTemplate;
});
let WJElement = _WJElement;
let __esModule = "true";
export {
Permissions,
WjElementUtils,
__esModule,
WJElement as default,
event
};
//# sourceMappingURL=wje-element.js.map