@elenajs/core 0.15.0 → 1.0.0-alpha.2

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}}function i(e){return{__raw:!0,toString:()=>e??""}}const o={__raw:!0,toString:()=>""},a=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,a=o?String(i):s(String(i??""));if(a!==e._tplValues[t])if(e._tplValues[t]=a,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=a.get(t);i||(i=Array.from(t,e=>e.replace(/\n\s*/g," ")),a.set(t,i));const o=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,o),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 c(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,o=r&&r.props?r.props:[],a=[],c=new Set;for(const e of o)"string"==typeof e?a.push(e):(a.push(e.name),!1===e.reflect&&c.add(e.name));class u 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[...a,"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&(l(this,e.strings,e.values),this._hydrated&&(this.element=i(this)))}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)for(const[n,s]of this._props){if(c.has(n))continue;t(this,n,e(typeof s,s,"toAttribute"))}}_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.setAttribute("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 a.length&&(a.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){for(const i of s){const s=!r||!r.has(i);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)&&(this._props.set(i,n),this.isConnected))if(s){const s=e(typeof n,n,"toAttribute");t(this,i,s)}else this._hydrated&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}})}}(u.prototype,a,c)),r&&r.tagName&&(u._tagName=r.tagName),u.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.")},u}export{c as Elena,r as html,o as nothing,i as unsafeHTML};
1
+ function t(t,e,s){if(e="boolean"===t&&"boolean"!=typeof e?null!==e:e,!s)return e;if("toAttribute"===s)switch(t){case"object":case"array":return null===e?null:JSON.stringify(e);case"boolean":return e?"":null;case"number":return null===e?null:e;default:return""===e?null:e}else switch(t){case"object":case"array":if(!e)return e;try{return JSON.parse(e)}catch{return console.warn("░█ [ELENA]: Invalid JSON: "+e),null}case"boolean":return e;case"number":return null!==e?+e:e;default:return e??""}}function e(t,e,s){t?null===s?t.removeAttribute(e):t.setAttribute(e,s):console.warn("░█ [ELENA]: Cannot sync attrs.")}function s(t){return Array.isArray(t)?t.map(t=>n(t)).join(""):n(t)}function n(t){return t?.t?String(t):function(t){const e={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return String(t).replace(/[&<>"']/g,t=>e[t])}(String(t??""))}function i(t,...e){let n;return{t:!0,strings:t,values:e,toString:()=>(void 0===n&&(n=t.reduce((t,n,i)=>t+n+s(e[i]),"")),n)}}function r(t){return{t:!0,toString:()=>t??""}}const o=Object.freeze({t:!0,toString:()=>""}),h=t=>Array.isArray(t)?t.some(t=>t?.t):t?.t,c=t=>Array.isArray(t)?t.map(t=>String(t??"")).join(""):String(t??"");function u(t){return t.replace(/>\n\s*/g,">").replace(/\n\s*</g,"<").replace(/\n\s*/g," ")}const l=new WeakMap,a="e"+Math.random().toString(36).slice(2,6);function f(t,e,n){return!function(t,e,s){if(t.i!==e||!t.o)return!1;for(let e=0;e<s.length;e++){const n=s[e],i=Array.isArray(n)?c(n):n;if(i!==t.h[e]){if(h(n)||!t.o[e])return!1;t.h[e]=i,t.o[e].textContent=c(n)}}return!0}(t,e,n)&&(function(t,e,n){let i=l.get(e);if(!i){const t=Array.from(e,u);i={processedStrings:t,template:n.length>0?d(t,n.length):null},l.set(e,i)}if(i.template)t.o=function(t,e,n){const i=e.content.cloneNode(!0),r=document.createTreeWalker(i,NodeFilter.SHOW_COMMENT),o=new Array(n.length),u=[];let l;for(;l=r.nextNode();)l.data===a&&u.push(l);for(let t=0;t<u.length;t++){const e=n[t];if(h(e)){const n=document.createElement("template");n.innerHTML=s(e),u[t].parentNode.replaceChild(n.content,u[t])}else{const s=document.createTextNode(c(e));u[t].parentNode.replaceChild(s,u[t]),o[t]=s}}return t.replaceChildren(i),o}(t,i.template,n);else{const e=n.map(t=>s(t)),r=i.processedStrings.reduce((t,s,n)=>t+s+(e[n]??""),"").replace(/>\s+</g,"><").trim(),o=document.createElement("template");o.innerHTML=r,p(t,o.content.childNodes),t.o=new Array(n.length)}t.i=e,t.h=n.map(t=>Array.isArray(t)?c(t):t)}(t,e,n),!0)}function d(t,e){const s=`\x3c!--${a}--\x3e`,n=t.reduce((t,n,i)=>t+n.replace(/>\s+</g,"><")+(i<e?s:""),"").trim(),i=document.createElement("template");i.innerHTML=n;const r=document.createTreeWalker(i.content,NodeFilter.SHOW_COMMENT);let o=0;for(;r.nextNode();)r.currentNode.data===a&&o++;return o===e?i:null}function p(t,e){const s=Array.from(t.childNodes),n=Array.from(e),i=Math.max(s.length,n.length);for(let e=0;e<i;e++){const i=s[e],r=n[e];i?r?i.nodeType!==r.nodeType||i.nodeType===Node.ELEMENT_NODE&&i.tagName!==r.tagName?t.replaceChild(r,i):i.nodeType===Node.TEXT_NODE?i.textContent!==r.textContent&&(i.textContent=r.textContent):i.nodeType===Node.ELEMENT_NODE&&(y(i,r),p(i,r.childNodes)):t.removeChild(i):t.appendChild(r)}}function y(t,e){for(let s=t.attributes.length-1;s>=0;s--){const{name:n}=t.attributes[s];e.hasAttribute(n)||t.removeAttribute(n)}for(let s=0;s<e.attributes.length;s++){const{name:n,value:i}=e.attributes[s];t.getAttribute(n)!==i&&t.setAttribute(n,i)}}class g extends Event{constructor(t,e){super(t,{bubbles:!0,composed:!0,...e})}}const m=new WeakSet;function b(s){return class extends s{element=null;attributeChangedCallback(e,s,n){super.attributeChangedCallback?.(e,s,n),"text"!==e?(this.u=!0,function(e,s,n,i){if(n!==i){const n=typeof e[s];"undefined"===n&&console.warn(`░█ [ELENA]: Prop "${s}" has no default.`);const r=t(n,i,"toProp");e[s]=r}}(this,e,s,n),this.u=!1,this.l&&s!==n&&!this.p&&this.m()):this.text=n??""}static get observedAttributes(){if(this.A)return this.A;const t=this.S||(this.props||[]).map(t=>"string"==typeof t?t:t.name);return this.A=[...t,"text"],this.A}connectedCallback(){super.connectedCallback?.(),this.N(),this._(),this.v(),this.C(),this.willUpdate(),this.L(),this.k(),this.O(),this.P(),this.l||(this.l=!0,this.setAttribute("hydrated",""),this.firstUpdated()),this.updated()}N(){const s=this.constructor;if(m.has(s))return;const n=new Set,i=[];if(s.props){for(const t of s.props)"string"==typeof t?i.push(t):(i.push(t.name),!1===t.reflect&&n.add(t.name));i.includes("text")&&console.warn('░█ [ELENA]: "text" is reserved.'),function(s,n,i){for(const r of n){const n=!i||!i.has(r);Object.defineProperty(s,r,{configurable:!0,enumerable:!0,get(){return this.j?this.j.get(r):void 0},set(s){if(this.j||(this.j=new Map),s!==this.j.get(r)&&(this.j.set(r,s),this.isConnected))if(n){if(!this.u){const n=t(typeof s,s,"toAttribute");e(this,r,n)}}else this.l&&!this.p&&this.m()}})}}(s.prototype,i,n)}var r;s.S=i,s.M=n,s.U=s.events||null,s.q=(r=s.element)?t=>t.querySelector(r):t=>t.firstElementChild,m.add(s)}_(){this.u=!0;for(const t of this.constructor.S)if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}this.u=!1}v(){this.l||void 0!==this.F||(this.text=this.textContent.trim())}get J(){return this.R??this.shadowRoot??this}C(){const t=this.constructor;if(!t.shadow)return;(this.R??this.shadowRoot)||(this.R=this.attachShadow({mode:t.shadow}));const e=this.R??this.shadowRoot;if(t.styles){if(!t.I){const e=Array.isArray(t.styles)?t.styles:[t.styles];t.I=e.map(t=>{if("string"==typeof t){const e=new CSSStyleSheet;return e.replaceSync(t),e}return t})}e.adoptedStyleSheets=t.I}}L(){const t=this.render();if(t&&t.strings){const e=this.J,s=f(e,t.strings,t.values);this.l&&s&&(this.element=this.constructor.q(e))}}k(){if(!this.element){const t=this.J;this.element=this.constructor.q(t),this.element||(this.constructor.element&&console.warn("░█ [ELENA]: Element not found."),this.element=t.firstElementChild)}}O(){if(this.j){const s=this.constructor.M;for(const[n,i]of this.j){if(s.has(n))continue;const r=t(typeof i,i,"toAttribute");(null!==r||this.hasAttribute(n))&&e(this,n,r)}}}P(){const t=this.constructor.U;if(!this.W&&t?.length)if(this.element){this.W=!0;for(const e of t)this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)}else console.warn("░█ [ELENA]: Cannot add events.")}render(){}willUpdate(){}firstUpdated(){}updated(){}adoptedCallback(){super.adoptedCallback?.()}disconnectedCallback(){if(super.disconnectedCallback?.(),this.W){this.W=!1;for(const t of this.constructor.U)this.element?.removeEventListener(t,this)}}handleEvent(t){this.constructor.U?.includes(t.type)&&(t.stopPropagation(),this.dispatchEvent(new g(t.type,{cancelable:!0})))}get text(){return this.F??""}set text(t){const e=this.F;this.F=t,this.l&&e!==t&&!this.p&&this.m()}static define(){var t,e;this.tagName?(t=this.tagName,e=this,"undefined"!=typeof window&&"customElements"in window&&(window.customElements.get(t)||window.customElements.define(t,e))):console.warn("░█ [ELENA]: define() without a tagName.")}m(){this.p||this.$||(this.$=!0,this.D=new Promise(t=>{this.T=t}),queueMicrotask(()=>{try{this.B()}catch(t){console.error("░█ [ELENA]:",t)}}))}B(){this.$=!1;const t=this.T;this.T=null;try{try{this.willUpdate(),this.p=!0,this.L()}finally{this.p=!1}this.updated()}finally{this.D=null,t()}}get updateComplete(){return this.D?this.D:Promise.resolve()}requestUpdate(){this.l&&!this.p&&this.m()}}}export{b as Elena,g as ElenaEvent,i as html,o as nothing,r as unsafeHTML};
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[]} propNames - Prop names to define\n * @param {Set<string>} [noReflect] - Props that should not reflect to attributes\n */\nexport function setProps(proto, propNames, noReflect) {\n for (const prop of propNames) {\n const reflects = !noReflect || !noReflect.has(prop);\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 if (reflects) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n } else if (this._hydrated && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\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 * Renders a string as HTML rather than text.\n *\n * @param {string} str - The raw HTML string to trust.\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function unsafeHTML(str) {\n return { __raw: true, toString: () => str ?? \"\" };\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 renders it.\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 (or undefined)\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 // created 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 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 // No mapped text node for this value, 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 without a matching\n * text node will be undefined.\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, unsafeHTML, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, unsafeHTML, nothing };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {(string | {name: string, reflect?: boolean})[]} [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 * @typedef {{ text: string, element: HTMLElement | null, render(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers\n */\n\n/**\n * @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & { define(): void, readonly observedAttributes: string[] }} ElenaElementConstructor\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 {ElenaElementConstructor} 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 * Normalize prop definitions into a names array and a\n * set of non-reflecting prop names. Props can be plain\n * strings or objects with { name, reflect? } shape.\n */\n const rawProps = options && options.props ? options.props : [];\n const propNames = [];\n const noReflect = new Set();\n for (const p of rawProps) {\n if (typeof p === \"string\") {\n propNames.push(p);\n } else {\n propNames.push(p.name);\n if (p.reflect === false) {\n noReflect.add(p.name);\n }\n }\n }\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 return [...propNames, \"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 // Re-resolve element ref after render in case the DOM was rebuilt.\n if (this._hydrated) {\n this.element = resolveElement(this);\n }\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 // Angular sets textContent after the element connects,\n // so we defer capture to the next microtask to pick it up.\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 for (const [prop, value] of this._props) {\n if (noReflect.has(prop)) {\n continue;\n }\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\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 (propNames.length) {\n if (propNames.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, propNames, noReflect);\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","unsafeHTML","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","rawProps","props","propNames","noReflect","Set","p","push","reflect","add","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","context","newAttr","getProps","this","_hydrated","_isRendering","_applyRender","text","observedAttributes","render","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","queueMicrotask","_props","has","_events","events","forEach","e","addEventListener","args","disconnectedCallback","removeEventListener","handleEvent","event","includes","stopPropagation","dispatchEvent","cancelable","old","proto","reflects","Object","defineProperty","configurable","enumerable","undefined","Map","isConnected","attrValue","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/utils.js","../src/common/render.js","../src/common/events.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: \" + 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 attrs.\");\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[]} propNames - Prop names to define\n * @param {Set<string>} [noReflect] - Props that should not reflect to attributes\n */\nexport function setProps(proto, propNames, noReflect) {\n for (const prop of propNames) {\n const reflects = !noReflect || !noReflect.has(prop);\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 if (reflects) {\n // Skip reflection when called from attributeChangedCallback. The\n // attribute is already at the new value, setting it again is redundant\n // and would fire an extra attributeChangedCallback with identical values.\n if (!this._syncing) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n }\n } else if (this._hydrated && !this._isRendering) {\n this._safeRender();\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(`░█ [ELENA]: Prop \"${name}\" has no default.`);\n }\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\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 * Resolve an interpolated template value to its\n * HTML string representation.\n *\n * @param {*} value\n * @returns {string}\n */\nexport function resolveValue(value) {\n if (Array.isArray(value)) {\n return value.map(item => resolveItem(item)).join(\"\");\n }\n return resolveItem(value);\n}\n\n/**\n * Resolve a single value to its HTML string\n * representation.\n *\n * @param {*} value\n * @returns {string}\n */\nfunction resolveItem(value) {\n return value?.__raw ? String(value) : escapeHtml(String(value ?? \"\"));\n}\n\n/**\n * Tagged template for trusted HTML. Use as the return value\n * of render(), or for sub-fragments inside render methods.\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 let str;\n return {\n __raw: true,\n strings,\n values,\n toString: () => {\n if (str === undefined) {\n str = strings.reduce((acc, s, i) => {\n return acc + s + resolveValue(values[i]);\n }, \"\");\n }\n return str;\n },\n };\n}\n\n/**\n * Renders a string as HTML rather than text.\n *\n * @param {string} str - The raw HTML string to trust.\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function unsafeHTML(str) {\n return { __raw: true, toString: () => str ?? \"\" };\n}\n\n/**\n * A placeholder you can return from a conditional expression\n * inside a template to render nothing.\n *\n * @type {{ __raw: true, toString(): string }}\n */\nexport const nothing = Object.freeze({ __raw: true, toString: () => \"\" });\n\n/**\n * Check if a value contains trusted HTML fragments.\n *\n * @param {*} value\n * @returns {boolean}\n */\nexport const isRaw = value =>\n Array.isArray(value) ? value.some(item => item?.__raw) : value?.__raw;\n\n/**\n * Convert a value to its plain text string.\n *\n * @param {*} value\n * @returns {string}\n */\nexport const toPlainText = value =>\n Array.isArray(value) ? value.map(item => String(item ?? \"\")).join(\"\") : String(value ?? \"\");\n\n/**\n * Collapse whitespace from a static string part.\n *\n * @param {string} string\n * @returns {string}\n */\nexport function collapseWhitespace(string) {\n return string\n .replace(/>\\n\\s*/g, \">\") // newline after tag close\n .replace(/\\n\\s*</g, \"<\") // newline before tag open\n .replace(/\\n\\s*/g, \" \"); // newline in text content, preserve word boundary\n}\n","import { collapseWhitespace, isRaw, resolveValue, toPlainText } from \"./utils.js\";\n\nconst stringsCache = new WeakMap();\nconst markerKey = \"e\" + Math.random().toString(36).slice(2, 6);\n\n/**\n * Render a tagged template into an Elena Element with DOM diffing.\n * Returns true if the DOM was fully rebuilt, false if only text\n * nodes were patched in place.\n *\n * @param {HTMLElement} element\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n * @returns {boolean}\n */\nexport function renderTemplate(element, strings, values) {\n if (patchTextNodes(element, strings, values)) {\n return false;\n }\n fullRender(element, strings, values);\n return true;\n}\n\n/**\n * Fast path: patch only the text nodes whose values changed.\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 for (let i = 0; i < values.length; i++) {\n const v = values[i];\n const comparable = Array.isArray(v) ? toPlainText(v) : v;\n\n if (comparable === element._tplValues[i]) {\n continue;\n }\n\n if (isRaw(v) || !element._tplParts[i]) {\n return false;\n }\n\n element._tplValues[i] = comparable;\n element._tplParts[i].textContent = toPlainText(v);\n }\n\n return true;\n}\n\n/**\n * Cold path: clone a cached <template> and patch in values.\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 let entry = stringsCache.get(strings);\n\n if (!entry) {\n const processedStrings = Array.from(strings, collapseWhitespace);\n entry = {\n processedStrings,\n template: values.length > 0 ? createTemplate(processedStrings, values.length) : null,\n };\n stringsCache.set(strings, entry);\n }\n\n if (entry.template) {\n element._tplParts = cloneAndPatch(element, entry.template, values);\n } else {\n // Fallback for attribute-position values or static templates.\n // White space collapsing here protects against Vue SSR mismatches.\n const renderedValues = values.map(value => resolveValue(value));\n const markup = entry.processedStrings\n .reduce((out, str, i) => out + str + (renderedValues[i] ?? \"\"), \"\")\n .replace(/>\\s+</g, \"><\")\n .trim();\n\n // Morph existing DOM to match new markup instead of replacing it.\n const tpl = document.createElement(\"template\");\n tpl.innerHTML = markup;\n morphContent(element, tpl.content.childNodes);\n element._tplParts = new Array(values.length);\n }\n\n element._tplStrings = strings;\n element._tplValues = values.map(v => (Array.isArray(v) ? toPlainText(v) : v));\n}\n\n/**\n * Build a <template> element with comment markers.\n *\n * @param {string[]} processedStrings - Whitespace-collapsed static parts\n * @param {number} valueCount - Number of dynamic values\n * @returns {HTMLTemplateElement | null}\n */\nfunction createTemplate(processedStrings, valueCount) {\n const marker = `<!--${markerKey}-->`;\n const markup = processedStrings\n .reduce((out, str, i) => {\n const collapsed = str.replace(/>\\s+</g, \"><\");\n return out + collapsed + (i < valueCount ? marker : \"\");\n }, \"\")\n .trim();\n\n const tpl = document.createElement(\"template\");\n tpl.innerHTML = markup;\n\n // Mismatch means this template shape cannot use the clone path.\n const walker = document.createTreeWalker(tpl.content, NodeFilter.SHOW_COMMENT);\n let count = 0;\n\n while (walker.nextNode()) {\n if (walker.currentNode.data === markerKey) {\n count++;\n }\n }\n\n return count === valueCount ? tpl : null;\n}\n\n/**\n * Clone a cached template and replace comment markers\n * with actual content.\n *\n * @param {HTMLElement} element - The host element to render into\n * @param {HTMLTemplateElement} template - Cached template with markers\n * @param {Array} values - Raw interpolated values\n * @param {string[]} renderedValues - HTML-escaped rendered values\n * @returns {Array<Text | undefined>} Text node map for fast-path patching\n */\nfunction cloneAndPatch(element, template, values) {\n const clone = template.content.cloneNode(true);\n const walker = document.createTreeWalker(clone, NodeFilter.SHOW_COMMENT);\n const parts = new Array(values.length);\n const markers = [];\n let node;\n\n // Collect markers before modifying the tree\n while ((node = walker.nextNode())) {\n if (node.data === markerKey) {\n markers.push(node);\n }\n }\n\n for (let i = 0; i < markers.length; i++) {\n const value = values[i];\n\n if (isRaw(value)) {\n // Raw HTML: parse and insert as fragment\n const tmp = document.createElement(\"template\");\n tmp.innerHTML = resolveValue(value);\n markers[i].parentNode.replaceChild(tmp.content, markers[i]);\n\n // Raw values can't be fast-patched; leave parts undefined\n } else {\n // Create text node with unescaped content\n const textNode = document.createTextNode(toPlainText(value));\n markers[i].parentNode.replaceChild(textNode, markers[i]);\n parts[i] = textNode;\n }\n }\n\n element.replaceChildren(clone);\n return parts;\n}\n\n/**\n * Patches attributes and text content in-place when structure is stable,\n * preserving element identity and focus state across re-renders.\n *\n * @param {Node} parent\n * @param {NodeList} nextNodes - The desired child nodes from the new render\n */\nfunction morphContent(parent, nextNodes) {\n const current = Array.from(parent.childNodes);\n const next = Array.from(nextNodes);\n const len = Math.max(current.length, next.length);\n\n for (let i = 0; i < len; i++) {\n const cur = current[i];\n const nxt = next[i];\n\n if (!cur) {\n parent.appendChild(nxt);\n } else if (!nxt) {\n parent.removeChild(cur);\n } else if (\n cur.nodeType !== nxt.nodeType ||\n (cur.nodeType === Node.ELEMENT_NODE && cur.tagName !== nxt.tagName)\n ) {\n parent.replaceChild(nxt, cur);\n } else if (cur.nodeType === Node.TEXT_NODE) {\n if (cur.textContent !== nxt.textContent) {\n cur.textContent = nxt.textContent;\n }\n } else if (cur.nodeType === Node.ELEMENT_NODE) {\n morphAttributes(cur, nxt);\n morphContent(cur, nxt.childNodes);\n }\n }\n}\n\n/**\n * Morhp element’s attributes without rebuilding the DOM.\n *\n * @param {Element} current - The current existing DOM element\n * @param {Element} next - The desired element from the new render\n */\nfunction morphAttributes(current, next) {\n for (let i = current.attributes.length - 1; i >= 0; i--) {\n const { name } = current.attributes[i];\n\n if (!next.hasAttribute(name)) {\n current.removeAttribute(name);\n }\n }\n\n for (let i = 0; i < next.attributes.length; i++) {\n const { name, value } = next.attributes[i];\n\n if (current.getAttribute(name) !== value) {\n current.setAttribute(name, value);\n }\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 * ██████████ ████\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 { defineElement, html, unsafeHTML, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\nimport { ElenaEvent } from \"./common/events.js\";\n\nexport { html, unsafeHTML, nothing, ElenaEvent };\n\n/**\n * Returns a function that finds the inner element using the given selector.\n * Built once per component class to avoid repeated work.\n *\n * - No selector: uses firstElementChild\n * - Any string: uses querySelector\n *\n * @param {string | undefined} selector\n * @returns {(host: HTMLElement) => HTMLElement | null}\n */\nfunction elementResolver(selector) {\n if (!selector) {\n return host => host.firstElementChild;\n }\n return host => host.querySelector(selector);\n}\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * @typedef {{ text: string, element: HTMLElement | null, render(): void, willUpdate(): void, firstUpdated(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers\n */\n\n/**\n * @typedef {{ name: string, reflect?: boolean }} ElenaPropObject\n */\n\n/**\n * @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & {\n * define(): void,\n * readonly observedAttributes: string[],\n * tagName?: string,\n * props?: (string | ElenaPropObject)[],\n * events?: string[],\n * element?: string,\n * shadow?: \"open\" | \"closed\",\n * styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[],\n * }} ElenaElementConstructor\n */\n\n// Tracks which component classes have already been set up.\nconst setupRegistry = new WeakSet();\n\n/**\n * Creates an Elena component class by extending `superClass`.\n *\n * Adds rendering, props, and event handling to your component.\n * Configure it using static class fields: `static tagName`,\n * `static props`, `static events`, and `static element`.\n *\n * @param {ElenaConstructor} superClass - The base class to extend (usually `HTMLElement`).\n * @returns {ElenaElementConstructor} A class ready to be defined as a custom element.\n */\nexport function Elena(superClass) {\n /**\n * The base Elena element class with all built-in behavior.\n */\n class ElenaElement extends superClass {\n /**\n * The inner element rendered by this component.\n *\n * @type {HTMLElement | null}\n */\n element = null;\n\n /**\n * Called by the browser when an observed attribute changes.\n * Updates the matching prop and re-renders if needed.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n super.attributeChangedCallback?.(prop, oldValue, newValue);\n\n if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\n // Set flag so the property setter skips redundant attribute reflection:\n // the attribute is already at the new value, no need to set it again.\n this._syncing = true;\n getProps(this, prop, oldValue, newValue);\n this._syncing = false;\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 if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._safeRender();\n }\n }\n\n /**\n * Lists the attributes Elena watches for changes.\n * Reads from the subclass’s `static props` field.\n */\n static get observedAttributes() {\n if (this._observedAttrs) {\n return this._observedAttrs;\n }\n\n const propNames =\n this._propNames || (this.props || []).map(p => (typeof p === \"string\" ? p : p.name));\n this._observedAttrs = [...propNames, \"text\"];\n return this._observedAttrs;\n }\n\n /**\n * Called by the browser each time the element is added to the page.\n */\n connectedCallback() {\n super.connectedCallback?.();\n this._setupStaticProps();\n this._captureClassFieldDefaults();\n this._captureText();\n this._attachShadow();\n this.willUpdate();\n this._applyRender();\n this._resolveInnerElement();\n this._syncProps();\n this._delegateEvents();\n if (!this._hydrated) {\n this._hydrated = true;\n this.setAttribute(\"hydrated\", \"\");\n this.firstUpdated();\n }\n this.updated();\n }\n\n /**\n * Sets up props, events, and the element selector once per component class.\n * Runs the first time an instance of a given class connects to the page.\n *\n * @internal\n */\n _setupStaticProps() {\n const component = this.constructor;\n\n if (setupRegistry.has(component)) {\n return;\n }\n\n // Props with reflect: false\n const noRef = new Set();\n const names = [];\n\n if (component.props) {\n for (const p of component.props) {\n if (typeof p === \"string\") {\n names.push(p);\n } else {\n names.push(p.name);\n\n if (p.reflect === false) {\n noRef.add(p.name);\n }\n }\n }\n\n if (names.includes(\"text\")) {\n console.warn('░█ [ELENA]: \"text\" is reserved.');\n }\n\n setProps(component.prototype, names, noRef);\n }\n\n component._propNames = names;\n component._noReflect = noRef;\n component._elenaEvents = component.events || null;\n component._resolver = elementResolver(component.element);\n setupRegistry.add(component);\n }\n\n /**\n * Moves class field defaults into Elena’s internal props store\n * so that getters and setters work correctly.\n *\n * @internal\n */\n _captureClassFieldDefaults() {\n this._syncing = true;\n\n for (const name of this.constructor._propNames) {\n if (Object.prototype.hasOwnProperty.call(this, name)) {\n const value = this[name];\n delete this[name];\n this[name] = value;\n }\n }\n\n this._syncing = false;\n }\n\n /**\n * Saves any text inside the element before the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated && this._text === undefined) {\n this.text = this.textContent.trim();\n }\n }\n\n /**\n * The root node to render into. Returns the shadow root when shadow mode\n * is enabled, otherwise the host element itself.\n *\n * @type {ShadowRoot | HTMLElement}\n */\n get _renderRoot() {\n return this._shadow ?? this.shadowRoot ?? this;\n }\n\n /**\n * Attaches a shadow root and adopts styles on first connect.\n * Only runs when `static shadow` is set on the component class.\n *\n * @internal\n */\n _attachShadow() {\n const component = this.constructor;\n\n if (!component.shadow) {\n return;\n }\n\n // A shadow root may already exist if Declarative Shadow DOM was used.\n // In that case skip attachShadow() but still adopt styles below.\n // Store the reference so closed shadow roots remain accessible.\n const root = this._shadow ?? this.shadowRoot;\n\n if (!root) {\n this._shadow = this.attachShadow({ mode: component.shadow });\n }\n\n const shadowRoot = this._shadow ?? this.shadowRoot;\n\n if (!component.styles) {\n return;\n }\n\n // Normalize to array and cache converted CSSStyleSheet instances on the class.\n // Avoids re-parsing CSS strings on every element instance.\n if (!component._adoptedSheets) {\n const stylesList = Array.isArray(component.styles) ? component.styles : [component.styles];\n\n component._adoptedSheets = stylesList.map(s => {\n if (typeof s === \"string\") {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(s);\n return sheet;\n }\n return s;\n });\n }\n\n shadowRoot.adoptedStyleSheets = component._adoptedSheets;\n }\n\n /**\n * Calls render() and updates the DOM with the result.\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n\n if (result && result.strings) {\n const root = this._renderRoot;\n const rebuilt = renderTemplate(root, result.strings, result.values);\n\n // Re-resolve element ref only when the DOM was fully rebuilt.\n // Fast-path text node patching leaves the DOM structure intact,\n // so the existing ref is still valid.\n if (this._hydrated && rebuilt) {\n this.element = this.constructor._resolver(root);\n }\n }\n }\n\n /**\n * Finds and stores a reference to the inner element.\n *\n * @internal\n */\n _resolveInnerElement() {\n if (!this.element) {\n const root = this._renderRoot;\n this.element = this.constructor._resolver(root);\n\n if (!this.element) {\n if (this.constructor.element) {\n console.warn(\"░█ [ELENA]: Element not found.\");\n }\n this.element = root.firstElementChild;\n }\n }\n }\n\n /**\n * Syncs any props that were set before the element\n * connected to the page.\n *\n * @internal\n */\n _syncProps() {\n if (this._props) {\n const noReflect = this.constructor._noReflect;\n\n for (const [prop, value] of this._props) {\n if (noReflect.has(prop)) {\n continue;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n\n if (attrValue === null && !this.hasAttribute(prop)) {\n continue;\n }\n\n syncAttribute(this, prop, attrValue);\n }\n }\n }\n\n /**\n * Forwards events from the inner element\n * up to the host element.\n *\n * @internal\n */\n _delegateEvents() {\n const events = this.constructor._elenaEvents;\n\n if (!this._events && events?.length) {\n if (!this.element) {\n console.warn(\"░█ [ELENA]: Cannot add events.\");\n } else {\n this._events = true;\n\n for (const e of events) {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n }\n }\n }\n }\n\n /**\n * Define the element’s HTML here. Return an `html`\n * tagged template. If not overridden, the element connects\n * to the page without rendering anything.\n */\n render() {}\n\n /**\n * Called before every render.\n * Override to prepare state before the template runs.\n */\n willUpdate() {}\n\n /**\n * Called once after the element’s first render.\n * Override to run setup that needs the DOM.\n */\n firstUpdated() {}\n\n /**\n * Called after every render.\n * Override to react to changes.\n */\n updated() {}\n\n /**\n * Called by the browser when the element is moved\n * to a new document via `adoptNode()`.\n */\n adoptedCallback() {\n super.adoptedCallback?.();\n }\n\n /**\n * Called by the browser each time the element\n * is removed from the page.\n */\n disconnectedCallback() {\n super.disconnectedCallback?.();\n if (this._events) {\n this._events = false;\n\n for (const e of this.constructor._elenaEvents) {\n this.element?.removeEventListener(e, this);\n }\n }\n }\n\n /**\n * Receives events from the inner element and\n * re-fires them on the host.\n *\n * @internal\n */\n handleEvent(event) {\n if (this.constructor._elenaEvents?.includes(event.type)) {\n event.stopPropagation();\n\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n\n /**\n * The text content of the element. Elena reads this\n * from the element’s children before the first render.\n * Updating it triggers 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\n if (this._hydrated && old !== value && !this._isRendering) {\n this._safeRender();\n }\n }\n\n /**\n * Registers the component as a custom element using `static tagName`.\n * Call this on your component class after the class body is defined,\n * not on the Elena mixin itself.\n */\n static define() {\n if (this.tagName) {\n defineElement(this.tagName, this);\n } else {\n console.warn(\"░█ [ELENA]: define() without a tagName.\");\n }\n }\n\n /**\n * Schedules a re-render via microtask. If called multiple times\n * before the microtask fires, only one render runs.\n *\n * @internal\n */\n _safeRender() {\n if (this._isRendering) {\n return;\n }\n if (!this._renderPending) {\n this._renderPending = true;\n this._updateComplete = new Promise(resolve => {\n this._resolveUpdate = resolve;\n });\n queueMicrotask(() => {\n try {\n this._performUpdate();\n } catch (e) {\n console.error(\"░█ [ELENA]:\", e);\n }\n });\n }\n }\n\n /**\n * Runs the batched update cycle.\n * Called by the microtask in _safeRender().\n *\n * @internal\n */\n _performUpdate() {\n this._renderPending = false;\n const resolve = this._resolveUpdate;\n this._resolveUpdate = null;\n try {\n try {\n this.willUpdate();\n this._isRendering = true;\n this._applyRender();\n } finally {\n this._isRendering = false;\n }\n this.updated();\n } finally {\n this._updateComplete = null;\n resolve();\n }\n }\n\n /**\n * A Promise that resolves after the render completes.\n * Resolves immediately if no render is scheduled.\n *\n * @type {Promise<void>}\n */\n get updateComplete() {\n if (this._updateComplete) {\n return this._updateComplete;\n }\n return Promise.resolve();\n }\n\n /**\n * Schedules a re-render. Use this to manually trigger an\n * update when Elena cannot detect the change automatically.\n */\n requestUpdate() {\n if (this._hydrated && !this._isRendering) {\n this._safeRender();\n }\n }\n }\n\n return ElenaElement;\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","console","warn","syncAttribute","element","name","removeAttribute","setAttribute","resolveValue","Array","isArray","map","item","resolveItem","join","__raw","String","str","Escape","replace","c","escapeHtml","html","strings","values","toString","undefined","reduce","acc","s","i","unsafeHTML","nothing","Object","freeze","isRaw","some","toPlainText","collapseWhitespace","string","stringsCache","WeakMap","markerKey","Math","random","slice","renderTemplate","_tplStrings","_tplParts","length","v","comparable","_tplValues","textContent","patchTextNodes","entry","get","processedStrings","from","template","createTemplate","set","clone","content","cloneNode","walker","document","createTreeWalker","NodeFilter","SHOW_COMMENT","parts","markers","node","nextNode","data","push","tmp","createElement","innerHTML","parentNode","replaceChild","textNode","createTextNode","replaceChildren","cloneAndPatch","renderedValues","markup","out","trim","tpl","morphContent","childNodes","fullRender","valueCount","marker","count","currentNode","parent","nextNodes","current","next","len","max","cur","nxt","nodeType","Node","ELEMENT_NODE","tagName","TEXT_NODE","morphAttributes","removeChild","appendChild","attributes","hasAttribute","getAttribute","ElenaEvent","Event","constructor","eventInitDict","super","bubbles","composed","setupRegistry","WeakSet","Elena","superClass","attributeChangedCallback","prop","oldValue","newValue","this","_syncing","context","newAttr","getProps","_hydrated","_isRendering","_safeRender","text","observedAttributes","_observedAttrs","propNames","_propNames","props","p","connectedCallback","_setupStaticProps","_captureClassFieldDefaults","_captureText","_attachShadow","willUpdate","_applyRender","_resolveInnerElement","_syncProps","_delegateEvents","firstUpdated","updated","component","has","noRef","Set","names","reflect","add","includes","proto","noReflect","reflects","defineProperty","configurable","enumerable","_props","Map","isConnected","attrValue","setProps","prototype","selector","_noReflect","_elenaEvents","events","_resolver","host","querySelector","firstElementChild","hasOwnProperty","call","_text","_renderRoot","_shadow","shadowRoot","shadow","attachShadow","mode","styles","_adoptedSheets","stylesList","sheet","CSSStyleSheet","replaceSync","adoptedStyleSheets","result","render","root","rebuilt","_events","e","addEventListener","args","adoptedCallback","disconnectedCallback","removeEventListener","handleEvent","event","stopPropagation","dispatchEvent","cancelable","old","define","Element","window","customElements","_renderPending","_updateComplete","Promise","resolve","_resolveUpdate","queueMicrotask","_performUpdate","error","updateComplete","requestUpdate"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../src/common/props.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,GAAG,cACH,aAAa,GAAG,QAAQ,OAwClC;AAED;;;;;;GAMG;AACH,uCAJW,OAAO,QACP,MAAM,SACN,MAAM,GAAG,IAAI,QAYvB;AAED;;;;;;;;GAQG;AACH,qDAHW,MAAM,EAAE,cACR,GAAG,CAAC,MAAM,CAAC,QAmCrB;AAED;;;;;;;;GAQG;AACH,kCALW,MAAM,QACN,MAAM,YACN,GAAG,YACH,GAAG,QAcb"}
1
+ {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../src/common/props.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,GAAG,cACH,aAAa,GAAG,QAAQ,OAwClC;AAED;;;;;;GAMG;AACH,uCAJW,OAAO,QACP,MAAM,SACN,MAAM,GAAG,IAAI,QAYvB;AAED;;;;;;;;GAQG;AACH,qDAHW,MAAM,EAAE,cACR,GAAG,CAAC,MAAM,CAAC,QAsCrB;AAED;;;;;;;;GAQG;AACH,kCALW,MAAM,QACN,MAAM,YACN,GAAG,YACH,GAAG,QAWb"}
@@ -1,30 +1,12 @@
1
1
  /**
2
2
  * Render a tagged template into an Elena Element with DOM diffing.
3
- *
4
- * On first render, builds the full HTML markup and renders it.
5
- * On re-renders, patches only the text nodes whose values changed,
6
- * avoiding a full DOM rebuild.
7
- *
8
- * Cache state is stored on the element instance:
9
- * - _tplStrings: reference to the template’s static strings array
10
- * - _tplValues: array of escaped values from the last render
11
- * - _tplParts: array mapping each value index to its DOM text node (or undefined)
3
+ * Returns true if the DOM was fully rebuilt, false if only text
4
+ * nodes were patched in place.
12
5
  *
13
6
  * @param {HTMLElement} element
14
7
  * @param {TemplateStringsArray} strings - Static parts of the tagged template
15
8
  * @param {Array} values - Dynamic interpolated values
9
+ * @returns {boolean}
16
10
  */
17
- export function renderTemplate(element: HTMLElement, strings: TemplateStringsArray, values: any[]): void;
18
- /**
19
- * Render an HTML string into an element by parsing it into a
20
- * DocumentFragment via createContextualFragment and swapping
21
- * the element’s children in a single replaceChildren call.
22
- *
23
- * Uses element.ownerDocument.createRange() so that elements
24
- * in e.g. iframes parse HTML correctly.
25
- *
26
- * @param {HTMLElement} element
27
- * @param {string} markup
28
- */
29
- export function renderHtml(element: HTMLElement, markup: string): void;
11
+ export function renderTemplate(element: HTMLElement, strings: TemplateStringsArray, values: any[]): boolean;
30
12
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/common/render.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AACH,wCAJW,WAAW,WACX,oBAAoB,uBAQ9B;AA8HD;;;;;;;;;;GAUG;AACH,oCAHW,WAAW,UACX,MAAM,QAQhB"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/common/render.js"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,wCALW,WAAW,WACX,oBAAoB,kBAElB,OAAO,CAQnB"}
@@ -13,9 +13,16 @@ export function defineElement(tagName: string, Element: Function): void;
13
13
  */
14
14
  export function escapeHtml(str: string): string;
15
15
  /**
16
- * Tagged template for trusted HTML. Use as the return value of render(), or for
17
- * sub-fragments inside render methods. Interpolated values are auto-escaped;
18
- * nested `html` fragments are passed through without double-escaping.
16
+ * Resolve an interpolated template value to its
17
+ * HTML string representation.
18
+ *
19
+ * @param {*} value
20
+ * @returns {string}
21
+ */
22
+ export function resolveValue(value: any): string;
23
+ /**
24
+ * Tagged template for trusted HTML. Use as the return value
25
+ * of render(), or for sub-fragments inside render methods.
19
26
  *
20
27
  * @param {TemplateStringsArray} strings
21
28
  * @param {...*} values
@@ -38,9 +45,15 @@ export function unsafeHTML(str: string): {
38
45
  toString(): string;
39
46
  };
40
47
  /**
41
- * A placeholder you can return from a conditional expression inside a template
42
- * to render nothing. Always produces an empty string; signals to the template
43
- * engine that no further processing is needed.
48
+ * Collapse whitespace from a static string part.
49
+ *
50
+ * @param {string} string
51
+ * @returns {string}
52
+ */
53
+ export function collapseWhitespace(string: string): string;
54
+ /**
55
+ * A placeholder you can return from a conditional expression
56
+ * inside a template to render nothing.
44
57
  *
45
58
  * @type {{ __raw: true, toString(): string }}
46
59
  */
@@ -48,4 +61,6 @@ export const nothing: {
48
61
  __raw: true;
49
62
  toString(): string;
50
63
  };
64
+ export function isRaw(value: any): boolean;
65
+ export function toPlainText(value: any): string;
51
66
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,uCAHW,MAAM,2BAShB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;;;;GAQG;AACH,8BAJW,oBAAoB,aACjB,GAAC,EAAA,GACF;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAC;IAAC,MAAM,QAAQ;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAQ7F;AAED;;;;;GAKG;AACH,gCAHW,MAAM,GACJ;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAI/C;AAED;;;;;;GAMG;AACH,sBAFU;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAEc"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,uCAHW,MAAM,2BAShB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;;GAMG;AACH,oCAHW,GAAC,GACC,MAAM,CAOlB;AAaD;;;;;;;GAOG;AACH,8BAJW,oBAAoB,aACjB,GAAC,EAAA,GACF;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAC;IAAC,MAAM,QAAQ;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAiB7F;AAED;;;;;GAKG;AACH,gCAHW,MAAM,GACJ;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAI/C;AA4BD;;;;;GAKG;AACH,2CAHW,MAAM,GACJ,MAAM,CAOlB;AArCD;;;;;GAKG;AACH,sBAFU;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAE6B;AAQnE,6BAHI,GAAC,GACC,OAAO,CAGmD;AAQhE,mCAHI,GAAC,GACC,MAAM,CAG0E"}
package/dist/elena.d.ts CHANGED
@@ -1,66 +1,42 @@
1
1
  /**
2
- * @typedef {Object} ElenaOptions
3
- * @property {string} [tagName] - Custom element tag name to register (e.g. "elena-button").
4
- * @property {(string | {name: string, reflect?: boolean})[]} [props] - Props observed and synced as attributes.
5
- * @property {string[]} [events] - Events to delegate from the inner element.
6
- * @property {string} [element] - CSS selector for the inner element.
7
- */
8
- /**
9
- * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor
10
- */
11
- /**
12
- * @typedef {{ text: string, element: HTMLElement | null, render(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers
13
- */
14
- /**
15
- * @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & { define(): void, readonly observedAttributes: string[] }} ElenaElementConstructor
16
- */
17
- /**
18
- * Factory that creates Elena mixin class.
2
+ * Creates an Elena component class by extending `superClass`.
19
3
  *
20
- * Wraps `superClass` with Elena’s lifecycle, templating, props,
21
- * and events features. The `options` argument is optional.
4
+ * Adds rendering, props, and event handling to your component.
5
+ * Configure it using static class fields: `static tagName`,
6
+ * `static props`, `static events`, and `static element`.
22
7
  *
23
- * @param {ElenaConstructor} superClass - Base class to extend.
24
- * @param {ElenaOptions} [options] - Optional configuration options.
25
- * @returns {ElenaElementConstructor} A class ready to be registered.
8
+ * @param {ElenaConstructor} superClass - The base class to extend (usually `HTMLElement`).
9
+ * @returns {ElenaElementConstructor} A class ready to be defined as a custom element.
26
10
  */
27
- export function Elena(superClass: ElenaConstructor, options?: ElenaOptions): ElenaElementConstructor;
28
- export type ElenaOptions = {
29
- /**
30
- * - Custom element tag name to register (e.g. "elena-button").
31
- */
32
- tagName?: string;
33
- /**
34
- * - Props observed and synced as attributes.
35
- */
36
- props?: (string | {
37
- name: string;
38
- reflect?: boolean;
39
- })[];
40
- /**
41
- * - Events to delegate from the inner element.
42
- */
43
- events?: string[];
44
- /**
45
- * - CSS selector for the inner element.
46
- */
47
- element?: string;
48
- };
11
+ export function Elena(superClass: ElenaConstructor): ElenaElementConstructor;
49
12
  export type ElenaConstructor = new (...args: any[]) => HTMLElement;
50
13
  export type ElenaInstanceMembers = {
51
14
  text: string;
52
15
  element: HTMLElement | null;
53
16
  render(): void;
17
+ willUpdate(): void;
18
+ firstUpdated(): void;
54
19
  updated(): void;
55
20
  connectedCallback(): void;
56
21
  disconnectedCallback(): void;
57
22
  };
23
+ export type ElenaPropObject = {
24
+ name: string;
25
+ reflect?: boolean;
26
+ };
58
27
  export type ElenaElementConstructor = (new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & {
59
28
  define(): void;
60
29
  readonly observedAttributes: string[];
30
+ tagName?: string;
31
+ props?: (string | ElenaPropObject)[];
32
+ events?: string[];
33
+ element?: string;
34
+ shadow?: "open" | "closed";
35
+ styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[];
61
36
  };
62
37
  import { html } from "./common/utils.js";
63
38
  import { unsafeHTML } from "./common/utils.js";
64
39
  import { nothing } from "./common/utils.js";
65
- export { html, unsafeHTML, nothing };
40
+ import { ElenaEvent } from "./common/events.js";
41
+ export { html, unsafeHTML, nothing, ElenaEvent };
66
42
  //# sourceMappingURL=elena.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"elena.d.ts","sourceRoot":"","sources":["../src/elena.js"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;GAEG;AAEH;;;;;;;;;GASG;AACH,kCAJW,gBAAgB,YAChB,YAAY,GACV,uBAAuB,CA4SnC;;;;;cAtUa,MAAM;;;;YACN,CAAC,MAAM,GAAG;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC,EAAE;;;;aAC9C,MAAM,EAAE;;;;cACR,MAAM;;+BAIP,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW;mCAInC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,MAAM,IAAI,IAAI,CAAC;IAAC,OAAO,IAAI,IAAI,CAAC;IAAC,iBAAiB,IAAI,IAAI,CAAC;IAAC,oBAAoB,IAAI,IAAI,CAAA;CAAE;sCAIvI,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW,GAAG,oBAAoB,CAAC,GAAG;IAAE,MAAM,IAAI,IAAI,CAAC;IAAC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE;qBAtB5E,mBAAmB;2BAAnB,mBAAmB;wBAAnB,mBAAmB"}
1
+ {"version":3,"file":"elena.d.ts","sourceRoot":"","sources":["../src/elena.js"],"names":[],"mappings":"AAkEA;;;;;;;;;GASG;AACH,kCAHW,gBAAgB,GACd,uBAAuB,CAwdnC;+BA3fY,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW;mCAInC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,MAAM,IAAI,IAAI,CAAC;IAAC,UAAU,IAAI,IAAI,CAAC;IAAC,YAAY,IAAI,IAAI,CAAC;IAAC,OAAO,IAAI,IAAI,CAAC;IAAC,iBAAiB,IAAI,IAAI,CAAC;IAAC,oBAAoB,IAAI,IAAI,CAAA;CAAE;8BAIjL;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;sCAInC,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW,GAAG,oBAAoB,CAAC,GAAG;IACvE,MAAM,IAAI,IAAI,CAAC;IACnB,QAAY,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,eAAe,CAAC,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;CAC9D;qBA7CqD,mBAAmB;2BAAnB,mBAAmB;wBAAnB,mBAAmB;2BAEjD,oBAAoB"}
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,unsafeHTML}from"./utils.js";import{renderTemplate as h}from"./render.js";function o(o,a){const l=a&&a.element?/^[a-z][a-z0-9-]*$/i.test(a.element)?e=>e.getElementsByClassName(a.element)[0]:e=>e.querySelector(a.element):e=>e.firstElementChild,d=a&&a.props?a.props:[],m=[],p=new Set;for(const e of d)"string"==typeof e?m.push(e):(m.push(e.name),!1===e.reflect&&p.add(e.name));class c 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[...m,"text"]}render(){}_applyRender(){const e=this.render();e&&e.strings&&(h(this,e.strings,e.values),this._hydrated&&(this.element=l(this)))}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=l(this),this.element||(a&&a.element&&console.warn("░█ [ELENA]: No element found, using firstElementChild as fallback."),this.element=this.firstElementChild))}_flushProps(){if(this._props)for(const[e,t]of this._props){if(p.has(e))continue;const i=s(typeof t,t,"toAttribute");n(this,e,i)}}_delegateEvents(){!this._events&&a&&a.events&&(this.element?(this._events=!0,a.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,a.events?.forEach(e=>{this.element?.removeEventListener(e,this)}))}handleEvent(e){a.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 m.length&&(m.includes("text")&&console.warn('░█ [ELENA]: "text" is a reserved property. Rename your prop to avoid overriding the built-in text content feature.'),e(c.prototype,m,p)),a&&a.tagName&&(c._tagName=a.tagName),c.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.")},c}export{o as Elena};
1
+ import{getProps as t,setProps as e,getPropValue as s,syncAttribute as i}from"./props.js";import{defineElement as n}from"./utils.js";export{html,nothing,unsafeHTML}from"./utils.js";import{renderTemplate as r}from"./render.js";import{ElenaEvent as o}from"./events.js";const h=new WeakSet;function a(a){return class extends a{element=null;attributeChangedCallback(e,s,i){super.attributeChangedCallback?.(e,s,i),"text"!==e?(this._syncing=!0,t(this,e,s,i),this._syncing=!1,this._hydrated&&s!==i&&!this._isRendering&&this._safeRender()):this.text=i??""}static get observedAttributes(){if(this._observedAttrs)return this._observedAttrs;const t=this._propNames||(this.props||[]).map(t=>"string"==typeof t?t:t.name);return this._observedAttrs=[...t,"text"],this._observedAttrs}connectedCallback(){super.connectedCallback?.(),this._setupStaticProps(),this._captureClassFieldDefaults(),this._captureText(),this._attachShadow(),this.willUpdate(),this._applyRender(),this._resolveInnerElement(),this._syncProps(),this._delegateEvents(),this._hydrated||(this._hydrated=!0,this.setAttribute("hydrated",""),this.firstUpdated()),this.updated()}_setupStaticProps(){const t=this.constructor;if(h.has(t))return;const s=new Set,i=[];if(t.props){for(const e of t.props)"string"==typeof e?i.push(e):(i.push(e.name),!1===e.reflect&&s.add(e.name));i.includes("text")&&console.warn('░█ [ELENA]: "text" is reserved.'),e(t.prototype,i,s)}var n;t._propNames=i,t._noReflect=s,t._elenaEvents=t.events||null,t._resolver=(n=t.element)?t=>t.querySelector(n):t=>t.firstElementChild,h.add(t)}_captureClassFieldDefaults(){this._syncing=!0;for(const t of this.constructor._propNames)if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}this._syncing=!1}_captureText(){this._hydrated||void 0!==this._text||(this.text=this.textContent.trim())}get _renderRoot(){return this._shadow??this.shadowRoot??this}_attachShadow(){const t=this.constructor;if(!t.shadow)return;(this._shadow??this.shadowRoot)||(this._shadow=this.attachShadow({mode:t.shadow}));const e=this._shadow??this.shadowRoot;if(t.styles){if(!t._adoptedSheets){const e=Array.isArray(t.styles)?t.styles:[t.styles];t._adoptedSheets=e.map(t=>{if("string"==typeof t){const e=new CSSStyleSheet;return e.replaceSync(t),e}return t})}e.adoptedStyleSheets=t._adoptedSheets}}_applyRender(){const t=this.render();if(t&&t.strings){const e=this._renderRoot,s=r(e,t.strings,t.values);this._hydrated&&s&&(this.element=this.constructor._resolver(e))}}_resolveInnerElement(){if(!this.element){const t=this._renderRoot;this.element=this.constructor._resolver(t),this.element||(this.constructor.element&&console.warn("░█ [ELENA]: Element not found."),this.element=t.firstElementChild)}}_syncProps(){if(this._props){const t=this.constructor._noReflect;for(const[e,n]of this._props){if(t.has(e))continue;const r=s(typeof n,n,"toAttribute");(null!==r||this.hasAttribute(e))&&i(this,e,r)}}}_delegateEvents(){const t=this.constructor._elenaEvents;if(!this._events&&t?.length)if(this.element){this._events=!0;for(const e of t)this.element.addEventListener(e,this),this[e]=(...t)=>this.element[e](...t)}else console.warn("░█ [ELENA]: Cannot add events.")}render(){}willUpdate(){}firstUpdated(){}updated(){}adoptedCallback(){super.adoptedCallback?.()}disconnectedCallback(){if(super.disconnectedCallback?.(),this._events){this._events=!1;for(const t of this.constructor._elenaEvents)this.element?.removeEventListener(t,this)}}handleEvent(t){this.constructor._elenaEvents?.includes(t.type)&&(t.stopPropagation(),this.dispatchEvent(new o(t.type,{cancelable:!0})))}get text(){return this._text??""}set text(t){const e=this._text;this._text=t,this._hydrated&&e!==t&&!this._isRendering&&this._safeRender()}static define(){this.tagName?n(this.tagName,this):console.warn("░█ [ELENA]: define() without a tagName.")}_safeRender(){this._isRendering||this._renderPending||(this._renderPending=!0,this._updateComplete=new Promise(t=>{this._resolveUpdate=t}),queueMicrotask(()=>{try{this._performUpdate()}catch(t){console.error("░█ [ELENA]:",t)}}))}_performUpdate(){this._renderPending=!1;const t=this._resolveUpdate;this._resolveUpdate=null;try{try{this.willUpdate(),this._isRendering=!0,this._applyRender()}finally{this._isRendering=!1}this.updated()}finally{this._updateComplete=null,t()}}get updateComplete(){return this._updateComplete?this._updateComplete:Promise.resolve()}requestUpdate(){this._hydrated&&!this._isRendering&&this._safeRender()}}}export{a as Elena,o as ElenaEvent};
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, unsafeHTML, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\n\nexport { html, unsafeHTML, nothing };\n\n/**\n * @typedef {Object} ElenaOptions\n * @property {string} [tagName] - Custom element tag name to register (e.g. \"elena-button\").\n * @property {(string | {name: string, reflect?: boolean})[]} [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 * @typedef {{ text: string, element: HTMLElement | null, render(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers\n */\n\n/**\n * @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & { define(): void, readonly observedAttributes: string[] }} ElenaElementConstructor\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 {ElenaElementConstructor} 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 * Normalize prop definitions into a names array and a\n * set of non-reflecting prop names. Props can be plain\n * strings or objects with { name, reflect? } shape.\n */\n const rawProps = options && options.props ? options.props : [];\n const propNames = [];\n const noReflect = new Set();\n for (const p of rawProps) {\n if (typeof p === \"string\") {\n propNames.push(p);\n } else {\n propNames.push(p.name);\n if (p.reflect === false) {\n noReflect.add(p.name);\n }\n }\n }\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 return [...propNames, \"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 // Re-resolve element ref after render in case the DOM was rebuilt.\n if (this._hydrated) {\n this.element = resolveElement(this);\n }\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 // Angular sets textContent after the element connects,\n // so we defer capture to the next microtask to pick it up.\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 for (const [prop, value] of this._props) {\n if (noReflect.has(prop)) {\n continue;\n }\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\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 (propNames.length) {\n if (propNames.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, propNames, noReflect);\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","rawProps","props","propNames","noReflect","Set","p","push","name","reflect","add","ElenaElement","attributeChangedCallback","prop","oldValue","newValue","getProps","this","_hydrated","_isRendering","_applyRender","text","observedAttributes","render","result","strings","renderTemplate","values","connectedCallback","_captureText","_resolveInnerElement","_flushProps","_delegateEvents","updated","_text","textContent","trim","queueMicrotask","console","warn","_props","value","has","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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"}
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 { defineElement, html, unsafeHTML, nothing } from \"./common/utils.js\";\nimport { renderTemplate } from \"./common/render.js\";\nimport { ElenaEvent } from \"./common/events.js\";\n\nexport { html, unsafeHTML, nothing, ElenaEvent };\n\n/**\n * Returns a function that finds the inner element using the given selector.\n * Built once per component class to avoid repeated work.\n *\n * - No selector: uses firstElementChild\n * - Any string: uses querySelector\n *\n * @param {string | undefined} selector\n * @returns {(host: HTMLElement) => HTMLElement | null}\n */\nfunction elementResolver(selector) {\n if (!selector) {\n return host => host.firstElementChild;\n }\n return host => host.querySelector(selector);\n}\n\n/**\n * @typedef {new (...args: any[]) => HTMLElement} ElenaConstructor\n */\n\n/**\n * @typedef {{ text: string, element: HTMLElement | null, render(): void, willUpdate(): void, firstUpdated(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers\n */\n\n/**\n * @typedef {{ name: string, reflect?: boolean }} ElenaPropObject\n */\n\n/**\n * @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & {\n * define(): void,\n * readonly observedAttributes: string[],\n * tagName?: string,\n * props?: (string | ElenaPropObject)[],\n * events?: string[],\n * element?: string,\n * shadow?: \"open\" | \"closed\",\n * styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[],\n * }} ElenaElementConstructor\n */\n\n// Tracks which component classes have already been set up.\nconst setupRegistry = new WeakSet();\n\n/**\n * Creates an Elena component class by extending `superClass`.\n *\n * Adds rendering, props, and event handling to your component.\n * Configure it using static class fields: `static tagName`,\n * `static props`, `static events`, and `static element`.\n *\n * @param {ElenaConstructor} superClass - The base class to extend (usually `HTMLElement`).\n * @returns {ElenaElementConstructor} A class ready to be defined as a custom element.\n */\nexport function Elena(superClass) {\n /**\n * The base Elena element class with all built-in behavior.\n */\n class ElenaElement extends superClass {\n /**\n * The inner element rendered by this component.\n *\n * @type {HTMLElement | null}\n */\n element = null;\n\n /**\n * Called by the browser when an observed attribute changes.\n * Updates the matching prop and re-renders if needed.\n *\n * @param {string} prop\n * @param {string} oldValue\n * @param {string} newValue\n */\n attributeChangedCallback(prop, oldValue, newValue) {\n super.attributeChangedCallback?.(prop, oldValue, newValue);\n\n if (prop === \"text\") {\n this.text = newValue ?? \"\";\n return;\n }\n\n // Set flag so the property setter skips redundant attribute reflection:\n // the attribute is already at the new value, no need to set it again.\n this._syncing = true;\n getProps(this, prop, oldValue, newValue);\n this._syncing = false;\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 if (this._hydrated && oldValue !== newValue && !this._isRendering) {\n this._safeRender();\n }\n }\n\n /**\n * Lists the attributes Elena watches for changes.\n * Reads from the subclass’s `static props` field.\n */\n static get observedAttributes() {\n if (this._observedAttrs) {\n return this._observedAttrs;\n }\n\n const propNames =\n this._propNames || (this.props || []).map(p => (typeof p === \"string\" ? p : p.name));\n this._observedAttrs = [...propNames, \"text\"];\n return this._observedAttrs;\n }\n\n /**\n * Called by the browser each time the element is added to the page.\n */\n connectedCallback() {\n super.connectedCallback?.();\n this._setupStaticProps();\n this._captureClassFieldDefaults();\n this._captureText();\n this._attachShadow();\n this.willUpdate();\n this._applyRender();\n this._resolveInnerElement();\n this._syncProps();\n this._delegateEvents();\n if (!this._hydrated) {\n this._hydrated = true;\n this.setAttribute(\"hydrated\", \"\");\n this.firstUpdated();\n }\n this.updated();\n }\n\n /**\n * Sets up props, events, and the element selector once per component class.\n * Runs the first time an instance of a given class connects to the page.\n *\n * @internal\n */\n _setupStaticProps() {\n const component = this.constructor;\n\n if (setupRegistry.has(component)) {\n return;\n }\n\n // Props with reflect: false\n const noRef = new Set();\n const names = [];\n\n if (component.props) {\n for (const p of component.props) {\n if (typeof p === \"string\") {\n names.push(p);\n } else {\n names.push(p.name);\n\n if (p.reflect === false) {\n noRef.add(p.name);\n }\n }\n }\n\n if (names.includes(\"text\")) {\n console.warn('░█ [ELENA]: \"text\" is reserved.');\n }\n\n setProps(component.prototype, names, noRef);\n }\n\n component._propNames = names;\n component._noReflect = noRef;\n component._elenaEvents = component.events || null;\n component._resolver = elementResolver(component.element);\n setupRegistry.add(component);\n }\n\n /**\n * Moves class field defaults into Elena’s internal props store\n * so that getters and setters work correctly.\n *\n * @internal\n */\n _captureClassFieldDefaults() {\n this._syncing = true;\n\n for (const name of this.constructor._propNames) {\n if (Object.prototype.hasOwnProperty.call(this, name)) {\n const value = this[name];\n delete this[name];\n this[name] = value;\n }\n }\n\n this._syncing = false;\n }\n\n /**\n * Saves any text inside the element before the first render.\n *\n * @internal\n */\n _captureText() {\n if (!this._hydrated && this._text === undefined) {\n this.text = this.textContent.trim();\n }\n }\n\n /**\n * The root node to render into. Returns the shadow root when shadow mode\n * is enabled, otherwise the host element itself.\n *\n * @type {ShadowRoot | HTMLElement}\n */\n get _renderRoot() {\n return this._shadow ?? this.shadowRoot ?? this;\n }\n\n /**\n * Attaches a shadow root and adopts styles on first connect.\n * Only runs when `static shadow` is set on the component class.\n *\n * @internal\n */\n _attachShadow() {\n const component = this.constructor;\n\n if (!component.shadow) {\n return;\n }\n\n // A shadow root may already exist if Declarative Shadow DOM was used.\n // In that case skip attachShadow() but still adopt styles below.\n // Store the reference so closed shadow roots remain accessible.\n const root = this._shadow ?? this.shadowRoot;\n\n if (!root) {\n this._shadow = this.attachShadow({ mode: component.shadow });\n }\n\n const shadowRoot = this._shadow ?? this.shadowRoot;\n\n if (!component.styles) {\n return;\n }\n\n // Normalize to array and cache converted CSSStyleSheet instances on the class.\n // Avoids re-parsing CSS strings on every element instance.\n if (!component._adoptedSheets) {\n const stylesList = Array.isArray(component.styles) ? component.styles : [component.styles];\n\n component._adoptedSheets = stylesList.map(s => {\n if (typeof s === \"string\") {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(s);\n return sheet;\n }\n return s;\n });\n }\n\n shadowRoot.adoptedStyleSheets = component._adoptedSheets;\n }\n\n /**\n * Calls render() and updates the DOM with the result.\n *\n * @internal\n */\n _applyRender() {\n const result = this.render();\n\n if (result && result.strings) {\n const root = this._renderRoot;\n const rebuilt = renderTemplate(root, result.strings, result.values);\n\n // Re-resolve element ref only when the DOM was fully rebuilt.\n // Fast-path text node patching leaves the DOM structure intact,\n // so the existing ref is still valid.\n if (this._hydrated && rebuilt) {\n this.element = this.constructor._resolver(root);\n }\n }\n }\n\n /**\n * Finds and stores a reference to the inner element.\n *\n * @internal\n */\n _resolveInnerElement() {\n if (!this.element) {\n const root = this._renderRoot;\n this.element = this.constructor._resolver(root);\n\n if (!this.element) {\n if (this.constructor.element) {\n console.warn(\"░█ [ELENA]: Element not found.\");\n }\n this.element = root.firstElementChild;\n }\n }\n }\n\n /**\n * Syncs any props that were set before the element\n * connected to the page.\n *\n * @internal\n */\n _syncProps() {\n if (this._props) {\n const noReflect = this.constructor._noReflect;\n\n for (const [prop, value] of this._props) {\n if (noReflect.has(prop)) {\n continue;\n }\n\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n\n if (attrValue === null && !this.hasAttribute(prop)) {\n continue;\n }\n\n syncAttribute(this, prop, attrValue);\n }\n }\n }\n\n /**\n * Forwards events from the inner element\n * up to the host element.\n *\n * @internal\n */\n _delegateEvents() {\n const events = this.constructor._elenaEvents;\n\n if (!this._events && events?.length) {\n if (!this.element) {\n console.warn(\"░█ [ELENA]: Cannot add events.\");\n } else {\n this._events = true;\n\n for (const e of events) {\n this.element.addEventListener(e, this);\n this[e] = (...args) => this.element[e](...args);\n }\n }\n }\n }\n\n /**\n * Define the element’s HTML here. Return an `html`\n * tagged template. If not overridden, the element connects\n * to the page without rendering anything.\n */\n render() {}\n\n /**\n * Called before every render.\n * Override to prepare state before the template runs.\n */\n willUpdate() {}\n\n /**\n * Called once after the element’s first render.\n * Override to run setup that needs the DOM.\n */\n firstUpdated() {}\n\n /**\n * Called after every render.\n * Override to react to changes.\n */\n updated() {}\n\n /**\n * Called by the browser when the element is moved\n * to a new document via `adoptNode()`.\n */\n adoptedCallback() {\n super.adoptedCallback?.();\n }\n\n /**\n * Called by the browser each time the element\n * is removed from the page.\n */\n disconnectedCallback() {\n super.disconnectedCallback?.();\n if (this._events) {\n this._events = false;\n\n for (const e of this.constructor._elenaEvents) {\n this.element?.removeEventListener(e, this);\n }\n }\n }\n\n /**\n * Receives events from the inner element and\n * re-fires them on the host.\n *\n * @internal\n */\n handleEvent(event) {\n if (this.constructor._elenaEvents?.includes(event.type)) {\n event.stopPropagation();\n\n /** @internal */\n this.dispatchEvent(new ElenaEvent(event.type, { cancelable: true }));\n }\n }\n\n /**\n * The text content of the element. Elena reads this\n * from the element’s children before the first render.\n * Updating it triggers 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\n if (this._hydrated && old !== value && !this._isRendering) {\n this._safeRender();\n }\n }\n\n /**\n * Registers the component as a custom element using `static tagName`.\n * Call this on your component class after the class body is defined,\n * not on the Elena mixin itself.\n */\n static define() {\n if (this.tagName) {\n defineElement(this.tagName, this);\n } else {\n console.warn(\"░█ [ELENA]: define() without a tagName.\");\n }\n }\n\n /**\n * Schedules a re-render via microtask. If called multiple times\n * before the microtask fires, only one render runs.\n *\n * @internal\n */\n _safeRender() {\n if (this._isRendering) {\n return;\n }\n if (!this._renderPending) {\n this._renderPending = true;\n this._updateComplete = new Promise(resolve => {\n this._resolveUpdate = resolve;\n });\n queueMicrotask(() => {\n try {\n this._performUpdate();\n } catch (e) {\n console.error(\"░█ [ELENA]:\", e);\n }\n });\n }\n }\n\n /**\n * Runs the batched update cycle.\n * Called by the microtask in _safeRender().\n *\n * @internal\n */\n _performUpdate() {\n this._renderPending = false;\n const resolve = this._resolveUpdate;\n this._resolveUpdate = null;\n try {\n try {\n this.willUpdate();\n this._isRendering = true;\n this._applyRender();\n } finally {\n this._isRendering = false;\n }\n this.updated();\n } finally {\n this._updateComplete = null;\n resolve();\n }\n }\n\n /**\n * A Promise that resolves after the render completes.\n * Resolves immediately if no render is scheduled.\n *\n * @type {Promise<void>}\n */\n get updateComplete() {\n if (this._updateComplete) {\n return this._updateComplete;\n }\n return Promise.resolve();\n }\n\n /**\n * Schedules a re-render. Use this to manually trigger an\n * update when Elena cannot detect the change automatically.\n */\n requestUpdate() {\n if (this._hydrated && !this._isRendering) {\n this._safeRender();\n }\n }\n }\n\n return ElenaElement;\n}\n"],"names":["setupRegistry","WeakSet","Elena","superClass","element","attributeChangedCallback","prop","oldValue","newValue","super","this","_syncing","getProps","_hydrated","_isRendering","_safeRender","text","observedAttributes","_observedAttrs","propNames","_propNames","props","map","p","name","connectedCallback","_setupStaticProps","_captureClassFieldDefaults","_captureText","_attachShadow","willUpdate","_applyRender","_resolveInnerElement","_syncProps","_delegateEvents","setAttribute","firstUpdated","updated","component","constructor","has","noRef","Set","names","push","reflect","add","includes","console","warn","setProps","prototype","selector","_noReflect","_elenaEvents","events","_resolver","host","querySelector","firstElementChild","Object","hasOwnProperty","call","value","undefined","_text","textContent","trim","_renderRoot","_shadow","shadowRoot","shadow","attachShadow","mode","styles","_adoptedSheets","stylesList","Array","isArray","s","sheet","CSSStyleSheet","replaceSync","adoptedStyleSheets","result","render","strings","root","rebuilt","renderTemplate","values","_props","noReflect","attrValue","getPropValue","hasAttribute","syncAttribute","_events","length","e","addEventListener","args","adoptedCallback","disconnectedCallback","removeEventListener","handleEvent","event","type","stopPropagation","dispatchEvent","ElenaEvent","cancelable","old","define","tagName","defineElement","_renderPending","_updateComplete","Promise","resolve","_resolveUpdate","queueMicrotask","_performUpdate","error","updateComplete","requestUpdate"],"mappings":"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"}
package/dist/props.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.")}function n(n,r,o){for(const s of r){const r=!o||!o.has(s);Object.defineProperty(n,s,{configurable:!0,enumerable:!0,get(){return this._props?this._props.get(s):void 0},set(n){if(this._props||(this._props=new Map),n!==this._props.get(s)&&(this._props.set(s,n),this.isConnected))if(r){const r=e(typeof n,n,"toAttribute");t(this,s,r)}else this._hydrated&&!this._isRendering&&(this._isRendering=!0,this._applyRender(),this._isRendering=!1)}})}}function r(t,n,r,o){if(r!==o){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 s=e(r,o,"toProp");t[n]=s}}export{e as getPropValue,r as getProps,n as setProps,t as syncAttribute};
1
+ function t(t,e,n){if(e="boolean"===t&&"boolean"!=typeof e?null!==e:e,!n)return e;if("toAttribute"===n)switch(t){case"object":case"array":return null===e?null:JSON.stringify(e);case"boolean":return e?"":null;case"number":return null===e?null:e;default:return""===e?null:e}else switch(t){case"object":case"array":if(!e)return e;try{return JSON.parse(e)}catch{return console.warn("░█ [ELENA]: Invalid JSON: "+e),null}case"boolean":return e;case"number":return null!==e?+e:e;default:return e??""}}function e(t,e,n){t?null===n?t.removeAttribute(e):t.setAttribute(e,n):console.warn("░█ [ELENA]: Cannot sync attrs.")}function n(n,r,s){for(const o of r){const r=!s||!s.has(o);Object.defineProperty(n,o,{configurable:!0,enumerable:!0,get(){return this._props?this._props.get(o):void 0},set(n){if(this._props||(this._props=new Map),n!==this._props.get(o)&&(this._props.set(o,n),this.isConnected))if(r){if(!this._syncing){const r=t(typeof n,n,"toAttribute");e(this,o,r)}}else this._hydrated&&!this._isRendering&&this._safeRender()}})}}function r(e,n,r,s){if(r!==s){const r=typeof e[n];"undefined"===r&&console.warn(`░█ [ELENA]: Prop "${n}" has no default.`);const o=t(r,s,"toProp");e[n]=o}}export{t as getPropValue,r as getProps,n as setProps,e as syncAttribute};
2
2
  //# sourceMappingURL=props.js.map
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 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[]} propNames - Prop names to define\n * @param {Set<string>} [noReflect] - Props that should not reflect to attributes\n */\nexport function setProps(proto, propNames, noReflect) {\n for (const prop of propNames) {\n const reflects = !noReflect || !noReflect.has(prop);\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 if (reflects) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n } else if (this._hydrated && !this._isRendering) {\n this._isRendering = true;\n this._applyRender();\n this._isRendering = false;\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"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","console","warn","syncAttribute","element","name","removeAttribute","setAttribute","setProps","proto","propNames","noReflect","prop","reflects","has","Object","defineProperty","configurable","enumerable","get","this","_props","undefined","set","Map","isConnected","attrValue","_hydrated","_isRendering","_applyRender","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,IAAKC,EACH,OAAOA,EAET,IACE,OAAOE,KAAKE,MAAMJ,EACpB,CAAE,MAEA,OADAK,QAAQC,KAAK,gDAAkDN,GACxD,IACT,CACF,IAAK,UACH,OAAOA,EACT,IAAK,SACH,OAAiB,OAAVA,GAAkBA,EAAQA,EACnC,QACE,OAAOA,EAGf,CASO,SAASO,EAAcC,EAASC,EAAMT,GACtCQ,EAIS,OAAVR,EACFQ,EAAQE,gBAAgBD,GAExBD,EAAQG,aAAaF,EAAMT,GAN3BK,QAAQC,KAAK,wDAQjB,CAWO,SAASM,EAASC,EAAOC,EAAWC,GACzC,IAAK,MAAMC,KAAQF,EAAW,CAC5B,MAAMG,GAAYF,IAAcA,EAAUG,IAAIF,GAC9CG,OAAOC,eAAeP,EAAOG,EAAM,CACjCK,cAAc,EACdC,YAAY,EACZ,GAAAC,GACE,OAAOC,KAAKC,OAASD,KAAKC,OAAOF,IAAIP,QAAQU,CAC/C,EACA,GAAAC,CAAI3B,GAIF,GAHKwB,KAAKC,SACRD,KAAKC,OAAS,IAAIG,KAEhB5B,IAAUwB,KAAKC,OAAOF,IAAIP,KAI9BQ,KAAKC,OAAOE,IAAIX,EAAMhB,GACjBwB,KAAKK,aAIV,GAAIZ,EAAU,CACZ,MAAMa,EAAYhC,SAAoBE,EAAOA,EAAO,eACpDO,EAAciB,KAAMR,EAAMc,EAC5B,MAAWN,KAAKO,YAAcP,KAAKQ,eACjCR,KAAKQ,cAAe,EACpBR,KAAKS,eACLT,KAAKQ,cAAe,EAExB,GAEJ,CACF,CAWO,SAASE,EAASC,EAAS1B,EAAM2B,EAAUC,GAChD,GAAID,IAAaC,EAAU,CACzB,MAAMtC,SAAcoC,EAAQ1B,GACf,cAATV,GACFM,QAAQC,KACN,qBAAqBG,kGAIzB,MAAM6B,EAAUxC,EAAaC,EAAMsC,EAAU,UAC7CF,EAAQ1B,GAAQ6B,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 if (!value) {\n return value;\n }\n try {\n return JSON.parse(value);\n } catch {\n console.warn(\"░█ [ELENA]: Invalid JSON: \" + 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 attrs.\");\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[]} propNames - Prop names to define\n * @param {Set<string>} [noReflect] - Props that should not reflect to attributes\n */\nexport function setProps(proto, propNames, noReflect) {\n for (const prop of propNames) {\n const reflects = !noReflect || !noReflect.has(prop);\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 if (reflects) {\n // Skip reflection when called from attributeChangedCallback. The\n // attribute is already at the new value, setting it again is redundant\n // and would fire an extra attributeChangedCallback with identical values.\n if (!this._syncing) {\n const attrValue = getPropValue(typeof value, value, \"toAttribute\");\n syncAttribute(this, prop, attrValue);\n }\n } else if (this._hydrated && !this._isRendering) {\n this._safeRender();\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(`░█ [ELENA]: Prop \"${name}\" has no default.`);\n }\n const newAttr = getPropValue(type, newValue, \"toProp\");\n context[name] = newAttr;\n }\n}\n"],"names":["getPropValue","type","value","transform","JSON","stringify","parse","console","warn","syncAttribute","element","name","removeAttribute","setAttribute","setProps","proto","propNames","noReflect","prop","reflects","has","Object","defineProperty","configurable","enumerable","get","this","_props","undefined","set","Map","isConnected","_syncing","attrValue","_hydrated","_isRendering","_safeRender","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,IAAKC,EACH,OAAOA,EAET,IACE,OAAOE,KAAKE,MAAMJ,EACpB,CAAE,MAEA,OADAK,QAAQC,KAAK,6BAA+BN,GACrC,IACT,CACF,IAAK,UACH,OAAOA,EACT,IAAK,SACH,OAAiB,OAAVA,GAAkBA,EAAQA,EACnC,QACE,OAAOA,GAAS,GAGxB,CASO,SAASO,EAAcC,EAASC,EAAMT,GACtCQ,EAIS,OAAVR,EACFQ,EAAQE,gBAAgBD,GAExBD,EAAQG,aAAaF,EAAMT,GAN3BK,QAAQC,KAAK,iCAQjB,CAWO,SAASM,EAASC,EAAOC,EAAWC,GACzC,IAAK,MAAMC,KAAQF,EAAW,CAC5B,MAAMG,GAAYF,IAAcA,EAAUG,IAAIF,GAC9CG,OAAOC,eAAeP,EAAOG,EAAM,CACjCK,cAAc,EACdC,YAAY,EACZ,GAAAC,GACE,OAAOC,KAAKC,OAASD,KAAKC,OAAOF,IAAIP,QAAQU,CAC/C,EACA,GAAAC,CAAI3B,GAIF,GAHKwB,KAAKC,SACRD,KAAKC,OAAS,IAAIG,KAEhB5B,IAAUwB,KAAKC,OAAOF,IAAIP,KAI9BQ,KAAKC,OAAOE,IAAIX,EAAMhB,GACjBwB,KAAKK,aAIV,GAAIZ,GAIF,IAAKO,KAAKM,SAAU,CAClB,MAAMC,EAAYjC,SAAoBE,EAAOA,EAAO,eACpDO,EAAciB,KAAMR,EAAMe,EAC5B,OACSP,KAAKQ,YAAcR,KAAKS,cACjCT,KAAKU,aAET,GAEJ,CACF,CAWO,SAASC,EAASC,EAAS3B,EAAM4B,EAAUC,GAChD,GAAID,IAAaC,EAAU,CACzB,MAAMvC,SAAcqC,EAAQ3B,GACf,cAATV,GACFM,QAAQC,KAAK,qBAAqBG,sBAEpC,MAAM8B,EAAUzC,EAAaC,EAAMuC,EAAU,UAC7CF,EAAQ3B,GAAQ8B,CAClB,CACF"}
package/dist/render.js CHANGED
@@ -1,2 +1,2 @@
1
- import{escapeHtml as t}from"./utils.js";const e=new WeakMap;function n(n,l,o){(function(e,n,r){if(e._tplStrings!==n||!e._tplParts)return!1;let l=!1;for(let n=0;n<r.length;n++){const o=r[n],a=o&&o.__raw,c=a?String(o):t(String(o??""));if(c!==e._tplValues[n])if(e._tplValues[n]=c,a)l=!0;else{const t=e._tplParts[n];t?t.textContent=String(o??""):l=!0}}return!l})(n,l,o)||function(n,l,o){const a=o.map(e=>e&&e.__raw?String(e):t(String(e??"")));let c=e.get(l);c||(c=Array.from(l,t=>t.replace(/\n\s*/g," ")),e.set(l,c));const s=c.reduce((t,e,n)=>t+e+(a[n]??""),"").replace(/>\s+</g,"><").replace(/>\s+/g,">").replace(/\s+</g,"<").trim();r(n,s),n._tplStrings=l,n._tplValues=a,n._tplParts=function(t,e){const n=new Array(e.length),r=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);let l,o=0;for(;(l=r.nextNode())&&o<e.length;)l.textContent===e[o]&&(n[o]=l,o++);return n}(n,a)}(n,l,o)}function r(t,e){t?t.replaceChildren(t.ownerDocument.createRange().createContextualFragment(e)):console.warn("░█ [ELENA]: Cannot render to a null element.")}export{r as renderHtml,n as renderTemplate};
1
+ import{toPlainText as e,isRaw as t,collapseWhitespace as n,resolveValue as r}from"./utils.js";const o=new WeakMap,l="e"+Math.random().toString(36).slice(2,6);function a(a,i,d){return!function(n,r,o){if(n._tplStrings!==r||!n._tplParts)return!1;for(let r=0;r<o.length;r++){const l=o[r],a=Array.isArray(l)?e(l):l;if(a!==n._tplValues[r]){if(t(l)||!n._tplParts[r])return!1;n._tplValues[r]=a,n._tplParts[r].textContent=e(l)}}return!0}(a,i,d)&&(function(a,i,d){let p=o.get(i);if(!p){const e=Array.from(i,n);p={processedStrings:e,template:d.length>0?c(e,d.length):null},o.set(i,p)}if(p.template)a._tplParts=function(n,o,a){const c=o.content.cloneNode(!0),s=document.createTreeWalker(c,NodeFilter.SHOW_COMMENT),i=new Array(a.length),d=[];let p;for(;p=s.nextNode();)p.data===l&&d.push(p);for(let n=0;n<d.length;n++){const o=a[n];if(t(o)){const e=document.createElement("template");e.innerHTML=r(o),d[n].parentNode.replaceChild(e.content,d[n])}else{const t=document.createTextNode(e(o));d[n].parentNode.replaceChild(t,d[n]),i[n]=t}}return n.replaceChildren(c),i}(a,p.template,d);else{const e=d.map(e=>r(e)),t=p.processedStrings.reduce((t,n,r)=>t+n+(e[r]??""),"").replace(/>\s+</g,"><").trim(),n=document.createElement("template");n.innerHTML=t,s(a,n.content.childNodes),a._tplParts=new Array(d.length)}a._tplStrings=i,a._tplValues=d.map(t=>Array.isArray(t)?e(t):t)}(a,i,d),!0)}function c(e,t){const n=`\x3c!--${l}--\x3e`,r=e.reduce((e,r,o)=>e+r.replace(/>\s+</g,"><")+(o<t?n:""),"").trim(),o=document.createElement("template");o.innerHTML=r;const a=document.createTreeWalker(o.content,NodeFilter.SHOW_COMMENT);let c=0;for(;a.nextNode();)a.currentNode.data===l&&c++;return c===t?o:null}function s(e,t){const n=Array.from(e.childNodes),r=Array.from(t),o=Math.max(n.length,r.length);for(let t=0;t<o;t++){const o=n[t],l=r[t];o?l?o.nodeType!==l.nodeType||o.nodeType===Node.ELEMENT_NODE&&o.tagName!==l.tagName?e.replaceChild(l,o):o.nodeType===Node.TEXT_NODE?o.textContent!==l.textContent&&(o.textContent=l.textContent):o.nodeType===Node.ELEMENT_NODE&&(i(o,l),s(o,l.childNodes)):e.removeChild(o):e.appendChild(l)}}function i(e,t){for(let n=e.attributes.length-1;n>=0;n--){const{name:r}=e.attributes[n];t.hasAttribute(r)||e.removeAttribute(r)}for(let n=0;n<t.attributes.length;n++){const{name:r,value:o}=t.attributes[n];e.getAttribute(r)!==o&&e.setAttribute(r,o)}}export{a as renderTemplate};
2
2
  //# sourceMappingURL=render.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sources":["../src/common/render.js"],"sourcesContent":["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 renders it.\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 (or undefined)\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 // created 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 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 // No mapped text node for this value, 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 without a matching\n * text node will be undefined.\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"],"names":["_stringsCache","WeakMap","renderTemplate","element","strings","values","_tplStrings","_tplParts","needsFullRender","i","length","v","isRaw","__raw","newRendered","String","escapeHtml","_tplValues","textNode","textContent","patchTextNodes","renderedValues","map","processedStrings","get","Array","from","s","replace","set","markup","reduce","out","str","trim","renderHtml","escapedValues","parts","walker","document","createTreeWalker","NodeFilter","SHOW_TEXT","node","valueIndex","nextNode","mapTextNodes","fullRender","replaceChildren","ownerDocument","createRange","createContextualFragment","console","warn"],"mappings":"wCAGA,MAAMA,EAAgB,IAAIC,QAkBnB,SAASC,EAAeC,EAASC,EAASC,IAgBjD,SAAwBF,EAASC,EAASC,GAExC,GAAIF,EAAQG,cAAgBF,IAAYD,EAAQI,UAC9C,OAAO,EAGT,IAAIC,GAAkB,EAEtB,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAAOK,OAAQD,IAAK,CACtC,MAAME,EAAIN,EAAOI,GAGXG,EAAQD,GAAKA,EAAEE,MACfC,EAAcF,EAAQG,OAAOJ,GAAKK,EAAWD,OAAOJ,GAAK,KAG/D,GAAIG,IAFgBX,EAAQc,WAAWR,GASvC,GAHAN,EAAQc,WAAWR,GAAKK,EAGpBF,EACFJ,GAAkB,MACb,CACL,MAAMU,EAAWf,EAAQI,UAAUE,GAC/BS,EAEFA,EAASC,YAAcJ,OAAOJ,GAAK,IAGnCH,GAAkB,CAEtB,CACF,CAEA,OAAQA,CACV,EArDMY,CAAejB,EAASC,EAASC,IAgEvC,SAAoBF,EAASC,EAASC,GACpC,MAAMgB,EAAiBhB,EAAOiB,IAAIX,GAAMA,GAAKA,EAAEE,MAAQE,OAAOJ,GAAKK,EAAWD,OAAOJ,GAAK,MAI1F,IAAIY,EAAmBvB,EAAcwB,IAAIpB,GACpCmB,IACHA,EAAmBE,MAAMC,KAAKtB,EAASuB,GAAKA,EAAEC,QAAQ,SAAU,MAChE5B,EAAc6B,IAAIzB,EAASmB,IAI7B,MAAMO,EAASP,EACZQ,OAAO,CAACC,EAAKC,EAAKxB,IAAMuB,EAAMC,GAAOZ,EAAeZ,IAAM,IAAK,IAC/DmB,QAAQ,SAAU,MAClBA,QAAQ,QAAS,KACjBA,QAAQ,QAAS,KACjBM,OAEHC,EAAWhC,EAAS2B,GAGpB3B,EAAQG,YAAcF,EACtBD,EAAQc,WAAaI,EAGrBlB,EAAQI,UAuBV,SAAsBJ,EAASiC,GAC7B,MAAMC,EAAQ,IAAIZ,MAAMW,EAAc1B,QAChC4B,EAASC,SAASC,iBAAiBrC,EAASsC,WAAWC,WAE7D,IACIC,EADAC,EAAa,EAGjB,MAAQD,EAAOL,EAAOO,aAAeD,EAAaR,EAAc1B,QAC1DiC,EAAKxB,cAAgBiB,EAAcQ,KACrCP,EAAMO,GAAcD,EACpBC,KAIJ,OAAOP,CACT,CAtCsBS,CAAa3C,EAASkB,EAC5C,CAxFE0B,CAAW5C,EAASC,EAASC,EAC/B,CAyIO,SAAS8B,EAAWhC,EAAS2B,GAC7B3B,EAILA,EAAQ6C,gBAAgB7C,EAAQ8C,cAAcC,cAAcC,yBAAyBrB,IAHnFsB,QAAQC,KAAK,+CAIjB"}
1
+ {"version":3,"file":"render.js","sources":["../src/common/render.js"],"sourcesContent":["import { collapseWhitespace, isRaw, resolveValue, toPlainText } from \"./utils.js\";\n\nconst stringsCache = new WeakMap();\nconst markerKey = \"e\" + Math.random().toString(36).slice(2, 6);\n\n/**\n * Render a tagged template into an Elena Element with DOM diffing.\n * Returns true if the DOM was fully rebuilt, false if only text\n * nodes were patched in place.\n *\n * @param {HTMLElement} element\n * @param {TemplateStringsArray} strings - Static parts of the tagged template\n * @param {Array} values - Dynamic interpolated values\n * @returns {boolean}\n */\nexport function renderTemplate(element, strings, values) {\n if (patchTextNodes(element, strings, values)) {\n return false;\n }\n fullRender(element, strings, values);\n return true;\n}\n\n/**\n * Fast path: patch only the text nodes whose values changed.\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 for (let i = 0; i < values.length; i++) {\n const v = values[i];\n const comparable = Array.isArray(v) ? toPlainText(v) : v;\n\n if (comparable === element._tplValues[i]) {\n continue;\n }\n\n if (isRaw(v) || !element._tplParts[i]) {\n return false;\n }\n\n element._tplValues[i] = comparable;\n element._tplParts[i].textContent = toPlainText(v);\n }\n\n return true;\n}\n\n/**\n * Cold path: clone a cached <template> and patch in values.\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 let entry = stringsCache.get(strings);\n\n if (!entry) {\n const processedStrings = Array.from(strings, collapseWhitespace);\n entry = {\n processedStrings,\n template: values.length > 0 ? createTemplate(processedStrings, values.length) : null,\n };\n stringsCache.set(strings, entry);\n }\n\n if (entry.template) {\n element._tplParts = cloneAndPatch(element, entry.template, values);\n } else {\n // Fallback for attribute-position values or static templates.\n // White space collapsing here protects against Vue SSR mismatches.\n const renderedValues = values.map(value => resolveValue(value));\n const markup = entry.processedStrings\n .reduce((out, str, i) => out + str + (renderedValues[i] ?? \"\"), \"\")\n .replace(/>\\s+</g, \"><\")\n .trim();\n\n // Morph existing DOM to match new markup instead of replacing it.\n const tpl = document.createElement(\"template\");\n tpl.innerHTML = markup;\n morphContent(element, tpl.content.childNodes);\n element._tplParts = new Array(values.length);\n }\n\n element._tplStrings = strings;\n element._tplValues = values.map(v => (Array.isArray(v) ? toPlainText(v) : v));\n}\n\n/**\n * Build a <template> element with comment markers.\n *\n * @param {string[]} processedStrings - Whitespace-collapsed static parts\n * @param {number} valueCount - Number of dynamic values\n * @returns {HTMLTemplateElement | null}\n */\nfunction createTemplate(processedStrings, valueCount) {\n const marker = `<!--${markerKey}-->`;\n const markup = processedStrings\n .reduce((out, str, i) => {\n const collapsed = str.replace(/>\\s+</g, \"><\");\n return out + collapsed + (i < valueCount ? marker : \"\");\n }, \"\")\n .trim();\n\n const tpl = document.createElement(\"template\");\n tpl.innerHTML = markup;\n\n // Mismatch means this template shape cannot use the clone path.\n const walker = document.createTreeWalker(tpl.content, NodeFilter.SHOW_COMMENT);\n let count = 0;\n\n while (walker.nextNode()) {\n if (walker.currentNode.data === markerKey) {\n count++;\n }\n }\n\n return count === valueCount ? tpl : null;\n}\n\n/**\n * Clone a cached template and replace comment markers\n * with actual content.\n *\n * @param {HTMLElement} element - The host element to render into\n * @param {HTMLTemplateElement} template - Cached template with markers\n * @param {Array} values - Raw interpolated values\n * @param {string[]} renderedValues - HTML-escaped rendered values\n * @returns {Array<Text | undefined>} Text node map for fast-path patching\n */\nfunction cloneAndPatch(element, template, values) {\n const clone = template.content.cloneNode(true);\n const walker = document.createTreeWalker(clone, NodeFilter.SHOW_COMMENT);\n const parts = new Array(values.length);\n const markers = [];\n let node;\n\n // Collect markers before modifying the tree\n while ((node = walker.nextNode())) {\n if (node.data === markerKey) {\n markers.push(node);\n }\n }\n\n for (let i = 0; i < markers.length; i++) {\n const value = values[i];\n\n if (isRaw(value)) {\n // Raw HTML: parse and insert as fragment\n const tmp = document.createElement(\"template\");\n tmp.innerHTML = resolveValue(value);\n markers[i].parentNode.replaceChild(tmp.content, markers[i]);\n\n // Raw values can't be fast-patched; leave parts undefined\n } else {\n // Create text node with unescaped content\n const textNode = document.createTextNode(toPlainText(value));\n markers[i].parentNode.replaceChild(textNode, markers[i]);\n parts[i] = textNode;\n }\n }\n\n element.replaceChildren(clone);\n return parts;\n}\n\n/**\n * Patches attributes and text content in-place when structure is stable,\n * preserving element identity and focus state across re-renders.\n *\n * @param {Node} parent\n * @param {NodeList} nextNodes - The desired child nodes from the new render\n */\nfunction morphContent(parent, nextNodes) {\n const current = Array.from(parent.childNodes);\n const next = Array.from(nextNodes);\n const len = Math.max(current.length, next.length);\n\n for (let i = 0; i < len; i++) {\n const cur = current[i];\n const nxt = next[i];\n\n if (!cur) {\n parent.appendChild(nxt);\n } else if (!nxt) {\n parent.removeChild(cur);\n } else if (\n cur.nodeType !== nxt.nodeType ||\n (cur.nodeType === Node.ELEMENT_NODE && cur.tagName !== nxt.tagName)\n ) {\n parent.replaceChild(nxt, cur);\n } else if (cur.nodeType === Node.TEXT_NODE) {\n if (cur.textContent !== nxt.textContent) {\n cur.textContent = nxt.textContent;\n }\n } else if (cur.nodeType === Node.ELEMENT_NODE) {\n morphAttributes(cur, nxt);\n morphContent(cur, nxt.childNodes);\n }\n }\n}\n\n/**\n * Morhp element’s attributes without rebuilding the DOM.\n *\n * @param {Element} current - The current existing DOM element\n * @param {Element} next - The desired element from the new render\n */\nfunction morphAttributes(current, next) {\n for (let i = current.attributes.length - 1; i >= 0; i--) {\n const { name } = current.attributes[i];\n\n if (!next.hasAttribute(name)) {\n current.removeAttribute(name);\n }\n }\n\n for (let i = 0; i < next.attributes.length; i++) {\n const { name, value } = next.attributes[i];\n\n if (current.getAttribute(name) !== value) {\n current.setAttribute(name, value);\n }\n }\n}\n"],"names":["stringsCache","WeakMap","markerKey","Math","random","toString","slice","renderTemplate","element","strings","values","_tplStrings","_tplParts","i","length","v","comparable","Array","isArray","toPlainText","_tplValues","isRaw","textContent","patchTextNodes","entry","get","processedStrings","from","collapseWhitespace","template","createTemplate","set","clone","content","cloneNode","walker","document","createTreeWalker","NodeFilter","SHOW_COMMENT","parts","markers","node","nextNode","data","push","value","tmp","createElement","innerHTML","resolveValue","parentNode","replaceChild","textNode","createTextNode","replaceChildren","cloneAndPatch","renderedValues","map","markup","reduce","out","str","replace","trim","tpl","morphContent","childNodes","fullRender","valueCount","marker","count","currentNode","parent","nextNodes","current","next","len","max","cur","nxt","nodeType","Node","ELEMENT_NODE","tagName","TEXT_NODE","morphAttributes","removeChild","appendChild","attributes","name","hasAttribute","removeAttribute","getAttribute","setAttribute"],"mappings":"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"}
package/dist/utils.js CHANGED
@@ -1,2 +1,2 @@
1
- function n(n,t){"undefined"!=typeof window&&"customElements"in window&&(window.customElements.get(n)||window.customElements.define(n,t))}function t(n){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return String(n).replace(/[&<>"']/g,n=>t[n])}function e(n,...e){const r=n.reduce((n,r,o)=>{const i=e[o];return n+r+(i&&i.__raw?String(i):t(String(i??"")))},"");return{__raw:!0,strings:n,values:e,toString:()=>r}}function r(n){return{__raw:!0,toString:()=>n??""}}const o={__raw:!0,toString:()=>""};export{n as defineElement,t as escapeHtml,e as html,o as nothing,r as unsafeHTML};
1
+ function n(n,r){"undefined"!=typeof window&&"customElements"in window&&(window.customElements.get(n)||window.customElements.define(n,r))}function r(n){const r={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};return String(n).replace(/[&<>"']/g,n=>r[n])}function t(n){return Array.isArray(n)?n.map(n=>e(n)).join(""):e(n)}function e(n){return n?.__raw?String(n):r(String(n??""))}function i(n,...r){let e;return{__raw:!0,strings:n,values:r,toString:()=>(void 0===e&&(e=n.reduce((n,e,i)=>n+e+t(r[i]),"")),e)}}function o(n){return{__raw:!0,toString:()=>n??""}}const a=Object.freeze({__raw:!0,toString:()=>""}),u=n=>Array.isArray(n)?n.some(n=>n?.__raw):n?.__raw,c=n=>Array.isArray(n)?n.map(n=>String(n??"")).join(""):String(n??"");function s(n){return n.replace(/>\n\s*/g,">").replace(/\n\s*</g,"<").replace(/\n\s*/g," ")}export{s as collapseWhitespace,n as defineElement,r as escapeHtml,i as html,u as isRaw,a as nothing,t as resolveValue,c as toPlainText,o as unsafeHTML};
2
2
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../src/common/utils.js"],"sourcesContent":["/**\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 * Renders a string as HTML rather than text.\n *\n * @param {string} str - The raw HTML string to trust.\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function unsafeHTML(str) {\n return { __raw: true, toString: () => str ?? \"\" };\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"],"names":["defineElement","tagName","Element","window","customElements","get","define","escapeHtml","str","Escape","String","replace","c","html","strings","values","result","reduce","acc","i","v","__raw","toString","unsafeHTML","nothing"],"mappings":"AAMO,SAASA,EAAcC,EAASC,GACf,oBAAXC,QAA0B,mBAAoBA,SAClDA,OAAOC,eAAeC,IAAIJ,IAC7BE,OAAOC,eAAeE,OAAOL,EAASC,GAG5C,CAQO,SAASK,EAAWC,GACzB,MAAMC,EAAS,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,OAAQ,IAAK,SAAU,IAAK,SAC7E,OAAOC,OAAOF,GAAKG,QAAQ,WAAYC,GAAKH,EAAOG,GACrD,CAWO,SAASC,EAAKC,KAAYC,GAC/B,MAAMC,EAASF,EAAQG,OAAO,CAACC,EAAKV,EAAKW,KACvC,MAAMC,EAAIL,EAAOI,GACjB,OAAOD,EAAMV,GAAOY,GAAKA,EAAEC,MAAQX,OAAOU,GAAKb,EAAWG,OAAOU,GAAK,OACrE,IACH,MAAO,CAAEC,OAAO,EAAMP,UAASC,SAAQO,SAAU,IAAMN,EACzD,CAQO,SAASO,EAAWf,GACzB,MAAO,CAAEa,OAAO,EAAMC,SAAU,IAAMd,GAAO,GAC/C,CASY,MAACgB,EAAU,CAAEH,OAAO,EAAMC,SAAU,IAAM"}
1
+ {"version":3,"file":"utils.js","sources":["../src/common/utils.js"],"sourcesContent":["/**\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 * Resolve an interpolated template value to its\n * HTML string representation.\n *\n * @param {*} value\n * @returns {string}\n */\nexport function resolveValue(value) {\n if (Array.isArray(value)) {\n return value.map(item => resolveItem(item)).join(\"\");\n }\n return resolveItem(value);\n}\n\n/**\n * Resolve a single value to its HTML string\n * representation.\n *\n * @param {*} value\n * @returns {string}\n */\nfunction resolveItem(value) {\n return value?.__raw ? String(value) : escapeHtml(String(value ?? \"\"));\n}\n\n/**\n * Tagged template for trusted HTML. Use as the return value\n * of render(), or for sub-fragments inside render methods.\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 let str;\n return {\n __raw: true,\n strings,\n values,\n toString: () => {\n if (str === undefined) {\n str = strings.reduce((acc, s, i) => {\n return acc + s + resolveValue(values[i]);\n }, \"\");\n }\n return str;\n },\n };\n}\n\n/**\n * Renders a string as HTML rather than text.\n *\n * @param {string} str - The raw HTML string to trust.\n * @returns {{ __raw: true, toString(): string }}\n */\nexport function unsafeHTML(str) {\n return { __raw: true, toString: () => str ?? \"\" };\n}\n\n/**\n * A placeholder you can return from a conditional expression\n * inside a template to render nothing.\n *\n * @type {{ __raw: true, toString(): string }}\n */\nexport const nothing = Object.freeze({ __raw: true, toString: () => \"\" });\n\n/**\n * Check if a value contains trusted HTML fragments.\n *\n * @param {*} value\n * @returns {boolean}\n */\nexport const isRaw = value =>\n Array.isArray(value) ? value.some(item => item?.__raw) : value?.__raw;\n\n/**\n * Convert a value to its plain text string.\n *\n * @param {*} value\n * @returns {string}\n */\nexport const toPlainText = value =>\n Array.isArray(value) ? value.map(item => String(item ?? \"\")).join(\"\") : String(value ?? \"\");\n\n/**\n * Collapse whitespace from a static string part.\n *\n * @param {string} string\n * @returns {string}\n */\nexport function collapseWhitespace(string) {\n return string\n .replace(/>\\n\\s*/g, \">\") // newline after tag close\n .replace(/\\n\\s*</g, \"<\") // newline before tag open\n .replace(/\\n\\s*/g, \" \"); // newline in text content, preserve word boundary\n}\n"],"names":["defineElement","tagName","Element","window","customElements","get","define","escapeHtml","str","Escape","String","replace","c","resolveValue","value","Array","isArray","map","item","resolveItem","join","__raw","html","strings","values","toString","undefined","reduce","acc","s","i","unsafeHTML","nothing","Object","freeze","isRaw","some","toPlainText","collapseWhitespace","string"],"mappings":"AAMO,SAASA,EAAcC,EAASC,GACf,oBAAXC,QAA0B,mBAAoBA,SAClDA,OAAOC,eAAeC,IAAIJ,IAC7BE,OAAOC,eAAeE,OAAOL,EAASC,GAG5C,CAQO,SAASK,EAAWC,GACzB,MAAMC,EAAS,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,OAAQ,IAAK,SAAU,IAAK,SAC7E,OAAOC,OAAOF,GAAKG,QAAQ,WAAYC,GAAKH,EAAOG,GACrD,CASO,SAASC,EAAaC,GAC3B,OAAIC,MAAMC,QAAQF,GACTA,EAAMG,IAAIC,GAAQC,EAAYD,IAAOE,KAAK,IAE5CD,EAAYL,EACrB,CASA,SAASK,EAAYL,GACnB,OAAOA,GAAOO,MAAQX,OAAOI,GAASP,EAAWG,OAAOI,GAAS,IACnE,CAUO,SAASQ,EAAKC,KAAYC,GAC/B,IAAIhB,EACJ,MAAO,CACLa,OAAO,EACPE,UACAC,SACAC,SAAU,UACIC,IAARlB,IACFA,EAAMe,EAAQI,OAAO,CAACC,EAAKC,EAAGC,IACrBF,EAAMC,EAAIhB,EAAaW,EAAOM,IACpC,KAEEtB,GAGb,CAQO,SAASuB,EAAWvB,GACzB,MAAO,CAAEa,OAAO,EAAMI,SAAU,IAAMjB,GAAO,GAC/C,CAQY,MAACwB,EAAUC,OAAOC,OAAO,CAAEb,OAAO,EAAMI,SAAU,IAAM,KAQvDU,EAAQrB,GACnBC,MAAMC,QAAQF,GAASA,EAAMsB,KAAKlB,GAAQA,GAAMG,OAASP,GAAOO,MAQrDgB,EAAcvB,GACzBC,MAAMC,QAAQF,GAASA,EAAMG,IAAIC,GAAQR,OAAOQ,GAAQ,KAAKE,KAAK,IAAMV,OAAOI,GAAS,IAQnF,SAASwB,EAAmBC,GACjC,OAAOA,EACJ5B,QAAQ,UAAW,KACnBA,QAAQ,UAAW,KACnBA,QAAQ,SAAU,IACvB"}
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@elenajs/core",
3
- "version": "0.15.0",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "Elena is a simple, tiny library for building Progressive Web Components.",
5
5
  "author": "Elena <hi@elenajs.com>",
6
6
  "homepage": "https://elenajs.com/",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/getelena/elena.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/getelena/elena/issues"
14
+ },
7
15
  "license": "MIT",
8
16
  "publishConfig": {
9
17
  "access": "public"
@@ -11,6 +19,13 @@
11
19
  "main": "./dist/elena.js",
12
20
  "types": "./dist/elena.d.ts",
13
21
  "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/elena.d.ts",
25
+ "default": "./dist/elena.js"
26
+ },
27
+ "./bundle": "./dist/bundle.js"
28
+ },
14
29
  "files": [
15
30
  "dist"
16
31
  ],
@@ -22,19 +37,21 @@
22
37
  "test": "vitest run --coverage",
23
38
  "test:visual": "npx playwright test",
24
39
  "test:visual:update": "npx playwright test --update-snapshots",
25
- "bench": "vitest bench",
40
+ "bench": "vitest bench --config vitest.bench.config.mjs",
26
41
  "clean": "rm -rf dist/"
27
42
  },
28
43
  "devDependencies": {
44
+ "@vitest/browser": "4.0.18",
45
+ "@vitest/browser-playwright": "4.0.18",
29
46
  "@playwright/test": "^1.58.2",
30
47
  "@rollup/plugin-terser": "0.4.4",
31
48
  "@vitest/coverage-v8": "4.0.18",
32
- "happy-dom": "20.7.0",
33
- "rollup": "4.58.0",
49
+ "happy-dom": "20.8.3",
50
+ "lit": "^3.3.0",
51
+ "rollup": "4.59.0",
34
52
  "rollup-plugin-summary": "3.0.1",
35
- "serve": "^14.2.5",
53
+ "serve": "^14.2.6",
36
54
  "typescript": "5.9.3",
37
55
  "vitest": "4.0.18"
38
- },
39
- "gitHead": "b4c41483e5196b542a1b87361f7d37222737fccc"
56
+ }
40
57
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Ariel Salminen https://arielsalminen.com
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.