@elenajs/core 0.8.1 → 0.9.0

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.
package/dist/bundle.js CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t,n){if(t="boolean"===e&&"boolean"!=typeof t?null!==t:t,!n)return t;if("toAttribute"===n)switch(e){case"object":case"array":return null===t?null:JSON.stringify(t);case"boolean":return t?"":null;case"number":return null===t?null:t;default:return""===t?null:t}else switch(e){case"object":case"array":if(!t)return t;try{return JSON.parse(t)}catch{return console.warn("░█ [ELENA]: Invalid JSON for prop, received: "+t),null}case"boolean":return t;case"number":return null!==t?+t:t;default:return t}}function t(e,t,n){e?null===n?e.removeAttribute(t):e.setAttribute(t,n):console.warn("░█ [ELENA]: Cannot sync attributes to a null element.")}class n extends Event{constructor(e,t){super(e,{bubbles:!0,composed:!0,...t})}}function s(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return String(e).replace(/[&<>"']/g,e=>t[e])}function r(e,...t){const n=e.reduce((e,n,r)=>{const i=t[r];return e+n+(i&&i.__raw?String(i):s(String(i??"")))},"");return{__raw:!0,strings:e,values:t,toString:()=>n}}const i={__raw:!0,toString:()=>""},o=new WeakMap;function l(e,t,n){(function(e,t,n){if(e._tplStrings!==t||!e._tplParts)return!1;let r=!1;for(let t=0;t<n.length;t++){const i=n[t],o=i&&i.__raw,l=o?String(i):s(String(i??""));if(l!==e._tplValues[t])if(e._tplValues[t]=l,o)r=!0;else{const n=e._tplParts[t];n?n.textContent=String(i??""):r=!0}}return!r})(e,t,n)||function(e,t,n){const r=n.map(e=>e&&e.__raw?String(e):s(String(e??"")));let i=o.get(t);i||(i=Array.from(t,e=>e.replace(/\n\s*/g," ")),o.set(t,i));const l=i.reduce((e,t,n)=>e+t+(r[n]??""),"").replace(/>\s+</g,"><").replace(/>\s+/g,">").replace(/\s+</g,"<").trim();(function(e,t){if(!e)return void console.warn("░█ [ELENA]: Cannot render to a null element.");e.replaceChildren(e.ownerDocument.createRange().createContextualFragment(t))})(e,l),e._tplStrings=t,e._tplValues=r,e._tplParts=function(e,t){const n=new Array(t.length),s=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);let r,i=0;for(;(r=s.nextNode())&&i<t.length;)r.textContent===t[i]&&(n[i]=r,i++);return n}(e,r)}(e,t,n)}function a(s,r){const i=r&&r.element?/^[a-z][a-z0-9-]*$/i.test(r.element)?e=>e.getElementsByClassName(r.element)[0]:e=>e.querySelector(r.element):e=>e.firstElementChild;class o extends s{element=null;attributeChangedCallback(t,n,s){"text"!==t?(!function(t,n,s,r){if(s!==r){const s=typeof t[n];"undefined"===s&&console.warn(`░█ [ELENA]: Prop "${n}" has no default value. Set a default in the constructor so Elena can infer the correct type.`);const i=e(s,r,"toProp");t[n]=i}}(this,t,n,s),this._hydrated&&n!==s&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)):this.text=s??""}static get observedAttributes(){return[...r&&r.props?r.props:[],"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&l(this,e.strings,e.values)}connectedCallback(){this._captureText(),this._applyRender(),this._resolveInnerElement(),this._flushProps(),this._delegateEvents(),this.updated()}_captureText(){if(!this._hydrated&&!this._text){const e=this.textContent.trim();e?this.text=e:queueMicrotask(()=>{this._text||(this.text=this.textContent.trim())})}}_resolveInnerElement(){this.element||(this.element=i(this),this.element||(r&&r.element&&console.warn("░█ [ELENA]: No element found, using firstElementChild as fallback."),this.element=this.firstElementChild))}_flushProps(){if(this._props){const n=!(!r||!r.element);for(const[s,r]of this._props){const i=e(typeof r,r,"toAttribute");t(this,s,i),n&&t(this.element,s,i)}}}_delegateEvents(){!this._events&&r&&r.events&&(this.element?(this._events=!0,r.events?.forEach(e=>{this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)})):console.warn("░█ [ELENA]: Cannot delegate events, no inner element found. Ensure the component renders an element or check your element selector."))}updated(){this._hydrated||(this._hydrated=!0,this.classList.add("elena-hydrated"))}disconnectedCallback(){this._events&&(this._events=!1,r.events?.forEach(e=>{this.element?.removeEventListener(e,this)}))}handleEvent(e){r.events?.includes(e.type)&&(e.stopPropagation(),this.dispatchEvent(new n(e.type,{cancelable:!0})))}get text(){return this._text??""}set text(e){const t=this._text;this._text=e,this._hydrated&&t!==e&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}}return r&&r.props?.length&&(r.props.includes("text")&&console.warn('░█ [ELENA]: "text" is a reserved property. Rename your prop to avoid overriding the built-in text content feature.'),function(n,s,r=!0){for(const i of s)Object.defineProperty(n,i,{configurable:!0,enumerable:!0,get(){return this._props?this._props.get(i):void 0},set(n){if(this._props||(this._props=new Map),n===this._props.get(i))return;if(this._props.set(i,n),!this.isConnected)return;const s=e(typeof n,n,"toAttribute");t(this,i,s),r&&this.element&&t(this.element,i,s)}})}(o.prototype,r.props,!(!r||!r.element))),r&&r.tagName&&(o._tagName=r.tagName),o.define=function(){var e,t;this._tagName?(e=this._tagName,t=this,"undefined"!=typeof window&&"customElements"in window&&(window.customElements.get(e)||window.customElements.define(e,t))):console.warn("░█ [ELENA]: define() called without a tagName. Set tagName in your Elena options to register the element.")},o}export{a as Elena,r as html,i as nothing};
1
+ function e(e,t,n){if(t="boolean"===e&&"boolean"!=typeof t?null!==t:t,!n)return t;if("toAttribute"===n)switch(e){case"object":case"array":return null===t?null:JSON.stringify(t);case"boolean":return t?"":null;case"number":return null===t?null:t;default:return""===t?null:t}else switch(e){case"object":case"array":if(!t)return t;try{return JSON.parse(t)}catch{return console.warn("░█ [ELENA]: Invalid JSON for prop, received: "+t),null}case"boolean":return t;case"number":return null!==t?+t:t;default:return t}}function t(e,t,n){e?null===n?e.removeAttribute(t):e.setAttribute(t,n):console.warn("░█ [ELENA]: Cannot sync attributes to a null element.")}class n extends Event{constructor(e,t){super(e,{bubbles:!0,composed:!0,...t})}}function r(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return String(e).replace(/[&<>"']/g,e=>t[e])}function s(e,...t){const n=e.reduce((e,n,s)=>{const i=t[s];return e+n+(i&&i.__raw?String(i):r(String(i??"")))},"");return{__raw:!0,strings:e,values:t,toString:()=>n}}const i={__raw:!0,toString:()=>""},o=new WeakMap;function l(e,t,n){(function(e,t,n){if(e._tplStrings!==t||!e._tplParts)return!1;let s=!1;for(let t=0;t<n.length;t++){const i=n[t],o=i&&i.__raw,l=o?String(i):r(String(i??""));if(l!==e._tplValues[t])if(e._tplValues[t]=l,o)s=!0;else{const n=e._tplParts[t];n?n.textContent=String(i??""):s=!0}}return!s})(e,t,n)||function(e,t,n){const s=n.map(e=>e&&e.__raw?String(e):r(String(e??"")));let i=o.get(t);i||(i=Array.from(t,e=>e.replace(/\n\s*/g," ")),o.set(t,i));const l=i.reduce((e,t,n)=>e+t+(s[n]??""),"").replace(/>\s+</g,"><").replace(/>\s+/g,">").replace(/\s+</g,"<").trim();(function(e,t){if(!e)return void console.warn("░█ [ELENA]: Cannot render to a null element.");e.replaceChildren(e.ownerDocument.createRange().createContextualFragment(t))})(e,l),e._tplStrings=t,e._tplValues=s,e._tplParts=function(e,t){const n=new Array(t.length),r=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);let s,i=0;for(;(s=r.nextNode())&&i<t.length;)s.textContent===t[i]&&(n[i]=s,i++);return n}(e,s)}(e,t,n)}function a(r,s){const i=s&&s.element?/^[a-z][a-z0-9-]*$/i.test(s.element)?e=>e.getElementsByClassName(s.element)[0]:e=>e.querySelector(s.element):e=>e.firstElementChild;class o extends r{element=null;attributeChangedCallback(t,n,r){"text"!==t?(!function(t,n,r,s){if(r!==s){const r=typeof t[n];"undefined"===r&&console.warn(`░█ [ELENA]: Prop "${n}" has no default value. Set a default in the constructor so Elena can infer the correct type.`);const i=e(r,s,"toProp");t[n]=i}}(this,t,n,r),this._hydrated&&n!==r&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)):this.text=r??""}static get observedAttributes(){return[...s&&s.props?s.props:[],"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&l(this,e.strings,e.values)}connectedCallback(){this._captureText(),this._applyRender(),this._resolveInnerElement(),this._flushProps(),this._delegateEvents(),this.updated()}_captureText(){if(!this._hydrated&&!this._text){const e=this.textContent.trim();e?this.text=e:queueMicrotask(()=>{this._text||(this.text=this.textContent.trim())})}}_resolveInnerElement(){this.element||(this.element=i(this),this.element||(s&&s.element&&console.warn("░█ [ELENA]: No element found, using firstElementChild as fallback."),this.element=this.firstElementChild))}_flushProps(){if(this._props){const n=!(!s||!s.element);for(const[r,s]of this._props){const i=e(typeof s,s,"toAttribute");t(this,r,i),n&&t(this.element,r,i)}}}_delegateEvents(){!this._events&&s&&s.events&&(this.element?(this._events=!0,s.events?.forEach(e=>{this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)})):console.warn("░█ [ELENA]: Cannot delegate events, no inner element found. Ensure the component renders an element or check your element selector."))}updated(){this._hydrated||(this._hydrated=!0,this.setAttribute("hydrated",""))}disconnectedCallback(){this._events&&(this._events=!1,s.events?.forEach(e=>{this.element?.removeEventListener(e,this)}))}handleEvent(e){s.events?.includes(e.type)&&(e.stopPropagation(),this.dispatchEvent(new n(e.type,{cancelable:!0})))}get text(){return this._text??""}set text(e){const t=this._text;this._text=e,this._hydrated&&t!==e&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}}return s&&s.props?.length&&(s.props.includes("text")&&console.warn('░█ [ELENA]: "text" is a reserved property. Rename your prop to avoid overriding the built-in text content feature.'),function(n,r,s=!0){for(const i of r)Object.defineProperty(n,i,{configurable:!0,enumerable:!0,get(){return this._props?this._props.get(i):void 0},set(n){if(this._props||(this._props=new Map),n===this._props.get(i))return;if(this._props.set(i,n),!this.isConnected)return;const r=e(typeof n,n,"toAttribute");t(this,i,r),s&&this.element&&t(this.element,i,r)}})}(o.prototype,s.props,!(!s||!s.element))),s&&s.tagName&&(o._tagName=s.tagName),o.define=function(){var e,t;this._tagName?(e=this._tagName,t=this,"undefined"!=typeof window&&"customElements"in window&&(window.customElements.get(e)||window.customElements.define(e,t))):console.warn("░█ [ELENA]: define() called without a tagName. Set tagName in your Elena options to register the element.")},o}export{a as Elena,s as html,i as nothing};
2
2
  //# sourceMappingURL=bundle.js.map
@@ -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 if (!value) {\n return value;\n }\n try {\n return JSON.parse(value);\n } catch {\n console.warn(\"░█ [ELENA]: Invalid JSON for prop, received: \" + value);\n return null;\n }\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 Composite 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 if (type === \"undefined\") {\n console.warn(\n `░█ [ELENA]: Prop \"${name}\" has no default value. ` +\n \"Set a default in the constructor so Elena can infer the correct type.\"\n );\n }\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. Use as the return value of render(), or for\n * sub-fragments inside render methods. Interpolated values are auto-escaped;\n * nested `html` fragments are passed through without double-escaping.\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n * @returns {{ __raw: true, strings: TemplateStringsArray, values: Array, toString(): string }}\n */\nexport function html(strings, ...values) {\n const result = strings.reduce((acc, str, i) => {\n const v = values[i];\n return acc + str + (v && v.__raw ? String(v) : escapeHtml(String(v ?? \"\")));\n }, \"\");\n return { __raw: true, strings, values, toString: () => result };\n}\n\n/**\n * A placeholder you can return from a conditional expression inside a template\n * to render nothing. Always produces an empty string; signals to the template\n * engine that no further processing is needed.\n *\n * @type {{ __raw: true, toString(): string }}\n */\nexport const nothing = { __raw: true, toString: () => \"\" };\n","import { escapeHtml } from \"./utils.js\";\n\n/** @type {WeakMap<TemplateStringsArray, string[]>} */\nconst _stringsCache = new WeakMap();\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 // The JS engine reuses the same `strings` object for every call from the same template\n // literal, so cache the cleaned-up parts and run the regex only once per template.\n let processedStrings = _stringsCache.get(strings);\n if (!processedStrings) {\n processedStrings = Array.from(strings, s => s.replace(/\\n\\s*/g, \" \"));\n _stringsCache.set(strings, processedStrings);\n }\n\n // Build the complete HTML markup\n const markup = processedStrings\n .reduce((out, str, i) => out + str + (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 * Known limitation: text nodes are matched by content. This could\n * cause the wrong node to be patched if a static part of the template\n * contains text that exactly matches a dynamic value with no surrounding\n * tag. In practice this is extremely rare, static template parts should\n * be HTML structure (tags, attributes, punctuation), not raw text.\n *\n * Example: `<span>Elena</span>${name}` with `name = \"Elena\"`. The\n * walker matches the static \"Elena\" inside the span first, so when\n * `name` later changes to \"Bob\", the span text is patched instead\n * of the trailing text node.\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\n * in e.g. iframes parse HTML correctly.\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, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, nothing };\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 if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\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 Composite Components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\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 const props = options && options.props ? options.props : [];\n return [...props, \"text\"];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * Return an `html` tagged template literal.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Calls render() and applies the result to\n * the DOM via renderTemplate().\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n if (result && result.strings) {\n renderTemplate(this, result.strings, result.values);\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this._captureText();\n this._applyRender();\n this._resolveInnerElement();\n this._flushProps();\n this._delegateEvents();\n this.updated();\n }\n\n /**\n * Capture textContent from the light DOM before\n * the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated) {\n if (!this._text) {\n const text = this.textContent.trim();\n if (text) {\n this.text = text;\n } else {\n queueMicrotask(() => {\n if (!this._text) {\n this.text = this.textContent.trim();\n }\n });\n }\n }\n }\n }\n\n /**\n * Resolve the inner element reference via\n * the pre-compiled selector.\n *\n * @internal\n */\n _resolveInnerElement() {\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 // Composite 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\n /**\n * Flush props set before connection to host and\n * the inner element attributes.\n *\n * @internal\n */\n _flushProps() {\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\n /**\n * Set up event delegation from inner element to host.\n *\n * @internal\n */\n _delegateEvents() {\n if (!this._events && options && options.events) {\n if (!this.element) {\n console.warn(\n \"░█ [ELENA]: Cannot delegate events, no inner element found. \" +\n \"Ensure the component renders an element or check your element selector.\"\n );\n } else {\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 }\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 * The text content of the element, captured from light DOM\n * before the first render. Setting this property triggers\n * a re-render.\n *\n * @type {string}\n */\n get text() {\n return this._text ?? \"\";\n }\n\n set text(value) {\n const old = this._text;\n this._text = value;\n if (this._hydrated && old !== value && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\n }\n }\n }\n\n if (options && options.props?.length) {\n if (options.props.includes(\"text\")) {\n console.warn(\n '░█ [ELENA]: \"text\" is a reserved property. ' +\n \"Rename your prop to avoid overriding the built-in text content feature.\"\n );\n }\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 } else {\n console.warn(\n \"░█ [ELENA]: define() called without a tagName. \" +\n \"Set tagName in your Elena options to register the element.\"\n );\n }\n };\n\n return ElenaElement;\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","console","warn","syncAttribute","element","name","removeAttribute","setAttribute","ElenaEvent","Event","constructor","eventInitDict","super","bubbles","composed","escapeHtml","str","Escape","String","replace","c","html","strings","values","result","reduce","acc","i","v","__raw","toString","nothing","_stringsCache","WeakMap","renderTemplate","_tplStrings","_tplParts","needsFullRender","length","isRaw","newRendered","_tplValues","textNode","textContent","patchTextNodes","renderedValues","map","processedStrings","get","Array","from","s","set","markup","out","trim","replaceChildren","ownerDocument","createRange","createContextualFragment","renderHtml","escapedValues","parts","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","_applyRender","text","observedAttributes","props","render","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","queueMicrotask","_props","hasElementSelector","attrValue","_events","events","forEach","e","addEventListener","args","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","stopPropagation","dispatchEvent","cancelable","old","proto","properties","syncToElement","Object","defineProperty","configurable","enumerable","undefined","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 if (!value) {\n return value;\n }\n try {\n return JSON.parse(value);\n } catch {\n console.warn(\"░█ [ELENA]: Invalid JSON for prop, received: \" + value);\n return null;\n }\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 Composite 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 if (type === \"undefined\") {\n console.warn(\n `░█ [ELENA]: Prop \"${name}\" has no default value. ` +\n \"Set a default in the constructor so Elena can infer the correct type.\"\n );\n }\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. Use as the return value of render(), or for\n * sub-fragments inside render methods. Interpolated values are auto-escaped;\n * nested `html` fragments are passed through without double-escaping.\n *\n * @param {TemplateStringsArray} strings\n * @param {...*} values\n * @returns {{ __raw: true, strings: TemplateStringsArray, values: Array, toString(): string }}\n */\nexport function html(strings, ...values) {\n const result = strings.reduce((acc, str, i) => {\n const v = values[i];\n return acc + str + (v && v.__raw ? String(v) : escapeHtml(String(v ?? \"\")));\n }, \"\");\n return { __raw: true, strings, values, toString: () => result };\n}\n\n/**\n * A placeholder you can return from a conditional expression inside a template\n * to render nothing. Always produces an empty string; signals to the template\n * engine that no further processing is needed.\n *\n * @type {{ __raw: true, toString(): string }}\n */\nexport const nothing = { __raw: true, toString: () => \"\" };\n","import { escapeHtml } from \"./utils.js\";\n\n/** @type {WeakMap<TemplateStringsArray, string[]>} */\nconst _stringsCache = new WeakMap();\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 // The JS engine reuses the same `strings` object for every call from the same template\n // literal, so cache the cleaned-up parts and run the regex only once per template.\n let processedStrings = _stringsCache.get(strings);\n if (!processedStrings) {\n processedStrings = Array.from(strings, s => s.replace(/\\n\\s*/g, \" \"));\n _stringsCache.set(strings, processedStrings);\n }\n\n // Build the complete HTML markup\n const markup = processedStrings\n .reduce((out, str, i) => out + str + (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 * Known limitation: text nodes are matched by content. This could\n * cause the wrong node to be patched if a static part of the template\n * contains text that exactly matches a dynamic value with no surrounding\n * tag. In practice this is extremely rare, static template parts should\n * be HTML structure (tags, attributes, punctuation), not raw text.\n *\n * Example: `<span>Elena</span>${name}` with `name = \"Elena\"`. The\n * walker matches the static \"Elena\" inside the span first, so when\n * `name` later changes to \"Bob\", the span text is patched instead\n * of the trailing text node.\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\n * in e.g. iframes parse HTML correctly.\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, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, nothing };\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 if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\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 Composite Components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\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 const props = options && options.props ? options.props : [];\n return [...props, \"text\"];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * Return an `html` tagged template literal.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Calls render() and applies the result to\n * the DOM via renderTemplate().\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n if (result && result.strings) {\n renderTemplate(this, result.strings, result.values);\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this._captureText();\n this._applyRender();\n this._resolveInnerElement();\n this._flushProps();\n this._delegateEvents();\n this.updated();\n }\n\n /**\n * Capture textContent from the light DOM before\n * the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated) {\n if (!this._text) {\n const text = this.textContent.trim();\n if (text) {\n this.text = text;\n } else {\n queueMicrotask(() => {\n if (!this._text) {\n this.text = this.textContent.trim();\n }\n });\n }\n }\n }\n }\n\n /**\n * Resolve the inner element reference via\n * the pre-compiled selector.\n *\n * @internal\n */\n _resolveInnerElement() {\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 // Composite 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\n /**\n * Flush props set before connection to host and\n * the inner element attributes.\n *\n * @internal\n */\n _flushProps() {\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\n /**\n * Set up event delegation from inner element to host.\n *\n * @internal\n */\n _delegateEvents() {\n if (!this._events && options && options.events) {\n if (!this.element) {\n console.warn(\n \"░█ [ELENA]: Cannot delegate events, no inner element found. \" +\n \"Ensure the component renders an element or check your element selector.\"\n );\n } else {\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 }\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.setAttribute(\"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 * The text content of the element, captured from light DOM\n * before the first render. Setting this property triggers\n * a re-render.\n *\n * @type {string}\n */\n get text() {\n return this._text ?? \"\";\n }\n\n set text(value) {\n const old = this._text;\n this._text = value;\n if (this._hydrated && old !== value && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\n }\n }\n }\n\n if (options && options.props?.length) {\n if (options.props.includes(\"text\")) {\n console.warn(\n '░█ [ELENA]: \"text\" is a reserved property. ' +\n \"Rename your prop to avoid overriding the built-in text content feature.\"\n );\n }\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 } else {\n console.warn(\n \"░█ [ELENA]: define() called without a tagName. \" +\n \"Set tagName in your Elena options to register the element.\"\n );\n }\n };\n\n return ElenaElement;\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","console","warn","syncAttribute","element","name","removeAttribute","setAttribute","ElenaEvent","Event","constructor","eventInitDict","super","bubbles","composed","escapeHtml","str","Escape","String","replace","c","html","strings","values","result","reduce","acc","i","v","__raw","toString","nothing","_stringsCache","WeakMap","renderTemplate","_tplStrings","_tplParts","needsFullRender","length","isRaw","newRendered","_tplValues","textNode","textContent","patchTextNodes","renderedValues","map","processedStrings","get","Array","from","s","set","markup","out","trim","replaceChildren","ownerDocument","createRange","createContextualFragment","renderHtml","escapedValues","parts","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","_applyRender","text","observedAttributes","props","render","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","queueMicrotask","_props","hasElementSelector","attrValue","_events","events","forEach","e","addEventListener","args","disconnectedCallback","removeEventListener","handleEvent","event","includes","stopPropagation","dispatchEvent","cancelable","old","proto","properties","syncToElement","Object","defineProperty","configurable","enumerable","undefined","Map","isConnected","setProps","prototype","tagName","_tagName","define","Element","window","customElements"],"mappings":"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"}
package/dist/elena.js CHANGED
@@ -1,2 +1,2 @@
1
- import{setProps as e,getProps as t,getPropValue as s,syncAttribute as n}from"./props.js";import{ElenaEvent as i}from"./events.js";import{defineElement as r}from"./utils.js";export{html,nothing}from"./utils.js";import{renderTemplate as h}from"./render.js";function o(o,l){const a=l&&l.element?/^[a-z][a-z0-9-]*$/i.test(l.element)?e=>e.getElementsByClassName(l.element)[0]:e=>e.querySelector(l.element):e=>e.firstElementChild;class d extends o{element=null;attributeChangedCallback(e,s,n){"text"!==e?(t(this,e,s,n),this._hydrated&&s!==n&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)):this.text=n??""}static get observedAttributes(){return[...l&&l.props?l.props:[],"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&h(this,e.strings,e.values)}connectedCallback(){this._captureText(),this._applyRender(),this._resolveInnerElement(),this._flushProps(),this._delegateEvents(),this.updated()}_captureText(){if(!this._hydrated&&!this._text){const e=this.textContent.trim();e?this.text=e:queueMicrotask(()=>{this._text||(this.text=this.textContent.trim())})}}_resolveInnerElement(){this.element||(this.element=a(this),this.element||(l&&l.element&&console.warn("░█ [ELENA]: No element found, using firstElementChild as fallback."),this.element=this.firstElementChild))}_flushProps(){if(this._props){const e=!(!l||!l.element);for(const[t,i]of this._props){const r=s(typeof i,i,"toAttribute");n(this,t,r),e&&n(this.element,t,r)}}}_delegateEvents(){!this._events&&l&&l.events&&(this.element?(this._events=!0,l.events?.forEach(e=>{this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)})):console.warn("░█ [ELENA]: Cannot delegate events, no inner element found. Ensure the component renders an element or check your element selector."))}updated(){this._hydrated||(this._hydrated=!0,this.classList.add("elena-hydrated"))}disconnectedCallback(){this._events&&(this._events=!1,l.events?.forEach(e=>{this.element?.removeEventListener(e,this)}))}handleEvent(e){l.events?.includes(e.type)&&(e.stopPropagation(),this.dispatchEvent(new i(e.type,{cancelable:!0})))}get text(){return this._text??""}set text(e){const t=this._text;this._text=e,this._hydrated&&t!==e&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}}return l&&l.props?.length&&(l.props.includes("text")&&console.warn('░█ [ELENA]: "text" is a reserved property. Rename your prop to avoid overriding the built-in text content feature.'),e(d.prototype,l.props,!(!l||!l.element))),l&&l.tagName&&(d._tagName=l.tagName),d.define=function(){this._tagName?r(this._tagName,this):console.warn("░█ [ELENA]: define() called without a tagName. Set tagName in your Elena options to register the element.")},d}export{o as Elena};
1
+ import{setProps as e,getProps as t,getPropValue as s,syncAttribute as n}from"./props.js";import{ElenaEvent as i}from"./events.js";import{defineElement as r}from"./utils.js";export{html,nothing}from"./utils.js";import{renderTemplate as h}from"./render.js";function o(o,l){const a=l&&l.element?/^[a-z][a-z0-9-]*$/i.test(l.element)?e=>e.getElementsByClassName(l.element)[0]:e=>e.querySelector(l.element):e=>e.firstElementChild;class d extends o{element=null;attributeChangedCallback(e,s,n){"text"!==e?(t(this,e,s,n),this._hydrated&&s!==n&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)):this.text=n??""}static get observedAttributes(){return[...l&&l.props?l.props:[],"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&h(this,e.strings,e.values)}connectedCallback(){this._captureText(),this._applyRender(),this._resolveInnerElement(),this._flushProps(),this._delegateEvents(),this.updated()}_captureText(){if(!this._hydrated&&!this._text){const e=this.textContent.trim();e?this.text=e:queueMicrotask(()=>{this._text||(this.text=this.textContent.trim())})}}_resolveInnerElement(){this.element||(this.element=a(this),this.element||(l&&l.element&&console.warn("░█ [ELENA]: No element found, using firstElementChild as fallback."),this.element=this.firstElementChild))}_flushProps(){if(this._props){const e=!(!l||!l.element);for(const[t,i]of this._props){const r=s(typeof i,i,"toAttribute");n(this,t,r),e&&n(this.element,t,r)}}}_delegateEvents(){!this._events&&l&&l.events&&(this.element?(this._events=!0,l.events?.forEach(e=>{this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)})):console.warn("░█ [ELENA]: Cannot delegate events, no inner element found. Ensure the component renders an element or check your element selector."))}updated(){this._hydrated||(this._hydrated=!0,this.setAttribute("hydrated",""))}disconnectedCallback(){this._events&&(this._events=!1,l.events?.forEach(e=>{this.element?.removeEventListener(e,this)}))}handleEvent(e){l.events?.includes(e.type)&&(e.stopPropagation(),this.dispatchEvent(new i(e.type,{cancelable:!0})))}get text(){return this._text??""}set text(e){const t=this._text;this._text=e,this._hydrated&&t!==e&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}}return l&&l.props?.length&&(l.props.includes("text")&&console.warn('░█ [ELENA]: "text" is a reserved property. Rename your prop to avoid overriding the built-in text content feature.'),e(d.prototype,l.props,!(!l||!l.element))),l&&l.tagName&&(d._tagName=l.tagName),d.define=function(){this._tagName?r(this._tagName,this):console.warn("░█ [ELENA]: define() called without a tagName. Set tagName in your Elena options to register the element.")},d}export{o as Elena};
2
2
  //# sourceMappingURL=elena.js.map
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, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, nothing };\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 if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\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 Composite Components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\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 const props = options && options.props ? options.props : [];\n return [...props, \"text\"];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * Return an `html` tagged template literal.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Calls render() and applies the result to\n * the DOM via renderTemplate().\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n if (result && result.strings) {\n renderTemplate(this, result.strings, result.values);\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this._captureText();\n this._applyRender();\n this._resolveInnerElement();\n this._flushProps();\n this._delegateEvents();\n this.updated();\n }\n\n /**\n * Capture textContent from the light DOM before\n * the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated) {\n if (!this._text) {\n const text = this.textContent.trim();\n if (text) {\n this.text = text;\n } else {\n queueMicrotask(() => {\n if (!this._text) {\n this.text = this.textContent.trim();\n }\n });\n }\n }\n }\n }\n\n /**\n * Resolve the inner element reference via\n * the pre-compiled selector.\n *\n * @internal\n */\n _resolveInnerElement() {\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 // Composite 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\n /**\n * Flush props set before connection to host and\n * the inner element attributes.\n *\n * @internal\n */\n _flushProps() {\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\n /**\n * Set up event delegation from inner element to host.\n *\n * @internal\n */\n _delegateEvents() {\n if (!this._events && options && options.events) {\n if (!this.element) {\n console.warn(\n \"░█ [ELENA]: Cannot delegate events, no inner element found. \" +\n \"Ensure the component renders an element or check your element selector.\"\n );\n } else {\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 }\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 * The text content of the element, captured from light DOM\n * before the first render. Setting this property triggers\n * a re-render.\n *\n * @type {string}\n */\n get text() {\n return this._text ?? \"\";\n }\n\n set text(value) {\n const old = this._text;\n this._text = value;\n if (this._hydrated && old !== value && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\n }\n }\n }\n\n if (options && options.props?.length) {\n if (options.props.includes(\"text\")) {\n console.warn(\n '░█ [ELENA]: \"text\" is a reserved property. ' +\n \"Rename your prop to avoid overriding the built-in text content feature.\"\n );\n }\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 } else {\n console.warn(\n \"░█ [ELENA]: define() called without a tagName. \" +\n \"Set tagName in your Elena options to register the element.\"\n );\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","_applyRender","text","observedAttributes","props","render","result","strings","renderTemplate","values","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","textContent","trim","queueMicrotask","console","warn","_props","hasElementSelector","value","attrValue","getPropValue","syncAttribute","_events","events","forEach","e","addEventListener","args","classList","add","disconnectedCallback","removeEventListener","handleEvent","event","includes","type","stopPropagation","dispatchEvent","ElenaEvent","cancelable","old","length","setProps","prototype","tagName","_tagName","define","defineElement"],"mappings":"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"}
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, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, nothing };\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 if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\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 Composite Components without an inner element ref also re-render correctly.\n if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\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 const props = options && options.props ? options.props : [];\n return [...props, \"text\"];\n }\n\n /**\n * Override in a subclass to define the element's HTML structure.\n * Return an `html` tagged template literal.\n * No-op by default: elements without a render method connect safely.\n */\n render() {}\n\n /**\n * Calls render() and applies the result to\n * the DOM via renderTemplate().\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n if (result && result.strings) {\n renderTemplate(this, result.strings, result.values);\n }\n }\n\n /**\n * This method is called each time the Elena Element\n * is added to the document.\n */\n connectedCallback() {\n this._captureText();\n this._applyRender();\n this._resolveInnerElement();\n this._flushProps();\n this._delegateEvents();\n this.updated();\n }\n\n /**\n * Capture textContent from the light DOM before\n * the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated) {\n if (!this._text) {\n const text = this.textContent.trim();\n if (text) {\n this.text = text;\n } else {\n queueMicrotask(() => {\n if (!this._text) {\n this.text = this.textContent.trim();\n }\n });\n }\n }\n }\n }\n\n /**\n * Resolve the inner element reference via\n * the pre-compiled selector.\n *\n * @internal\n */\n _resolveInnerElement() {\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 // Composite 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\n /**\n * Flush props set before connection to host and\n * the inner element attributes.\n *\n * @internal\n */\n _flushProps() {\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\n /**\n * Set up event delegation from inner element to host.\n *\n * @internal\n */\n _delegateEvents() {\n if (!this._events && options && options.events) {\n if (!this.element) {\n console.warn(\n \"░█ [ELENA]: Cannot delegate events, no inner element found. \" +\n \"Ensure the component renders an element or check your element selector.\"\n );\n } else {\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 }\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.setAttribute(\"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 * The text content of the element, captured from light DOM\n * before the first render. Setting this property triggers\n * a re-render.\n *\n * @type {string}\n */\n get text() {\n return this._text ?? \"\";\n }\n\n set text(value) {\n const old = this._text;\n this._text = value;\n if (this._hydrated && old !== value && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\n }\n }\n }\n\n if (options && options.props?.length) {\n if (options.props.includes(\"text\")) {\n console.warn(\n '░█ [ELENA]: \"text\" is a reserved property. ' +\n \"Rename your prop to avoid overriding the built-in text content feature.\"\n );\n }\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 } else {\n console.warn(\n \"░█ [ELENA]: define() called without a tagName. \" +\n \"Set tagName in your Elena options to register the element.\"\n );\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","_applyRender","text","observedAttributes","props","render","result","strings","renderTemplate","values","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","textContent","trim","queueMicrotask","console","warn","_props","hasElementSelector","value","attrValue","getPropValue","syncAttribute","_events","events","forEach","e","addEventListener","args","setAttribute","disconnectedCallback","removeEventListener","handleEvent","event","includes","type","stopPropagation","dispatchEvent","ElenaEvent","cancelable","old","length","setProps","prototype","tagName","_tagName","define","defineElement"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elenajs/core",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Elena is a lightweight, opinionated library for building progressive web components.",
5
5
  "author": "Elena <hi@elenajs.com>",
6
6
  "homepage": "https://elenajs.com/",
@@ -36,5 +36,5 @@
36
36
  "typescript": "5.9.3",
37
37
  "vitest": "4.0.18"
38
38
  },
39
- "gitHead": "056c6a4cf9c5c95b2fc99d99fb4c5d5fb49d9af2"
39
+ "gitHead": "323fcac2e1d15f81e7b4a039357c1f2e3e8f3030"
40
40
  }