@elenajs/core 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sources":["../src/common/props.js","../src/common/events.js","../src/common/utils.js","../src/common/render.js","../src/elena.js"],"sourcesContent":["/**\n * Get the value of the Elena Element property.\n *\n * @param {string} type\n * @param {any} value\n * @param {\"toAttribute\" | \"toProp\"} [transform]\n */\nexport function getPropValue(type, value, transform) {\n value = type === \"boolean\" && typeof value !== \"boolean\" ? value !== null : value;\n\n if (!transform) {\n return value;\n } else if (transform === \"toAttribute\") {\n switch (type) {\n case \"object\":\n case \"array\":\n return value === null ? null : JSON.stringify(value);\n case \"boolean\":\n return value ? \"\" : null;\n case \"number\":\n return value === null ? null : value;\n default:\n return value === \"\" ? null : value;\n }\n } else {\n switch (type) {\n case \"object\":\n case \"array\":\n return value && JSON.parse(value);\n case \"boolean\":\n return value; // conversion already handled above\n case \"number\":\n return value !== null ? +value : value;\n default:\n return value;\n }\n }\n}\n\n/**\n * Set or remove an attribute on an Elena Element.\n *\n * @param {Element} element - Target element\n * @param {string} name - Attribute name\n * @param {string | null} value - Attribute value, or null to remove\n */\nexport function syncAttribute(element, name, value) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot sync attributes to a null element.\");\n return;\n }\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n}\n\n/**\n * Define prop getters/setters on the prototype once\n * at class-creation time. Values are stored per-instance\n * via a `_props` Map that is lazily created.\n *\n * @param {Function} proto - The class prototype\n * @param {string[]} properties - Prop names to define\n * @param {boolean} [syncToElement=true] - Whether to also sync prop attributes to the\n * inner element ref. Pass false for wrapper components that have no explicit inner\n * element selector, so props are not leaked onto slotted children.\n */\nexport function setProps(proto, properties, syncToElement = true) {\n for (const prop of properties) {\n Object.defineProperty(proto, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return this._props ? this._props.get(prop) : undefined;\n },\n set(value) {\n if (!this._props) {\n this._props = new Map();\n }\n if (value === this._props.get(prop)) {\n return;\n }\n\n this._props.set(prop, value);\n if (!this.isConnected) {\n return;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (syncToElement && this.element) {\n syncAttribute(this.element, prop, attrValue);\n }\n },\n });\n }\n}\n\n/**\n * We need to update the internals of the Elena Element\n * when props on the host element are changed.\n *\n * @param {object} context\n * @param {string} name\n * @param {any} oldValue\n * @param {any} newValue\n */\nexport function getProps(context, name, oldValue, newValue) {\n if (oldValue !== newValue) {\n const type = typeof context[name];\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\n }\n}\n","/**\n * A base class for Elena Element’s events which\n * defaults to bubbling and composed.\n */\nexport class ElenaEvent extends Event {\n constructor(type, eventInitDict) {\n super(type, {\n bubbles: true,\n composed: true,\n ...eventInitDict,\n });\n }\n}\n","/**\n * Register the Elena Element if the browser supports it.\n *\n * @param {string} tagName\n * @param {Function} Element\n */\nexport function defineElement(tagName, Element) {\n if (typeof window !== \"undefined\" && \"customElements\" in window) {\n if (!window.customElements.get(tagName)) {\n window.customElements.define(tagName, Element);\n }\n }\n}\n\n/**\n * Escape a string for safe insertion into HTML.\n *\n * @param {string} str\n * @returns {string}\n */\nexport function escapeHtml(str) {\n const Escape = { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" };\n return String(str).replace(/[&<>\"']/g, c => Escape[c]);\n}\n\n/**\n * Tagged template for trusted HTML fragments with auto-escaped interpolations.\n * Use inside `this.template` for conditional HTML blocks.\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function html(strings, ...values) {\n const result = strings.reduce(\n (acc, str, i) => acc + str + escapeHtml(String(values[i] ?? \"\")),\n \"\"\n );\n return { __raw: true, toString: () => result };\n}\n","import { escapeHtml } from \"./utils.js\";\n\n/**\n * Render a tagged template into an Elena Element with DOM diffing.\n *\n * On first render, builds the full HTML markup and render.\n * On re-renders, patches only the text nodes whose values changed,\n * avoiding a full DOM rebuild.\n *\n * Cache state is stored on the element instance:\n * - _tplStrings: reference to the template’s static strings array\n * - _tplValues: array of escaped values from the last render\n * - _tplParts: array mapping each value index to its DOM text node\n *\n * @param {HTMLElement} element\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n */\nexport function renderTemplate(element, strings, values) {\n if (patchTextNodes(element, strings, values)) {\n return;\n }\n fullRender(element, strings, values);\n}\n\n/**\n * Fast path: patch only the text nodes whose values changed.\n * Returns true if all changes were handled (no full render needed).\n *\n * @param {HTMLElement} element - The host element with cached template state\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n * @returns {boolean} Whether patching was sufficient (false = full render)\n */\nfunction patchTextNodes(element, strings, values) {\n // Only works when re-rendering the same template shape\n if (element._tplStrings !== strings || !element._tplParts) {\n return false;\n }\n\n let needsFullRender = false;\n\n for (let i = 0; i < values.length; i++) {\n const v = values[i];\n // Check if this value is a trusted HTML fragment (isRaw),\n // reated via the `html` tag, which bypasses escaping\n const isRaw = v && v.__raw;\n const newRendered = isRaw ? String(v) : escapeHtml(String(v ?? \"\"));\n const oldRendered = element._tplValues[i];\n\n if (newRendered === oldRendered) {\n continue;\n }\n\n element._tplValues[i] = newRendered;\n\n // Raw HTML values or attribute-position values require a full render\n if (isRaw) {\n needsFullRender = true;\n } else {\n const textNode = element._tplParts[i];\n if (textNode) {\n // Value is in a text position, update the DOM node directly\n textNode.textContent = String(v ?? \"\");\n } else {\n // Value is in an attribute position, can’t patch, need full render\n needsFullRender = true;\n }\n }\n }\n\n return !needsFullRender;\n}\n\n/**\n * Cold path: build full HTML markup, render it via DocumentFragment,\n * and map each interpolated value to its corresponding DOM text node\n * for future fast-path patching.\n *\n * @param {HTMLElement} element - The host element to render into\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n */\nfunction fullRender(element, strings, values) {\n const renderedValues = values.map(v => (v && v.__raw ? String(v) : escapeHtml(String(v ?? \"\"))));\n\n // Build the complete HTML string, stripping template indentation.\n // 1. Replace newlines + indentation with a single space\n // 2. Clean up, remove whitespace between/around tags and trim\n const markup = strings\n .reduce((out, str, i) => out + str.replace(/\\n\\s*/g, \" \") + (renderedValues[i] ?? \"\"), \"\")\n .replace(/>\\s+</g, \"><\")\n .replace(/>\\s+/g, \">\")\n .replace(/\\s+</g, \"<\")\n .trim();\n\n renderHtml(element, markup);\n\n // Cache template identity and rendered values\n element._tplStrings = strings;\n element._tplValues = renderedValues;\n\n // Walk text nodes to map each value index to its DOM node\n element._tplParts = mapTextNodes(element, renderedValues);\n}\n\n/**\n * Walk the Elena Element’s text nodes and map each escaped value\n * to its corresponding DOM text node. Values in attribute\n * positions won't have a matching text node and will be null.\n *\n * @param {HTMLElement} element - The host element to walk\n * @param {string[]} escapedValues - HTML-escaped interpolated values\n * @returns {Array<Text | undefined>} Array mapping each value index to its text node\n */\nfunction mapTextNodes(element, escapedValues) {\n const parts = new Array(escapedValues.length);\n const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);\n\n let valueIndex = 0;\n let node;\n\n while ((node = walker.nextNode()) && valueIndex < escapedValues.length) {\n if (node.textContent === escapedValues[valueIndex]) {\n parts[valueIndex] = node;\n valueIndex++;\n }\n }\n\n return parts;\n}\n\n/**\n * Render an HTML string into an element by parsing it into a\n * DocumentFragment via createContextualFragment and swapping\n * the element’s children in a single replaceChildren call.\n *\n * Uses element.ownerDocument.createRange() so that elements in non-default\n * documents (e.g. iframes) parse HTML in the correct context.\n *\n * @param {HTMLElement} element\n * @param {string} markup\n */\nexport function renderHtml(element, markup) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot render to a null element.\");\n return;\n }\n element.replaceChildren(element.ownerDocument.createRange().createContextualFragment(markup));\n}\n","/**\n * ██████████ ████\n * ░░███░░░░░█░░███\n * ░███ █ ░ ░███ ██████ ████████ ██████\n * ░██████ ░███ ███░░███░░███░░███ ░░░░░███\n * ░███░░█ ░███ ░███████ ░███ ░███ ███████\n * ░███ ░ █ ░███ ░███░░░ ░███ ░███ ███░░███\n * ██████████ █████░░██████ ████ █████░░████████\n * ░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░\n *\n * Elena: Progressive Web Components\n * https://elenajs.com\n */\n\nimport { setProps, getProps, getPropValue, syncAttribute } from \"./common/props.js\";\nimport { ElenaEvent } from \"./common/events.js\";\nimport { defineElement, html } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {string[]} [props] - Props observed and synced as attributes.\n * @property {string[]} [events] - Events to delegate from the inner element.\n * @property {string} [element] - CSS selector for the inner element.\n */\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * Factory that creates Elena mixin class.\n *\n * Wraps `superClass` with Elena’s lifecycle, templating, props,\n * and events features. The `options` argument is optional.\n *\n * @param {ElenaConstructor} superClass - Base class to extend.\n * @param {ElenaOptions} [options] - Optional configuration options.\n * @returns {CustomElementConstructor} A class ready to be registered.\n */\nexport function Elena(superClass, options) {\n /**\n * Pre-compile element resolver once at class definition\n * time to improve performance:\n *\n * 1. no selector: firstElementChild (property access)\n * 2. className: getElementsByClassName (skips full selector parser)\n * 3. any other: querySelector (full parser, only when needed)\n */\n const resolveElement = !(options && options.element)\n ? host => host.firstElementChild\n : /^[a-z][a-z0-9-]*$/i.test(options.element)\n ? host => host.getElementsByClassName(options.element)[0]\n : host => host.querySelector(options.element);\n\n /**\n * Set up the initial state and default values for Elena Element.\n */\n class ElenaElement extends superClass {\n /**\n * Reference to the base element in the provided template.\n *\n * @type {Object}\n */\n element = null;\n\n /**\n * This method is called when the Elena Element’s\n * props are changed, added, removed or replaced.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n getProps(this, prop, oldValue, newValue);\n\n // Re-render when attributes change (after initial render).\n // Guard against re-entrant renders: if render() itself mutates an observed\n // attribute, skip the recursive call to prevent an infinite loop.\n // Use _hydrated (set at end of connectedCallback) rather than this.element\n // so wrapper components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this.render();\n this._isRendering = false;\n }\n }\n\n /**\n * This static method returns an array containing the\n * names of the props we want to observe.\n */\n static get observedAttributes() {\n return options && options.props ? options.props : [];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Render a tagged template literal with auto-escaping and DOM diffing.\n * Usage: this.template`<span>${this.label}</span>`\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n */\n template(strings, ...values) {\n renderTemplate(this, strings, values);\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this.render();\n\n if (!this.element) {\n this.element = resolveElement(this);\n\n if (!this.element) {\n // Only warn when an explicit element selector was provided but didn't match.\n // Wrapper components (no element option) intentionally have no inner ref.\n if (options && options.element) {\n console.warn(\"░█ [ELENA]: No element found, using firstElementChild as fallback.\");\n }\n this.element = this.firstElementChild;\n }\n }\n\n // Flush props set before connection to host and inner element attributes\n if (this._props) {\n const hasElementSelector = !!(options && options.element);\n for (const [prop, value] of this._props) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (hasElementSelector) {\n syncAttribute(this.element, prop, attrValue);\n }\n }\n }\n\n if (!this._events && options && options.events) {\n this._events = true;\n options.events?.forEach(e => {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n });\n }\n\n this.updated();\n }\n\n /**\n * Perform post-update after each render().\n *\n * @internal\n */\n updated() {\n if (!this._hydrated) {\n this._hydrated = true;\n this.classList.add(\"elena-hydrated\");\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is removed from the document.\n */\n disconnectedCallback() {\n if (this._events) {\n this._events = false;\n options.events?.forEach(e => {\n this.element?.removeEventListener(e, this);\n });\n }\n }\n\n /**\n * Handles events on the Elena Element.\n *\n * @internal\n */\n handleEvent(event) {\n if (options.events?.includes(event.type)) {\n event.stopPropagation();\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n }\n\n if (options && options.props?.length) {\n setProps(ElenaElement.prototype, options.props, !!(options && options.element));\n }\n\n if (options && options.tagName) {\n /** @type {string} */\n ElenaElement._tagName = options.tagName;\n }\n\n /**\n * Register this class as a custom element using the `tagName`\n * set in options. Must be called on the final subclass, not\n * on the Elena mixin directly.\n *\n * @this {CustomElementConstructor}\n */\n ElenaElement.define = function () {\n if (this._tagName) {\n defineElement(this._tagName, this);\n }\n };\n\n return ElenaElement;\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","syncAttribute","element","name","removeAttribute","setAttribute","console","warn","ElenaEvent","Event","constructor","eventInitDict","super","bubbles","composed","escapeHtml","str","Escape","String","replace","c","html","strings","values","result","reduce","acc","i","__raw","toString","renderTemplate","_tplStrings","_tplParts","needsFullRender","length","v","isRaw","newRendered","_tplValues","textNode","textContent","patchTextNodes","renderedValues","map","markup","out","trim","replaceChildren","ownerDocument","createRange","createContextualFragment","renderHtml","escapedValues","parts","Array","walker","document","createTreeWalker","NodeFilter","SHOW_TEXT","node","valueIndex","nextNode","mapTextNodes","fullRender","Elena","superClass","options","resolveElement","test","host","getElementsByClassName","querySelector","firstElementChild","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","context","newAttr","getProps","this","_hydrated","_isRendering","render","observedAttributes","props","template","connectedCallback","_props","hasElementSelector","attrValue","_events","events","forEach","e","addEventListener","args","updated","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","stopPropagation","dispatchEvent","cancelable","proto","properties","syncToElement","Object","defineProperty","configurable","enumerable","get","undefined","set","Map","isConnected","setProps","prototype","tagName","_tagName","define","Element","window","customElements"],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","sources":["../src/common/props.js","../src/common/events.js","../src/common/utils.js","../src/common/render.js","../src/elena.js"],"sourcesContent":["/**\n * Get the value of the Elena Element property.\n *\n * @param {string} type\n * @param {any} value\n * @param {\"toAttribute\" | \"toProp\"} [transform]\n */\nexport function getPropValue(type, value, transform) {\n value = type === \"boolean\" && typeof value !== \"boolean\" ? value !== null : value;\n\n if (!transform) {\n return value;\n } else if (transform === \"toAttribute\") {\n switch (type) {\n case \"object\":\n case \"array\":\n return value === null ? null : JSON.stringify(value);\n case \"boolean\":\n return value ? \"\" : null;\n case \"number\":\n return value === null ? null : value;\n default:\n return value === \"\" ? null : value;\n }\n } else {\n switch (type) {\n case \"object\":\n case \"array\":\n return value && JSON.parse(value);\n case \"boolean\":\n return value; // conversion already handled above\n case \"number\":\n return value !== null ? +value : value;\n default:\n return value;\n }\n }\n}\n\n/**\n * Set or remove an attribute on an Elena Element.\n *\n * @param {Element} element - Target element\n * @param {string} name - Attribute name\n * @param {string | null} value - Attribute value, or null to remove\n */\nexport function syncAttribute(element, name, value) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot sync attributes to a null element.\");\n return;\n }\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n}\n\n/**\n * Define prop getters/setters on the prototype once\n * at class-creation time. Values are stored per-instance\n * via a `_props` Map that is lazily created.\n *\n * @param {Function} proto - The class prototype\n * @param {string[]} properties - Prop names to define\n * @param {boolean} [syncToElement=true] - Whether to also sync prop attributes to the\n * inner element ref. Pass false for slot components that have no explicit inner\n * element selector, so props are not leaked onto slotted children.\n */\nexport function setProps(proto, properties, syncToElement = true) {\n for (const prop of properties) {\n Object.defineProperty(proto, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return this._props ? this._props.get(prop) : undefined;\n },\n set(value) {\n if (!this._props) {\n this._props = new Map();\n }\n if (value === this._props.get(prop)) {\n return;\n }\n\n this._props.set(prop, value);\n if (!this.isConnected) {\n return;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (syncToElement && this.element) {\n syncAttribute(this.element, prop, attrValue);\n }\n },\n });\n }\n}\n\n/**\n * We need to update the internals of the Elena Element\n * when props on the host element are changed.\n *\n * @param {object} context\n * @param {string} name\n * @param {any} oldValue\n * @param {any} newValue\n */\nexport function getProps(context, name, oldValue, newValue) {\n if (oldValue !== newValue) {\n const type = typeof context[name];\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\n }\n}\n","/**\n * A base class for Elena Element’s events which\n * defaults to bubbling and composed.\n */\nexport class ElenaEvent extends Event {\n constructor(type, eventInitDict) {\n super(type, {\n bubbles: true,\n composed: true,\n ...eventInitDict,\n });\n }\n}\n","/**\n * Register the Elena Element if the browser supports it.\n *\n * @param {string} tagName\n * @param {Function} Element\n */\nexport function defineElement(tagName, Element) {\n if (typeof window !== \"undefined\" && \"customElements\" in window) {\n if (!window.customElements.get(tagName)) {\n window.customElements.define(tagName, Element);\n }\n }\n}\n\n/**\n * Escape a string for safe insertion into HTML.\n *\n * @param {string} str\n * @returns {string}\n */\nexport function escapeHtml(str) {\n const Escape = { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" };\n return String(str).replace(/[&<>\"']/g, c => Escape[c]);\n}\n\n/**\n * Tagged template for trusted HTML fragments with auto-escaped interpolations.\n * Use inside `this.template` for conditional HTML blocks.\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function html(strings, ...values) {\n const result = strings.reduce(\n (acc, str, i) => acc + str + escapeHtml(String(values[i] ?? \"\")),\n \"\"\n );\n return { __raw: true, toString: () => result };\n}\n","import { escapeHtml } from \"./utils.js\";\n\n/**\n * Render a tagged template into an Elena Element with DOM diffing.\n *\n * On first render, builds the full HTML markup and render.\n * On re-renders, patches only the text nodes whose values changed,\n * avoiding a full DOM rebuild.\n *\n * Cache state is stored on the element instance:\n * - _tplStrings: reference to the template’s static strings array\n * - _tplValues: array of escaped values from the last render\n * - _tplParts: array mapping each value index to its DOM text node\n *\n * @param {HTMLElement} element\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n */\nexport function renderTemplate(element, strings, values) {\n if (patchTextNodes(element, strings, values)) {\n return;\n }\n fullRender(element, strings, values);\n}\n\n/**\n * Fast path: patch only the text nodes whose values changed.\n * Returns true if all changes were handled (no full render needed).\n *\n * @param {HTMLElement} element - The host element with cached template state\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n * @returns {boolean} Whether patching was sufficient (false = full render)\n */\nfunction patchTextNodes(element, strings, values) {\n // Only works when re-rendering the same template shape\n if (element._tplStrings !== strings || !element._tplParts) {\n return false;\n }\n\n let needsFullRender = false;\n\n for (let i = 0; i < values.length; i++) {\n const v = values[i];\n // Check if this value is a trusted HTML fragment (isRaw),\n // reated via the `html` tag, which bypasses escaping\n const isRaw = v && v.__raw;\n const newRendered = isRaw ? String(v) : escapeHtml(String(v ?? \"\"));\n const oldRendered = element._tplValues[i];\n\n if (newRendered === oldRendered) {\n continue;\n }\n\n element._tplValues[i] = newRendered;\n\n // Raw HTML values or attribute-position values require a full render\n if (isRaw) {\n needsFullRender = true;\n } else {\n const textNode = element._tplParts[i];\n if (textNode) {\n // Value is in a text position, update the DOM node directly\n textNode.textContent = String(v ?? \"\");\n } else {\n // Value is in an attribute position, can’t patch, need full render\n needsFullRender = true;\n }\n }\n }\n\n return !needsFullRender;\n}\n\n/**\n * Cold path: build full HTML markup, render it via DocumentFragment,\n * and map each interpolated value to its corresponding DOM text node\n * for future fast-path patching.\n *\n * @param {HTMLElement} element - The host element to render into\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n */\nfunction fullRender(element, strings, values) {\n const renderedValues = values.map(v => (v && v.__raw ? String(v) : escapeHtml(String(v ?? \"\"))));\n\n // Build the complete HTML string, stripping template indentation.\n // 1. Replace newlines + indentation with a single space\n // 2. Clean up, remove whitespace between/around tags and trim\n const markup = strings\n .reduce((out, str, i) => out + str.replace(/\\n\\s*/g, \" \") + (renderedValues[i] ?? \"\"), \"\")\n .replace(/>\\s+</g, \"><\")\n .replace(/>\\s+/g, \">\")\n .replace(/\\s+</g, \"<\")\n .trim();\n\n renderHtml(element, markup);\n\n // Cache template identity and rendered values\n element._tplStrings = strings;\n element._tplValues = renderedValues;\n\n // Walk text nodes to map each value index to its DOM node\n element._tplParts = mapTextNodes(element, renderedValues);\n}\n\n/**\n * Walk the Elena Element’s text nodes and map each escaped value\n * to its corresponding DOM text node. Values in attribute\n * positions won't have a matching text node and will be null.\n *\n * @param {HTMLElement} element - The host element to walk\n * @param {string[]} escapedValues - HTML-escaped interpolated values\n * @returns {Array<Text | undefined>} Array mapping each value index to its text node\n */\nfunction mapTextNodes(element, escapedValues) {\n const parts = new Array(escapedValues.length);\n const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);\n\n let valueIndex = 0;\n let node;\n\n while ((node = walker.nextNode()) && valueIndex < escapedValues.length) {\n if (node.textContent === escapedValues[valueIndex]) {\n parts[valueIndex] = node;\n valueIndex++;\n }\n }\n\n return parts;\n}\n\n/**\n * Render an HTML string into an element by parsing it into a\n * DocumentFragment via createContextualFragment and swapping\n * the element’s children in a single replaceChildren call.\n *\n * Uses element.ownerDocument.createRange() so that elements in non-default\n * documents (e.g. iframes) parse HTML in the correct context.\n *\n * @param {HTMLElement} element\n * @param {string} markup\n */\nexport function renderHtml(element, markup) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot render to a null element.\");\n return;\n }\n element.replaceChildren(element.ownerDocument.createRange().createContextualFragment(markup));\n}\n","/**\n * ██████████ ████\n * ░░███░░░░░█░░███\n * ░███ █ ░ ░███ ██████ ████████ ██████\n * ░██████ ░███ ███░░███░░███░░███ ░░░░░███\n * ░███░░█ ░███ ░███████ ░███ ░███ ███████\n * ░███ ░ █ ░███ ░███░░░ ░███ ░███ ███░░███\n * ██████████ █████░░██████ ████ █████░░████████\n * ░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░\n *\n * Elena: Progressive Web Components\n * https://elenajs.com\n */\n\nimport { setProps, getProps, getPropValue, syncAttribute } from \"./common/props.js\";\nimport { ElenaEvent } from \"./common/events.js\";\nimport { defineElement, html } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {string[]} [props] - Props observed and synced as attributes.\n * @property {string[]} [events] - Events to delegate from the inner element.\n * @property {string} [element] - CSS selector for the inner element.\n */\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * Factory that creates Elena mixin class.\n *\n * Wraps `superClass` with Elena’s lifecycle, templating, props,\n * and events features. The `options` argument is optional.\n *\n * @param {ElenaConstructor} superClass - Base class to extend.\n * @param {ElenaOptions} [options] - Optional configuration options.\n * @returns {CustomElementConstructor} A class ready to be registered.\n */\nexport function Elena(superClass, options) {\n /**\n * Pre-compile element resolver once at class definition\n * time to improve performance:\n *\n * 1. no selector: firstElementChild (property access)\n * 2. className: getElementsByClassName (skips full selector parser)\n * 3. any other: querySelector (full parser, only when needed)\n */\n const resolveElement = !(options && options.element)\n ? host => host.firstElementChild\n : /^[a-z][a-z0-9-]*$/i.test(options.element)\n ? host => host.getElementsByClassName(options.element)[0]\n : host => host.querySelector(options.element);\n\n /**\n * Set up the initial state and default values for Elena Element.\n */\n class ElenaElement extends superClass {\n /**\n * Reference to the base element in the provided template.\n *\n * @type {Object}\n */\n element = null;\n\n /**\n * This method is called when the Elena Element’s\n * props are changed, added, removed or replaced.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n getProps(this, prop, oldValue, newValue);\n\n // Re-render when attributes change (after initial render).\n // Guard against re-entrant renders: if render() itself mutates an observed\n // attribute, skip the recursive call to prevent an infinite loop.\n // Use _hydrated (set at end of connectedCallback) rather than this.element\n // so slot components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this.render();\n this._isRendering = false;\n }\n }\n\n /**\n * This static method returns an array containing the\n * names of the props we want to observe.\n */\n static get observedAttributes() {\n return options && options.props ? options.props : [];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Render a tagged template literal with auto-escaping and DOM diffing.\n * Usage: this.template`<span>${this.label}</span>`\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n */\n template(strings, ...values) {\n renderTemplate(this, strings, values);\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this.render();\n\n if (!this.element) {\n this.element = resolveElement(this);\n\n if (!this.element) {\n // Only warn when an explicit element selector was provided but didn't match.\n // Slot components (no element option) intentionally have no inner ref.\n if (options && options.element) {\n console.warn(\"░█ [ELENA]: No element found, using firstElementChild as fallback.\");\n }\n this.element = this.firstElementChild;\n }\n }\n\n // Flush props set before connection to host and inner element attributes\n if (this._props) {\n const hasElementSelector = !!(options && options.element);\n for (const [prop, value] of this._props) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (hasElementSelector) {\n syncAttribute(this.element, prop, attrValue);\n }\n }\n }\n\n if (!this._events && options && options.events) {\n this._events = true;\n options.events?.forEach(e => {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n });\n }\n\n this.updated();\n }\n\n /**\n * Perform post-update after each render().\n *\n * @internal\n */\n updated() {\n if (!this._hydrated) {\n this._hydrated = true;\n this.classList.add(\"elena-hydrated\");\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is removed from the document.\n */\n disconnectedCallback() {\n if (this._events) {\n this._events = false;\n options.events?.forEach(e => {\n this.element?.removeEventListener(e, this);\n });\n }\n }\n\n /**\n * Handles events on the Elena Element.\n *\n * @internal\n */\n handleEvent(event) {\n if (options.events?.includes(event.type)) {\n event.stopPropagation();\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n }\n\n if (options && options.props?.length) {\n setProps(ElenaElement.prototype, options.props, !!(options && options.element));\n }\n\n if (options && options.tagName) {\n /** @type {string} */\n ElenaElement._tagName = options.tagName;\n }\n\n /**\n * Register this class as a custom element using the `tagName`\n * set in options. Must be called on the final subclass, not\n * on the Elena mixin directly.\n *\n * @this {CustomElementConstructor}\n */\n ElenaElement.define = function () {\n if (this._tagName) {\n defineElement(this._tagName, this);\n }\n };\n\n return ElenaElement;\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","syncAttribute","element","name","removeAttribute","setAttribute","console","warn","ElenaEvent","Event","constructor","eventInitDict","super","bubbles","composed","escapeHtml","str","Escape","String","replace","c","html","strings","values","result","reduce","acc","i","__raw","toString","renderTemplate","_tplStrings","_tplParts","needsFullRender","length","v","isRaw","newRendered","_tplValues","textNode","textContent","patchTextNodes","renderedValues","map","markup","out","trim","replaceChildren","ownerDocument","createRange","createContextualFragment","renderHtml","escapedValues","parts","Array","walker","document","createTreeWalker","NodeFilter","SHOW_TEXT","node","valueIndex","nextNode","mapTextNodes","fullRender","Elena","superClass","options","resolveElement","test","host","getElementsByClassName","querySelector","firstElementChild","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","context","newAttr","getProps","this","_hydrated","_isRendering","render","observedAttributes","props","template","connectedCallback","_props","hasElementSelector","attrValue","_events","events","forEach","e","addEventListener","args","updated","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","stopPropagation","dispatchEvent","cancelable","proto","properties","syncToElement","Object","defineProperty","configurable","enumerable","get","undefined","set","Map","isConnected","setProps","prototype","tagName","_tagName","define","Element","window","customElements"],"mappings":"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"}
@@ -22,7 +22,7 @@ export function syncAttribute(element: Element, name: string, value: string | nu
22
22
  * @param {Function} proto - The class prototype
23
23
  * @param {string[]} properties - Prop names to define
24
24
  * @param {boolean} [syncToElement=true] - Whether to also sync prop attributes to the
25
- * inner element ref. Pass false for wrapper components that have no explicit inner
25
+ * inner element ref. Pass false for slot components that have no explicit inner
26
26
  * element selector, so props are not leaked onto slotted children.
27
27
  */
28
28
  export function setProps(proto: Function, properties: string[], syncToElement?: boolean): void;
package/dist/elena.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"elena.js","sources":["../src/elena.js"],"sourcesContent":["/**\n * ██████████ ████\n * ░░███░░░░░█░░███\n * ░███ █ ░ ░███ ██████ ████████ ██████\n * ░██████ ░███ ███░░███░░███░░███ ░░░░░███\n * ░███░░█ ░███ ░███████ ░███ ░███ ███████\n * ░███ ░ █ ░███ ░███░░░ ░███ ░███ ███░░███\n * ██████████ █████░░██████ ████ █████░░████████\n * ░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░\n *\n * Elena: Progressive Web Components\n * https://elenajs.com\n */\n\nimport { setProps, getProps, getPropValue, syncAttribute } from \"./common/props.js\";\nimport { ElenaEvent } from \"./common/events.js\";\nimport { defineElement, html } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {string[]} [props] - Props observed and synced as attributes.\n * @property {string[]} [events] - Events to delegate from the inner element.\n * @property {string} [element] - CSS selector for the inner element.\n */\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * Factory that creates Elena mixin class.\n *\n * Wraps `superClass` with Elena’s lifecycle, templating, props,\n * and events features. The `options` argument is optional.\n *\n * @param {ElenaConstructor} superClass - Base class to extend.\n * @param {ElenaOptions} [options] - Optional configuration options.\n * @returns {CustomElementConstructor} A class ready to be registered.\n */\nexport function Elena(superClass, options) {\n /**\n * Pre-compile element resolver once at class definition\n * time to improve performance:\n *\n * 1. no selector: firstElementChild (property access)\n * 2. className: getElementsByClassName (skips full selector parser)\n * 3. any other: querySelector (full parser, only when needed)\n */\n const resolveElement = !(options && options.element)\n ? host => host.firstElementChild\n : /^[a-z][a-z0-9-]*$/i.test(options.element)\n ? host => host.getElementsByClassName(options.element)[0]\n : host => host.querySelector(options.element);\n\n /**\n * Set up the initial state and default values for Elena Element.\n */\n class ElenaElement extends superClass {\n /**\n * Reference to the base element in the provided template.\n *\n * @type {Object}\n */\n element = null;\n\n /**\n * This method is called when the Elena Element’s\n * props are changed, added, removed or replaced.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n getProps(this, prop, oldValue, newValue);\n\n // Re-render when attributes change (after initial render).\n // Guard against re-entrant renders: if render() itself mutates an observed\n // attribute, skip the recursive call to prevent an infinite loop.\n // Use _hydrated (set at end of connectedCallback) rather than this.element\n // so wrapper components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this.render();\n this._isRendering = false;\n }\n }\n\n /**\n * This static method returns an array containing the\n * names of the props we want to observe.\n */\n static get observedAttributes() {\n return options && options.props ? options.props : [];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Render a tagged template literal with auto-escaping and DOM diffing.\n * Usage: this.template`<span>${this.label}</span>`\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n */\n template(strings, ...values) {\n renderTemplate(this, strings, values);\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this.render();\n\n if (!this.element) {\n this.element = resolveElement(this);\n\n if (!this.element) {\n // Only warn when an explicit element selector was provided but didn't match.\n // Wrapper components (no element option) intentionally have no inner ref.\n if (options && options.element) {\n console.warn(\"░█ [ELENA]: No element found, using firstElementChild as fallback.\");\n }\n this.element = this.firstElementChild;\n }\n }\n\n // Flush props set before connection to host and inner element attributes\n if (this._props) {\n const hasElementSelector = !!(options && options.element);\n for (const [prop, value] of this._props) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (hasElementSelector) {\n syncAttribute(this.element, prop, attrValue);\n }\n }\n }\n\n if (!this._events && options && options.events) {\n this._events = true;\n options.events?.forEach(e => {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n });\n }\n\n this.updated();\n }\n\n /**\n * Perform post-update after each render().\n *\n * @internal\n */\n updated() {\n if (!this._hydrated) {\n this._hydrated = true;\n this.classList.add(\"elena-hydrated\");\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is removed from the document.\n */\n disconnectedCallback() {\n if (this._events) {\n this._events = false;\n options.events?.forEach(e => {\n this.element?.removeEventListener(e, this);\n });\n }\n }\n\n /**\n * Handles events on the Elena Element.\n *\n * @internal\n */\n handleEvent(event) {\n if (options.events?.includes(event.type)) {\n event.stopPropagation();\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n }\n\n if (options && options.props?.length) {\n setProps(ElenaElement.prototype, options.props, !!(options && options.element));\n }\n\n if (options && options.tagName) {\n /** @type {string} */\n ElenaElement._tagName = options.tagName;\n }\n\n /**\n * Register this class as a custom element using the `tagName`\n * set in options. Must be called on the final subclass, not\n * on the Elena mixin directly.\n *\n * @this {CustomElementConstructor}\n */\n ElenaElement.define = function () {\n if (this._tagName) {\n defineElement(this._tagName, this);\n }\n };\n\n return ElenaElement;\n}\n"],"names":["Elena","superClass","options","resolveElement","element","test","host","getElementsByClassName","querySelector","firstElementChild","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","getProps","this","_hydrated","_isRendering","render","observedAttributes","props","template","strings","values","renderTemplate","connectedCallback","console","warn","_props","hasElementSelector","value","attrValue","getPropValue","syncAttribute","_events","events","forEach","e","addEventListener","args","updated","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","type","stopPropagation","dispatchEvent","ElenaEvent","cancelable","length","setProps","prototype","tagName","_tagName","define","defineElement"],"mappings":"uPA2CO,SAASA,EAAMC,EAAYC,GAShC,MAAMC,EAAmBD,GAAWA,EAAQE,QAExC,qBAAqBC,KAAKH,EAAQE,SAChCE,GAAQA,EAAKC,uBAAuBL,EAAQE,SAAS,GACrDE,GAAQA,EAAKE,cAAcN,EAAQE,SAHrCE,GAAQA,EAAKG,kBAQjB,MAAMC,UAAqBT,EAMzBG,QAAU,KAUV,wBAAAO,CAAyBC,EAAMC,EAAUC,GACvCC,EAASC,KAAMJ,EAAMC,EAAUC,GAO3BE,KAAKC,WAAaJ,IAAaC,IAAaE,KAAKE,eACnDF,KAAKE,cAAe,EACpBF,KAAKG,SACLH,KAAKE,cAAe,EAExB,CAMA,6BAAWE,GACT,OAAOlB,GAAWA,EAAQmB,MAAQnB,EAAQmB,MAAQ,EACpD,CAMA,MAAAF,GAAU,CASV,QAAAG,CAASC,KAAYC,GACnBC,EAAeT,KAAMO,EAASC,EAChC,CAMA,iBAAAE,GAiBE,GAhBAV,KAAKG,SAEAH,KAAKZ,UACRY,KAAKZ,QAAUD,EAAea,MAEzBA,KAAKZ,UAGJF,GAAWA,EAAQE,SACrBuB,QAAQC,KAAK,sEAEfZ,KAAKZ,QAAUY,KAAKP,oBAKpBO,KAAKa,OAAQ,CACf,MAAMC,KAAwB5B,IAAWA,EAAQE,SACjD,IAAK,MAAOQ,EAAMmB,KAAUf,KAAKa,OAAQ,CACvC,MAAMG,EAAYC,SAAoBF,EAAOA,EAAO,eACpDG,EAAclB,KAAMJ,EAAMoB,GACtBF,GACFI,EAAclB,KAAKZ,QAASQ,EAAMoB,EAEtC,CACF,EAEKhB,KAAKmB,SAAWjC,GAAWA,EAAQkC,SACtCpB,KAAKmB,SAAU,EACfjC,EAAQkC,QAAQC,QAAQC,IACtBtB,KAAKZ,QAAQmC,iBAAiBD,EAAGtB,MACjCA,KAAKsB,GAAK,IAAIE,IAASxB,KAAKZ,QAAQkC,MAAME,MAI9CxB,KAAKyB,SACP,CAOA,OAAAA,GACOzB,KAAKC,YACRD,KAAKC,WAAY,EACjBD,KAAK0B,UAAUC,IAAI,kBAEvB,CAMA,oBAAAC,GACM5B,KAAKmB,UACPnB,KAAKmB,SAAU,EACfjC,EAAQkC,QAAQC,QAAQC,IACtBtB,KAAKZ,SAASyC,oBAAoBP,EAAGtB,QAG3C,CAOA,WAAA8B,CAAYC,GACN7C,EAAQkC,QAAQY,SAASD,EAAME,QACjCF,EAAMG,kBAENlC,KAAKmC,cAAc,IAAIC,EAAWL,EAAME,KAAM,CAAEI,YAAY,KAEhE,EAyBF,OAtBInD,GAAWA,EAAQmB,OAAOiC,QAC5BC,EAAS7C,EAAa8C,UAAWtD,EAAQmB,SAAUnB,IAAWA,EAAQE,UAGpEF,GAAWA,EAAQuD,UAErB/C,EAAagD,SAAWxD,EAAQuD,SAUlC/C,EAAaiD,OAAS,WAChB3C,KAAK0C,UACPE,EAAc5C,KAAK0C,SAAU1C,KAEjC,EAEON,CACT"}
1
+ {"version":3,"file":"elena.js","sources":["../src/elena.js"],"sourcesContent":["/**\n * ██████████ ████\n * ░░███░░░░░█░░███\n * ░███ █ ░ ░███ ██████ ████████ ██████\n * ░██████ ░███ ███░░███░░███░░███ ░░░░░███\n * ░███░░█ ░███ ░███████ ░███ ░███ ███████\n * ░███ ░ █ ░███ ░███░░░ ░███ ░███ ███░░███\n * ██████████ █████░░██████ ████ █████░░████████\n * ░░░░░░░░░░ ░░░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░\n *\n * Elena: Progressive Web Components\n * https://elenajs.com\n */\n\nimport { setProps, getProps, getPropValue, syncAttribute } from \"./common/props.js\";\nimport { ElenaEvent } from \"./common/events.js\";\nimport { defineElement, html } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {string[]} [props] - Props observed and synced as attributes.\n * @property {string[]} [events] - Events to delegate from the inner element.\n * @property {string} [element] - CSS selector for the inner element.\n */\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * Factory that creates Elena mixin class.\n *\n * Wraps `superClass` with Elena’s lifecycle, templating, props,\n * and events features. The `options` argument is optional.\n *\n * @param {ElenaConstructor} superClass - Base class to extend.\n * @param {ElenaOptions} [options] - Optional configuration options.\n * @returns {CustomElementConstructor} A class ready to be registered.\n */\nexport function Elena(superClass, options) {\n /**\n * Pre-compile element resolver once at class definition\n * time to improve performance:\n *\n * 1. no selector: firstElementChild (property access)\n * 2. className: getElementsByClassName (skips full selector parser)\n * 3. any other: querySelector (full parser, only when needed)\n */\n const resolveElement = !(options && options.element)\n ? host => host.firstElementChild\n : /^[a-z][a-z0-9-]*$/i.test(options.element)\n ? host => host.getElementsByClassName(options.element)[0]\n : host => host.querySelector(options.element);\n\n /**\n * Set up the initial state and default values for Elena Element.\n */\n class ElenaElement extends superClass {\n /**\n * Reference to the base element in the provided template.\n *\n * @type {Object}\n */\n element = null;\n\n /**\n * This method is called when the Elena Element’s\n * props are changed, added, removed or replaced.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n getProps(this, prop, oldValue, newValue);\n\n // Re-render when attributes change (after initial render).\n // Guard against re-entrant renders: if render() itself mutates an observed\n // attribute, skip the recursive call to prevent an infinite loop.\n // Use _hydrated (set at end of connectedCallback) rather than this.element\n // so slot components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this.render();\n this._isRendering = false;\n }\n }\n\n /**\n * This static method returns an array containing the\n * names of the props we want to observe.\n */\n static get observedAttributes() {\n return options && options.props ? options.props : [];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Render a tagged template literal with auto-escaping and DOM diffing.\n * Usage: this.template`<span>${this.label}</span>`\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n */\n template(strings, ...values) {\n renderTemplate(this, strings, values);\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this.render();\n\n if (!this.element) {\n this.element = resolveElement(this);\n\n if (!this.element) {\n // Only warn when an explicit element selector was provided but didn't match.\n // Slot components (no element option) intentionally have no inner ref.\n if (options && options.element) {\n console.warn(\"░█ [ELENA]: No element found, using firstElementChild as fallback.\");\n }\n this.element = this.firstElementChild;\n }\n }\n\n // Flush props set before connection to host and inner element attributes\n if (this._props) {\n const hasElementSelector = !!(options && options.element);\n for (const [prop, value] of this._props) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (hasElementSelector) {\n syncAttribute(this.element, prop, attrValue);\n }\n }\n }\n\n if (!this._events && options && options.events) {\n this._events = true;\n options.events?.forEach(e => {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n });\n }\n\n this.updated();\n }\n\n /**\n * Perform post-update after each render().\n *\n * @internal\n */\n updated() {\n if (!this._hydrated) {\n this._hydrated = true;\n this.classList.add(\"elena-hydrated\");\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is removed from the document.\n */\n disconnectedCallback() {\n if (this._events) {\n this._events = false;\n options.events?.forEach(e => {\n this.element?.removeEventListener(e, this);\n });\n }\n }\n\n /**\n * Handles events on the Elena Element.\n *\n * @internal\n */\n handleEvent(event) {\n if (options.events?.includes(event.type)) {\n event.stopPropagation();\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n }\n\n if (options && options.props?.length) {\n setProps(ElenaElement.prototype, options.props, !!(options && options.element));\n }\n\n if (options && options.tagName) {\n /** @type {string} */\n ElenaElement._tagName = options.tagName;\n }\n\n /**\n * Register this class as a custom element using the `tagName`\n * set in options. Must be called on the final subclass, not\n * on the Elena mixin directly.\n *\n * @this {CustomElementConstructor}\n */\n ElenaElement.define = function () {\n if (this._tagName) {\n defineElement(this._tagName, this);\n }\n };\n\n return ElenaElement;\n}\n"],"names":["Elena","superClass","options","resolveElement","element","test","host","getElementsByClassName","querySelector","firstElementChild","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","getProps","this","_hydrated","_isRendering","render","observedAttributes","props","template","strings","values","renderTemplate","connectedCallback","console","warn","_props","hasElementSelector","value","attrValue","getPropValue","syncAttribute","_events","events","forEach","e","addEventListener","args","updated","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","type","stopPropagation","dispatchEvent","ElenaEvent","cancelable","length","setProps","prototype","tagName","_tagName","define","defineElement"],"mappings":"uPA2CO,SAASA,EAAMC,EAAYC,GAShC,MAAMC,EAAmBD,GAAWA,EAAQE,QAExC,qBAAqBC,KAAKH,EAAQE,SAChCE,GAAQA,EAAKC,uBAAuBL,EAAQE,SAAS,GACrDE,GAAQA,EAAKE,cAAcN,EAAQE,SAHrCE,GAAQA,EAAKG,kBAQjB,MAAMC,UAAqBT,EAMzBG,QAAU,KAUV,wBAAAO,CAAyBC,EAAMC,EAAUC,GACvCC,EAASC,KAAMJ,EAAMC,EAAUC,GAO3BE,KAAKC,WAAaJ,IAAaC,IAAaE,KAAKE,eACnDF,KAAKE,cAAe,EACpBF,KAAKG,SACLH,KAAKE,cAAe,EAExB,CAMA,6BAAWE,GACT,OAAOlB,GAAWA,EAAQmB,MAAQnB,EAAQmB,MAAQ,EACpD,CAMA,MAAAF,GAAU,CASV,QAAAG,CAASC,KAAYC,GACnBC,EAAeT,KAAMO,EAASC,EAChC,CAMA,iBAAAE,GAiBE,GAhBAV,KAAKG,SAEAH,KAAKZ,UACRY,KAAKZ,QAAUD,EAAea,MAEzBA,KAAKZ,UAGJF,GAAWA,EAAQE,SACrBuB,QAAQC,KAAK,sEAEfZ,KAAKZ,QAAUY,KAAKP,oBAKpBO,KAAKa,OAAQ,CACf,MAAMC,KAAwB5B,IAAWA,EAAQE,SACjD,IAAK,MAAOQ,EAAMmB,KAAUf,KAAKa,OAAQ,CACvC,MAAMG,EAAYC,SAAoBF,EAAOA,EAAO,eACpDG,EAAclB,KAAMJ,EAAMoB,GACtBF,GACFI,EAAclB,KAAKZ,QAASQ,EAAMoB,EAEtC,CACF,EAEKhB,KAAKmB,SAAWjC,GAAWA,EAAQkC,SACtCpB,KAAKmB,SAAU,EACfjC,EAAQkC,QAAQC,QAAQC,IACtBtB,KAAKZ,QAAQmC,iBAAiBD,EAAGtB,MACjCA,KAAKsB,GAAK,IAAIE,IAASxB,KAAKZ,QAAQkC,MAAME,MAI9CxB,KAAKyB,SACP,CAOA,OAAAA,GACOzB,KAAKC,YACRD,KAAKC,WAAY,EACjBD,KAAK0B,UAAUC,IAAI,kBAEvB,CAMA,oBAAAC,GACM5B,KAAKmB,UACPnB,KAAKmB,SAAU,EACfjC,EAAQkC,QAAQC,QAAQC,IACtBtB,KAAKZ,SAASyC,oBAAoBP,EAAGtB,QAG3C,CAOA,WAAA8B,CAAYC,GACN7C,EAAQkC,QAAQY,SAASD,EAAME,QACjCF,EAAMG,kBAENlC,KAAKmC,cAAc,IAAIC,EAAWL,EAAME,KAAM,CAAEI,YAAY,KAEhE,EAyBF,OAtBInD,GAAWA,EAAQmB,OAAOiC,QAC5BC,EAAS7C,EAAa8C,UAAWtD,EAAQmB,SAAUnB,IAAWA,EAAQE,UAGpEF,GAAWA,EAAQuD,UAErB/C,EAAagD,SAAWxD,EAAQuD,SAUlC/C,EAAaiD,OAAS,WAChB3C,KAAK0C,UACPE,EAAc5C,KAAK0C,SAAU1C,KAEjC,EAEON,CACT"}
package/dist/props.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"props.js","sources":["../src/common/props.js"],"sourcesContent":["/**\n * Get the value of the Elena Element property.\n *\n * @param {string} type\n * @param {any} value\n * @param {\"toAttribute\" | \"toProp\"} [transform]\n */\nexport function getPropValue(type, value, transform) {\n value = type === \"boolean\" && typeof value !== \"boolean\" ? value !== null : value;\n\n if (!transform) {\n return value;\n } else if (transform === \"toAttribute\") {\n switch (type) {\n case \"object\":\n case \"array\":\n return value === null ? null : JSON.stringify(value);\n case \"boolean\":\n return value ? \"\" : null;\n case \"number\":\n return value === null ? null : value;\n default:\n return value === \"\" ? null : value;\n }\n } else {\n switch (type) {\n case \"object\":\n case \"array\":\n return value && JSON.parse(value);\n case \"boolean\":\n return value; // conversion already handled above\n case \"number\":\n return value !== null ? +value : value;\n default:\n return value;\n }\n }\n}\n\n/**\n * Set or remove an attribute on an Elena Element.\n *\n * @param {Element} element - Target element\n * @param {string} name - Attribute name\n * @param {string | null} value - Attribute value, or null to remove\n */\nexport function syncAttribute(element, name, value) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot sync attributes to a null element.\");\n return;\n }\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n}\n\n/**\n * Define prop getters/setters on the prototype once\n * at class-creation time. Values are stored per-instance\n * via a `_props` Map that is lazily created.\n *\n * @param {Function} proto - The class prototype\n * @param {string[]} properties - Prop names to define\n * @param {boolean} [syncToElement=true] - Whether to also sync prop attributes to the\n * inner element ref. Pass false for wrapper components that have no explicit inner\n * element selector, so props are not leaked onto slotted children.\n */\nexport function setProps(proto, properties, syncToElement = true) {\n for (const prop of properties) {\n Object.defineProperty(proto, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return this._props ? this._props.get(prop) : undefined;\n },\n set(value) {\n if (!this._props) {\n this._props = new Map();\n }\n if (value === this._props.get(prop)) {\n return;\n }\n\n this._props.set(prop, value);\n if (!this.isConnected) {\n return;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (syncToElement && this.element) {\n syncAttribute(this.element, prop, attrValue);\n }\n },\n });\n }\n}\n\n/**\n * We need to update the internals of the Elena Element\n * when props on the host element are changed.\n *\n * @param {object} context\n * @param {string} name\n * @param {any} oldValue\n * @param {any} newValue\n */\nexport function getProps(context, name, oldValue, newValue) {\n if (oldValue !== newValue) {\n const type = typeof context[name];\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\n }\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","syncAttribute","element","name","removeAttribute","setAttribute","console","warn","setProps","proto","properties","syncToElement","prop","Object","defineProperty","configurable","enumerable","get","this","_props","undefined","set","Map","isConnected","attrValue","getProps","context","oldValue","newValue","newAttr"],"mappings":"AAOO,SAASA,EAAaC,EAAMC,EAAOC,GAGxC,GAFAD,EAAiB,YAATD,GAAuC,kBAAVC,EAAgC,OAAVA,EAAiBA,GAEvEC,EACH,OAAOD,EACF,GAAkB,gBAAdC,EACT,OAAQF,GACN,IAAK,SACL,IAAK,QACH,OAAiB,OAAVC,EAAiB,KAAOE,KAAKC,UAAUH,GAChD,IAAK,UACH,OAAOA,EAAQ,GAAK,KACtB,IAAK,SACH,OAAiB,OAAVA,EAAiB,KAAOA,EACjC,QACE,MAAiB,KAAVA,EAAe,KAAOA,OAGjC,OAAQD,GACN,IAAK,SACL,IAAK,QACH,OAAOC,GAASE,KAAKE,MAAMJ,GAC7B,IAAK,UAIL,QACE,OAAOA,EAHT,IAAK,SACH,OAAiB,OAAVA,GAAkBA,EAAQA,EAKzC,CASO,SAASK,EAAcC,EAASC,EAAMP,GACtCM,EAIS,OAAVN,EACFM,EAAQE,gBAAgBD,GAExBD,EAAQG,aAAaF,EAAMP,GAN3BU,QAAQC,KAAK,wDAQjB,CAaO,SAASC,EAASC,EAAOC,EAAYC,GAAgB,GAC1D,IAAK,MAAMC,KAAQF,EACjBG,OAAOC,eAAeL,EAAOG,EAAM,CACjCG,cAAc,EACdC,YAAY,EACZ,GAAAC,GACE,OAAOC,KAAKC,OAASD,KAAKC,OAAOF,IAAIL,QAAQQ,CAC/C,EACA,GAAAC,CAAIzB,GAIF,GAHKsB,KAAKC,SACRD,KAAKC,OAAS,IAAIG,KAEhB1B,IAAUsB,KAAKC,OAAOF,IAAIL,GAC5B,OAIF,GADAM,KAAKC,OAAOE,IAAIT,EAAMhB,IACjBsB,KAAKK,YACR,OAGF,MAAMC,EAAY9B,SAAoBE,EAAOA,EAAO,eACpDK,EAAciB,KAAMN,EAAMY,GACtBb,GAAiBO,KAAKhB,SACxBD,EAAciB,KAAKhB,QAASU,EAAMY,EAEtC,GAGN,CAWO,SAASC,EAASC,EAASvB,EAAMwB,EAAUC,GAChD,GAAID,IAAaC,EAAU,CACzB,MACMC,EAAUnC,SADIgC,EAAQvB,GACOyB,EAAU,UAC7CF,EAAQvB,GAAQ0B,CAClB,CACF"}
1
+ {"version":3,"file":"props.js","sources":["../src/common/props.js"],"sourcesContent":["/**\n * Get the value of the Elena Element property.\n *\n * @param {string} type\n * @param {any} value\n * @param {\"toAttribute\" | \"toProp\"} [transform]\n */\nexport function getPropValue(type, value, transform) {\n value = type === \"boolean\" && typeof value !== \"boolean\" ? value !== null : value;\n\n if (!transform) {\n return value;\n } else if (transform === \"toAttribute\") {\n switch (type) {\n case \"object\":\n case \"array\":\n return value === null ? null : JSON.stringify(value);\n case \"boolean\":\n return value ? \"\" : null;\n case \"number\":\n return value === null ? null : value;\n default:\n return value === \"\" ? null : value;\n }\n } else {\n switch (type) {\n case \"object\":\n case \"array\":\n return value && JSON.parse(value);\n case \"boolean\":\n return value; // conversion already handled above\n case \"number\":\n return value !== null ? +value : value;\n default:\n return value;\n }\n }\n}\n\n/**\n * Set or remove an attribute on an Elena Element.\n *\n * @param {Element} element - Target element\n * @param {string} name - Attribute name\n * @param {string | null} value - Attribute value, or null to remove\n */\nexport function syncAttribute(element, name, value) {\n if (!element) {\n console.warn(\"░█ [ELENA]: Cannot sync attributes to a null element.\");\n return;\n }\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n}\n\n/**\n * Define prop getters/setters on the prototype once\n * at class-creation time. Values are stored per-instance\n * via a `_props` Map that is lazily created.\n *\n * @param {Function} proto - The class prototype\n * @param {string[]} properties - Prop names to define\n * @param {boolean} [syncToElement=true] - Whether to also sync prop attributes to the\n * inner element ref. Pass false for slot components that have no explicit inner\n * element selector, so props are not leaked onto slotted children.\n */\nexport function setProps(proto, properties, syncToElement = true) {\n for (const prop of properties) {\n Object.defineProperty(proto, prop, {\n configurable: true,\n enumerable: true,\n get() {\n return this._props ? this._props.get(prop) : undefined;\n },\n set(value) {\n if (!this._props) {\n this._props = new Map();\n }\n if (value === this._props.get(prop)) {\n return;\n }\n\n this._props.set(prop, value);\n if (!this.isConnected) {\n return;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n if (syncToElement && this.element) {\n syncAttribute(this.element, prop, attrValue);\n }\n },\n });\n }\n}\n\n/**\n * We need to update the internals of the Elena Element\n * when props on the host element are changed.\n *\n * @param {object} context\n * @param {string} name\n * @param {any} oldValue\n * @param {any} newValue\n */\nexport function getProps(context, name, oldValue, newValue) {\n if (oldValue !== newValue) {\n const type = typeof context[name];\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\n }\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","syncAttribute","element","name","removeAttribute","setAttribute","console","warn","setProps","proto","properties","syncToElement","prop","Object","defineProperty","configurable","enumerable","get","this","_props","undefined","set","Map","isConnected","attrValue","getProps","context","oldValue","newValue","newAttr"],"mappings":"AAOO,SAASA,EAAaC,EAAMC,EAAOC,GAGxC,GAFAD,EAAiB,YAATD,GAAuC,kBAAVC,EAAgC,OAAVA,EAAiBA,GAEvEC,EACH,OAAOD,EACF,GAAkB,gBAAdC,EACT,OAAQF,GACN,IAAK,SACL,IAAK,QACH,OAAiB,OAAVC,EAAiB,KAAOE,KAAKC,UAAUH,GAChD,IAAK,UACH,OAAOA,EAAQ,GAAK,KACtB,IAAK,SACH,OAAiB,OAAVA,EAAiB,KAAOA,EACjC,QACE,MAAiB,KAAVA,EAAe,KAAOA,OAGjC,OAAQD,GACN,IAAK,SACL,IAAK,QACH,OAAOC,GAASE,KAAKE,MAAMJ,GAC7B,IAAK,UAIL,QACE,OAAOA,EAHT,IAAK,SACH,OAAiB,OAAVA,GAAkBA,EAAQA,EAKzC,CASO,SAASK,EAAcC,EAASC,EAAMP,GACtCM,EAIS,OAAVN,EACFM,EAAQE,gBAAgBD,GAExBD,EAAQG,aAAaF,EAAMP,GAN3BU,QAAQC,KAAK,wDAQjB,CAaO,SAASC,EAASC,EAAOC,EAAYC,GAAgB,GAC1D,IAAK,MAAMC,KAAQF,EACjBG,OAAOC,eAAeL,EAAOG,EAAM,CACjCG,cAAc,EACdC,YAAY,EACZ,GAAAC,GACE,OAAOC,KAAKC,OAASD,KAAKC,OAAOF,IAAIL,QAAQQ,CAC/C,EACA,GAAAC,CAAIzB,GAIF,GAHKsB,KAAKC,SACRD,KAAKC,OAAS,IAAIG,KAEhB1B,IAAUsB,KAAKC,OAAOF,IAAIL,GAC5B,OAIF,GADAM,KAAKC,OAAOE,IAAIT,EAAMhB,IACjBsB,KAAKK,YACR,OAGF,MAAMC,EAAY9B,SAAoBE,EAAOA,EAAO,eACpDK,EAAciB,KAAMN,EAAMY,GACtBb,GAAiBO,KAAKhB,SACxBD,EAAciB,KAAKhB,QAASU,EAAMY,EAEtC,GAGN,CAWO,SAASC,EAASC,EAASvB,EAAMwB,EAAUC,GAChD,GAAID,IAAaC,EAAU,CACzB,MACMC,EAAUnC,SADIgC,EAAQvB,GACOyB,EAAU,UAC7CF,EAAQvB,GAAQ0B,CAClB,CACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elenajs/core",
3
- "version": "0.0.8",
4
- "description": "Elena is a lightweight, opinionated library for building blazing fast progressive web components.",
3
+ "version": "0.0.10",
4
+ "description": "Elena is a lightweight, opinionated library for building progressive web components.",
5
5
  "author": "Ariel Salminen <info@arielsalminen.com>",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -30,5 +30,5 @@
30
30
  "typescript": "5.9.3",
31
31
  "vitest": "4.0.18"
32
32
  },
33
- "gitHead": "6abc8bf203d7b4470f08cd34bf4d495a1ef08255"
33
+ "gitHead": "10216357bac3ac7f05884d1bc4387c0f1b5ea320"
34
34
  }