@elenajs/core 1.0.0-rc.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -29
- package/dist/bundle.js +2 -2
- package/dist/common/props.d.ts +2 -2
- package/dist/common/props.d.ts.map +1 -1
- package/dist/common/render.d.ts +2 -2
- package/dist/common/utils.d.ts +4 -7
- package/dist/common/utils.d.ts.map +1 -1
- package/dist/elena.d.ts +6 -1
- package/dist/elena.d.ts.map +1 -1
- package/dist/elena.js +2 -2
- package/dist/render.js +1 -1
- package/dist/utils.js +1 -1
- package/package.json +22 -10
- package/src/common/props.js +1 -1
- package/src/common/render.js +103 -45
- package/src/common/utils.js +33 -24
- package/src/elena.js +61 -41
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
<br/>
|
|
1
2
|
<div align="center">
|
|
2
3
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://elenajs.com/
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://elenajs.com/elena-v2-dark.png" alt="Elena" width="127" height="156">
|
|
4
5
|
</source>
|
|
5
|
-
<source media="(prefers-color-scheme: light)" srcset="https://elenajs.com/
|
|
6
|
+
<source media="(prefers-color-scheme: light)" srcset="https://elenajs.com/elena-v3.png" alt="Elena" width="127" height="156">
|
|
6
7
|
</source>
|
|
7
|
-
<img src="https://elenajs.com/
|
|
8
|
+
<img src="https://elenajs.com/elena-v2.png" alt="Elena" width="127" height="156">
|
|
8
9
|
</picture>
|
|
9
10
|
|
|
10
|
-
### Simple, tiny library for building Progressive Web Components
|
|
11
|
+
### Simple, tiny library for building Progressive Web Components
|
|
11
12
|
|
|
12
13
|
<br/>
|
|
13
14
|
|
|
@@ -22,44 +23,80 @@
|
|
|
22
23
|
|
|
23
24
|
<br/>
|
|
24
25
|
|
|
25
|
-
<p align="center">Elena is a simple, tiny library
|
|
26
|
+
<p align="center">Elena is a simple, tiny library for building <a href="https://elenajs.com/">Progressive Web Components</a>. Unlike most web component libraries, Elena doesn’t force JavaScript for everything. You can load HTML and CSS first, then use JavaScript to progressively add interactivity.</p>
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- 🔋 **Extremely lightweight:** 2.9kB minified & compressed, simple and tiny by design.
|
|
31
|
+
- 📈 **Progressively enhanced:** Renders HTML & CSS first, then hydrates with JavaScript.
|
|
32
|
+
- 🫶 **Accessible by default:** Semantic HTML foundation with no Shadow DOM barriers.
|
|
33
|
+
- 🌍 **Standards based:** Built entirely on native custom elements & web standards.
|
|
34
|
+
- ⚡ **Reactive updates:** Prop and state changes trigger efficient, batched re-renders.
|
|
35
|
+
- 🎨 **Scoped styles:** Simple & clean CSS encapsulation without complex workarounds.
|
|
36
|
+
- 🖥️ **SSR friendly:** Works out of the box, with optional server-side utilities if needed.
|
|
37
|
+
- 🧩 **Zero dependencies:** No runtime dependencies, runs entirely on the web platform.
|
|
38
|
+
- 🔓 **Zero lock-in:** Works with every major framework, or no framework at all.
|
|
26
39
|
|
|
27
|
-
##
|
|
40
|
+
## Usage
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
To install Elena as a dependency, run:
|
|
30
43
|
|
|
31
44
|
```sh
|
|
32
45
|
npm install @elenajs/core
|
|
33
46
|
```
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
Then import Elena in a web component:
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
```js
|
|
51
|
+
import { Elena } from "@elenajs/core";
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| [`@elenajs/components`](https://github.com/getelena/elena/tree/main/packages/components) | Elena demo web components. | [](https://www.npmjs.com/package/@elenajs/components) |  |
|
|
43
|
-
| [`@elenajs/bundler`](https://github.com/getelena/elena/tree/main/packages/bundler) | Elena bundler for component libraries. | [](https://www.npmjs.com/package/@elenajs/bundler) |  |
|
|
44
|
-
| [`@elenajs/cli`](https://github.com/getelena/elena/tree/main/packages/cli) | Elena CLI for scaffolding web components. | [](https://www.npmjs.com/package/@elenajs/cli) |  |
|
|
45
|
-
| [`@elenajs/plugin-cem-define`](https://github.com/getelena/elena/tree/main/packages/plugin-cem-define) | Elena CEM Define plugin. | [](https://www.npmjs.com/package/@elenajs/plugin-cem-define) |  |
|
|
46
|
-
| [`@elenajs/plugin-cem-prop`](https://github.com/getelena/elena/tree/main/packages/plugin-cem-prop) | Elena CEM Prop plugin. | [](https://www.npmjs.com/package/@elenajs/plugin-cem-prop) |  |
|
|
47
|
-
| [`@elenajs/plugin-cem-tag`](https://github.com/getelena/elena/tree/main/packages/plugin-cem-tag) | Elena CEM Tag plugin. | [](https://www.npmjs.com/package/@elenajs/plugin-cem-tag) |  |
|
|
48
|
-
| [`@elenajs/plugin-cem-typescript`](https://github.com/getelena/elena/tree/main/packages/plugin-cem-typescript) | Elena CEM TypeScript plugin. | [](https://www.npmjs.com/package/@elenajs/plugin-cem-typescript) |  |
|
|
49
|
-
| [`@elenajs/plugin-rollup-css`](https://github.com/getelena/elena/tree/main/packages/plugin-rollup-css) | Elena Rollup CSS plugin. | [](https://www.npmjs.com/package/@elenajs/plugin-rollup-css) |  |
|
|
50
|
-
| [`@elenajs/ssr`](https://github.com/getelena/elena/tree/main/packages/ssr) | Elena server-side rendering tools. | [](https://www.npmjs.com/package/@elenajs/ssr) |  |
|
|
51
|
-
| [`@elenajs/mcp`](https://github.com/getelena/elena/tree/main/packages/ssr) | Elena MCP server. | [](https://www.npmjs.com/package/@elenajs/mcp) |  |
|
|
53
|
+
class Stack extends Elena(HTMLElement) {
|
|
54
|
+
static tagName = "my-stack";
|
|
55
|
+
static props = ["direction"];
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
direction = "column";
|
|
58
|
+
}
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
Stack.define();
|
|
61
|
+
```
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
**See the full documentation at [elenajs.com](https://elenajs.com).**
|
|
58
64
|
|
|
59
|
-
##
|
|
65
|
+
## Why was Elena created
|
|
66
|
+
|
|
67
|
+
Elena was created by [@arielle](https://arielsalminen.com/) after nearly a decade of building enterprise-scale design systems with web components. The recurring pain points were often similar: accessibility issues, server-side rendering, layout shifts, flash of invisible content, React Server Components, too much reliance on client side JavaScript, and compatibility with e.g. third party analytics tools.
|
|
68
|
+
|
|
69
|
+
Elena was built to solve these problems while staying grounded in web standards and what the platform natively provides. This is how [Progressive Web Components](https://arielsalminen.com/2026/progressive-web-components/) were born.
|
|
70
|
+
|
|
71
|
+
## Why should I use Elena
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
**Elena is built for teams creating component libraries and design systems.** If you need web components that work across multiple frameworks (such as [React](https://react.dev), [Next.js](https://nextjs.org), [Vue](https://vuejs.org), [Angular](https://angular.dev)), render HTML and CSS before JavaScript loads, and sidestep common issues like accessibility problems, SSR limitations, and layout shifts, Elena is built for exactly that.
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
It handles the cross-framework complexity (prop/attribute syncing, event delegation, framework compatibility) so you can focus on building components rather than plumbing.
|
|
76
|
+
|
|
77
|
+
## Next steps
|
|
78
|
+
|
|
79
|
+
- Start with the [Quick Start](https://elenajs.com/start/) guide.
|
|
80
|
+
- View the [Live examples](https://elenajs.com/examples/) for demos.
|
|
81
|
+
- Try Elena in the [Playground](https://elenajs.com/playground/).
|
|
82
|
+
- Read how [Elena compares](https://elenajs.com/advanced/faq#how-does-elena-compare-against-other-tools) against other web component libraries.
|
|
83
|
+
- Browse our [FAQ](https://elenajs.com/advanced/faq) for frequently asked questions.
|
|
84
|
+
|
|
85
|
+
## Provided tools
|
|
86
|
+
|
|
87
|
+
Elena is a monorepo containing several tools (13 in total!) published to npm under the `@elenajs` scope. These are the main tools intended for development:
|
|
88
|
+
|
|
89
|
+
| Package | Description | Version | Stability |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| [`@elenajs/core`](https://github.com/getelena/elena/tree/main/packages/core) | Elena core runtime library. | [](https://www.npmjs.com/package/@elenajs/core) |  |
|
|
92
|
+
| [`@elenajs/components`](https://github.com/getelena/elena/tree/main/packages/components) | Elena demo web components. | [](https://www.npmjs.com/package/@elenajs/components) |  |
|
|
93
|
+
| [`@elenajs/bundler`](https://github.com/getelena/elena/tree/main/packages/bundler) | Elena bundler for component libraries. | [](https://www.npmjs.com/package/@elenajs/bundler) |  |
|
|
94
|
+
| [`@elenajs/cli`](https://github.com/getelena/elena/tree/main/packages/cli) | Elena CLI for scaffolding web components. | [](https://www.npmjs.com/package/@elenajs/cli) |  |
|
|
95
|
+
| [`@elenajs/ssr`](https://github.com/getelena/elena/tree/main/packages/ssr) | Elena server-side rendering tools. | [](https://www.npmjs.com/package/@elenajs/ssr) |  |
|
|
96
|
+
| [`@elenajs/mcp`](https://github.com/getelena/elena/tree/main/packages/ssr) | Elena MCP server. | [](https://www.npmjs.com/package/@elenajs/mcp) |  |
|
|
97
|
+
|
|
98
|
+
<!-- https://github.com/orangemug/stability-badges -->
|
|
99
|
+
|
|
100
|
+
## License
|
|
64
101
|
|
|
65
|
-
Copyright © 2026 [Ariel Salminen](https://arielsalminen.com)
|
|
102
|
+
Released under the MIT License. Copyright © 2025-2026 [Ariel Salminen](https://arielsalminen.com).
|
package/dist/bundle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @elenajs/core v1.0.0
|
|
2
|
+
* @elenajs/core v1.0.0
|
|
3
3
|
* (c) 2025-present Ariel Salminen and Elena contributors
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
|
-
const t="░█ [ELENA]: ",s=Array.isArray,i=s=>console.warn(t+s),e={"&":"&","<":"<",">":">",'"':""","'":"'"};function
|
|
6
|
+
const t="░█ [ELENA]: ",s=Array.isArray,i=Symbol("elena.raw"),n=s=>console.warn(t+s),e={"&":"&","<":"<",">":">",'"':""","'":"'"};function o(t){return s(t)?t.map(r).join(""):r(t)}function r(t){return t?.[i]?String(t):String(t??"").replace(/[&<>"']/g,t=>e[t])}class h{constructor(t,s){this.strings=t,this.values=s}toString(){return null==this.t&&(this.t=this.strings.reduce((t,s,i)=>t+s+o(this.values[i]),"")),this.t}}function c(t,...s){return new h(t,s)}function l(t){return{[i]:!0,toString:()=>t??""}}h.prototype[i]=!0;const u={[i]:!0,toString:()=>""},f=t=>s(t)?t.some(t=>t?.[i]):!!t?.[i],a=t=>s(t)?t.join(""):String(t??"");function d(t){return t.replace(/(>)\n\s*|\n\s*(<)/g,"$1$2").replace(/\n\s*/g," ").replace(/>\s+</g,"><")}function p(t,s,i){if(s="boolean"===t&&"boolean"!=typeof s?null!==s:s,!i)return s;if("toAttribute"===i)switch(t){case"object":case"array":return s&&JSON.stringify(s);case"boolean":return s?"":null;default:return""===s?null:s}else switch(t){case"object":case"array":if(!s)return s;try{return JSON.parse(s)}catch{return n("Invalid JSON: "+s),null}case"number":return null!==s?+s:s;default:return s??""}}function g(t,s,i){t?null===i?t.removeAttribute(s):t.setAttribute(s,i):n("Cannot sync attrs.")}const y=new WeakMap,b="e"+Math.random().toString(36).slice(2),S=()=>document.createElement("template"),m=t=>document.createTreeWalker(t,128);function _(t,s){const i=`\x3c!--${b}--\x3e`,n=[];let e="";for(let o=0;o<t.length;o++)if(e+=t[o],o<s){const s=t[o].match(/([^\s"'>/=]+)\s*=\s*["']$/);s?(n.push(s[1]),e+=b+"_"+o):(n.push(null),e+=i)}const o=S();o.innerHTML=e.trim();const r=m(o.content);let h=0;for(;r.nextNode();)r.currentNode.data===b&&h++;return h!==n.filter(t=>null===t).length?null:{i:o,o:n}}function w(t,s){const i=Array.from(t.childNodes),n=Array.from(s),e=Math.max(i.length,n.length);for(let s=0;s<e;s++){const e=i[s],o=n[s];e?o?e.nodeType!==o.nodeType||1===e.nodeType&&e.tagName!==o.tagName?t.replaceChild(o,e):3===e.nodeType?e.textContent!==o.textContent&&(e.textContent=o.textContent):1===e.nodeType&&(v(e,o),w(e,o.childNodes)):t.removeChild(e):t.appendChild(o)}}function v(t,s){for(let i=t.attributes.length-1;i>=0;i--){const{name:n}=t.attributes[i];s.hasAttribute(n)||t.removeAttribute(n)}for(let i=0;i<s.attributes.length;i++){const{name:n,value:e}=s.attributes[i];t.getAttribute(n)!==e&&t.setAttribute(n,e)}}const x=new WeakSet,C=(t,s)=>Object.prototype.hasOwnProperty.call(t,s);function A(i){return class extends i{element=null;attributeChangedCallback(t,s,i){if(super.attributeChangedCallback?.(t,s,i),"text"!==t){if(s!==i)if(this.h&&!this.l){const s=this.u.get(t),n=typeof s,e="string"===n?i??"":p(n,i,"toProp");e!==s&&this.u.set(t,e),this.p()}else this.S=!0,function(t,s,i,e){if(i!==e){const i=typeof t[s];"undefined"===i&&n(`Prop "${s}" has no default.`);const o=p(i,e,"toProp");t[s]=o}}(this,t,s,i),this.S=!1}else this.text=i??""}static get observedAttributes(){if(this.m)return this.m;const t=(this.props||[]).map(t=>"string"==typeof t?t:t.name);return this.m=[...t,"text"],this.m}connectedCallback(){super.connectedCallback?.(),this._(),this.v(),this.h||void 0!==this.C||(this.text=this.textContent.trim()),this.A(),this.k=this.P??this.shadowRoot??this,this.$??=()=>{try{this.M()}catch(s){console.error(t,s)}},this.willUpdate(),this.N(),this.O(),this.j(),this.h||(this.h=!0,this.setAttribute("hydrated",""),this.firstUpdated()),this.updated()}_(){const t=this.constructor;if(x.has(t))return;const s=new Set,i=[];if(t.props){for(const n of t.props)"string"==typeof n?i.push(n):(i.push(n.name),!1===n.reflect&&s.add(n.name));i.includes("text")&&n('"text" is reserved.'),function(t,s,i){for(const n of s){const s=!i||!i.has(n);Object.defineProperty(t,n,{configurable:!0,enumerable:!0,get(){return this.u?.get(n)},set(t){if(this.u||(this.u=new Map),t!==this.u.get(n)&&(this.u.set(n,t),this.isConnected))if(s){if(!this.S){const s=p(typeof t,t,"toAttribute");g(this,n,s)}}else this.h&&!this.l&&this.p()}})}}(t.prototype,i,s)}if(t.U=i,t.q=s,t.J=t.events||null,t.J)for(const s of t.J)C(t.prototype,s)||(t.prototype[s]=function(...t){return this.element[s](...t)});var e;t.R=(e=t.element)?t=>t.querySelector(e):t=>t.firstElementChild,x.add(t)}v(){this.S=!0;for(const t of this.constructor.U)if(C(this,t)){const s=this[t];delete this[t],this[t]=s}this.S=!1}A(){const t=this.constructor;if(!t.shadow)return;if(!this.P&&!this.shadowRoot){const s={mode:t.shadow};t.registry&&(s.customElementRegistry=t.registry),this.P=this.attachShadow(s)}const s=this.P??this.shadowRoot;if(t.styles){if(!t.W){const s=[t.styles].flat();t.W=s.map(t=>{if("string"==typeof t){const s=new CSSStyleSheet;return s.replaceSync(t),s}return t})}s.adoptedStyleSheets=t.W}}N(){const t=this.constructor,i=this.k,e=this.render();if(e&&e.strings&&!function(t,i,n){if(t.D!==i||!t.F)return!1;const e=t.F,o=t.I;for(let t=0;t<n.length;t++){const i=n[t],r=s(i)?a(i):i;if(r===o[t])continue;if(f(i)&&i!==u)return!1;const h=e[t];if(!h)return!1;o[t]=r;const c=String(r??"");h.nodeType?h.textContent=c:h[0].setAttribute(h[1],c)}return!0}(r=i,h=e.strings,c=e.values)&&(function(t,i,n){let e=y.get(i);if(!e){const t=i.map(d);e={L:t,T:n.length>0?_(t,n.length):null},y.set(i,e)}if(e.T)t.F=function(t,i,n){const{i:e,o:r}=i,h=e.content.cloneNode(!0),c=m(h),l=Array(n.length),d=[];let p;for(;p=c.nextNode();)p.data===b&&d.push(p);let g=0;for(let t=0;t<n.length;t++){const i=r[t];if(i){const e=h.querySelector(`[${i}="${b+"_"+t}"]`);if(e){const o=n[t],r=String((s(o)?a(o):o)??"");e.setAttribute(i,r),l[t]=[e,i]}}else{const s=d[g++],i=n[t];if(f(i)&&i!==u){const t=S();t.innerHTML=o(i),s.parentNode.replaceChild(t.content,s)}else{const n=document.createTextNode(a(i));s.parentNode.replaceChild(n,s),l[t]=n}}}return t.D?(w(t,h.childNodes),null):(t.replaceChildren(h),l)}(t,e.T,n);else{const s=n.map(o),i=e.L.reduce((t,i,n)=>t+i+(s[n]??""),"").replace(/>\s+</g,"><").trim(),r=S();r.innerHTML=i,w(t,r.content.childNodes),t.F=null}t.D=i,t.I=n.map(t=>s(t)?a(t):t)}(r,h,c),1)){const s=this.element;if(this.element=t.R(i),this.B&&s&&this.element!==s){const i=t.J;for(const t of i)s.removeEventListener(t,this),this.element.addEventListener(t,this)}}var r,h,c;this.element||(this.element=t.R(i),this.element||(t.element&&n("Element not found."),this.element=i.firstElementChild))}O(){if(this.u){const t=this.constructor.q;for(const[s,i]of this.u){if(t.has(s))continue;const n=p(typeof i,i,"toAttribute");(null!==n||this.hasAttribute(s))&&g(this,s,n)}}}j(){const t=this.constructor.J;if(!this.B&&t?.length)if(this.element){this.B=!0;for(const s of t)this.element.addEventListener(s,this)}else n("Cannot add events.")}render(){}willUpdate(){}firstUpdated(){}updated(){}adoptedCallback(){super.adoptedCallback?.()}disconnectedCallback(){if(super.disconnectedCallback?.(),this.B){this.B=!1;for(const t of this.constructor.J)this.element?.removeEventListener(t,this)}}handleEvent(t){this.constructor.J?.includes(t.type)&&(t.bubbles&&(t.composed||this.k===this)||this.dispatchEvent(new Event(t.type,{bubbles:t.bubbles})))}get text(){return this.C??""}set text(t){const s=this.C;this.C=t,this.h&&s!==t&&!this.l&&this.p()}static define(t){const s=this.tagName;s?function(t,s,i){const n=i??globalThis.customElements;n?.get(t)||n?.define(t,s)}(s,this,t):n("define() without a tagName.")}p(){this.l||this.G||(this.G=!0,queueMicrotask(this.$))}M(){this.G=!1;const t=this.H;this.H=null;try{try{this.willUpdate(),this.l=!0,this.N()}finally{this.l=!1}this.updated()}finally{this.K=null,t?.()}}get updateComplete(){return this.G?(this.K||(this.K=new Promise(t=>{this.H=t})),this.K):Promise.resolve()}requestUpdate(){this.h&&!this.l&&this.p()}}}export{A as Elena,c as html,u as nothing,l as unsafeHTML};
|
package/dist/common/props.d.ts
CHANGED
|
@@ -19,11 +19,11 @@ export function syncAttribute(element: Element, name: string, value: string | nu
|
|
|
19
19
|
* at class-creation time. Values are stored per-instance
|
|
20
20
|
* via a `_props` Map that is lazily created.
|
|
21
21
|
*
|
|
22
|
-
* @param {
|
|
22
|
+
* @param {Object} proto - The class prototype
|
|
23
23
|
* @param {string[]} propNames - Prop names to define
|
|
24
24
|
* @param {Set<string>} [noReflect] - Props that should not reflect to attributes
|
|
25
25
|
*/
|
|
26
|
-
export function setProps(proto:
|
|
26
|
+
export function setProps(proto: Object, propNames: string[], noReflect?: Set<string>): void;
|
|
27
27
|
/**
|
|
28
28
|
* We need to update the internals of the Elena Element
|
|
29
29
|
* when props on the host element are changed.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../src/common/props.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,GAAG,cACH,aAAa,GAAG,QAAQ,OAqClC;AAED;;;;;;GAMG;AACH,uCAJW,OAAO,QACP,MAAM,SACN,MAAM,GAAG,IAAI,QAYvB;AAED;;;;;;;;GAQG;AACH,
|
|
1
|
+
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../src/common/props.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,GAAG,cACH,aAAa,GAAG,QAAQ,OAqClC;AAED;;;;;;GAMG;AACH,uCAJW,OAAO,QACP,MAAM,SACN,MAAM,GAAG,IAAI,QAYvB;AAED;;;;;;;;GAQG;AACH,gCAJW,MAAM,aACN,MAAM,EAAE,cACR,GAAG,CAAC,MAAM,CAAC,QAsCrB;AAED;;;;;;;;GAQG;AACH,kCALW,MAAM,QACN,MAAM,YACN,GAAG,YACH,GAAG,QAWb"}
|
package/dist/common/render.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Render a tagged template into an Elena Element with DOM diffing.
|
|
3
|
-
* Returns true if the DOM was fully rebuilt, false if
|
|
4
|
-
*
|
|
3
|
+
* Returns true if the DOM was fully rebuilt, false if parts were
|
|
4
|
+
* patched in place.
|
|
5
5
|
*
|
|
6
6
|
* @param {HTMLElement} element
|
|
7
7
|
* @param {TemplateStringsArray} strings - Static parts of the tagged template
|
package/dist/common/utils.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param {string} tagName
|
|
5
5
|
* @param {Function} Element
|
|
6
6
|
*/
|
|
7
|
-
export function defineElement(tagName: string, Element: Function): void;
|
|
7
|
+
export function defineElement(tagName: string, Element: Function, registry: any): void;
|
|
8
8
|
export function escapeHtml(str: any): string;
|
|
9
9
|
/**
|
|
10
10
|
* Resolve an interpolated template value to its
|
|
@@ -20,10 +20,9 @@ export function resolveValue(value: any): string;
|
|
|
20
20
|
*
|
|
21
21
|
* @param {TemplateStringsArray} strings
|
|
22
22
|
* @param {...*} values
|
|
23
|
-
* @returns {{
|
|
23
|
+
* @returns {{ strings: TemplateStringsArray, values: Array, toString(): string }}
|
|
24
24
|
*/
|
|
25
25
|
export function html(strings: TemplateStringsArray, ...values: any[]): {
|
|
26
|
-
__raw: true;
|
|
27
26
|
strings: TemplateStringsArray;
|
|
28
27
|
values: any[];
|
|
29
28
|
toString(): string;
|
|
@@ -32,10 +31,9 @@ export function html(strings: TemplateStringsArray, ...values: any[]): {
|
|
|
32
31
|
* Renders a string as HTML rather than text.
|
|
33
32
|
*
|
|
34
33
|
* @param {string} str - The raw HTML string to trust.
|
|
35
|
-
* @returns {{
|
|
34
|
+
* @returns {{ toString(): string }}
|
|
36
35
|
*/
|
|
37
36
|
export function unsafeHTML(str: string): {
|
|
38
|
-
__raw: true;
|
|
39
37
|
toString(): string;
|
|
40
38
|
};
|
|
41
39
|
/**
|
|
@@ -50,10 +48,9 @@ export function warn(msg: string): void;
|
|
|
50
48
|
* A placeholder you can return from a conditional expression
|
|
51
49
|
* inside a template to render nothing.
|
|
52
50
|
*
|
|
53
|
-
* @type {{
|
|
51
|
+
* @type {{ toString(): string }}
|
|
54
52
|
*/
|
|
55
53
|
export const nothing: {
|
|
56
|
-
__raw: true;
|
|
57
54
|
toString(): string;
|
|
58
55
|
};
|
|
59
56
|
export function isRaw(value: any): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.js"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,uCAHW,MAAM,0CAMhB;AASD,6CAEC;AAED;;;;;;GAMG;AACH,oCAHW,GAAC,GACC,MAAM,CAOlB;AAkCD;;;;;;;GAOG;AACH,8BAJW,oBAAoB,aACjB,GAAC,EAAA,GACF;IAAE,OAAO,EAAE,oBAAoB,CAAC;IAAC,MAAM,QAAQ;IAAC,QAAQ,IAAI,MAAM,CAAA;CAAE,CAIhF;AAED;;;;;GAKG;AACH,gCAHW,MAAM,GACJ;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,CAIlC;AA0BD;;;;;GAKG;AACH,2CAHW,MAAM,GACJ,MAAM,CAOlB;AAhIM,0BAHI,MAAM,QAGoC;AA6FrD;;;;;GAKG;AACH,sBAFU;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,CAE2B;AAQpD,6BAHI,GAAC,GACC,OAAO,CAE6E;AAQ1F,mCAHI,GAAC,GACC,MAAM,CAEwE;AA3H3F,qBAAe,wBAAc,CAAC;AAC9B,iDAA8B"}
|
package/dist/elena.d.ts
CHANGED
|
@@ -13,19 +13,23 @@ export type ElenaConstructor = new (...args: any[]) => HTMLElement;
|
|
|
13
13
|
export type ElenaInstanceMembers = {
|
|
14
14
|
text: string;
|
|
15
15
|
element: HTMLElement | null;
|
|
16
|
+
updateComplete: Promise<void>;
|
|
16
17
|
render(): void;
|
|
17
18
|
willUpdate(): void;
|
|
18
19
|
firstUpdated(): void;
|
|
19
20
|
updated(): void;
|
|
21
|
+
requestUpdate(): void;
|
|
20
22
|
connectedCallback(): void;
|
|
21
23
|
disconnectedCallback(): void;
|
|
24
|
+
adoptedCallback(): void;
|
|
25
|
+
attributeChangedCallback(prop: string, oldValue: string | null, newValue: string | null): void;
|
|
22
26
|
};
|
|
23
27
|
export type ElenaPropObject = {
|
|
24
28
|
name: string;
|
|
25
29
|
reflect?: boolean;
|
|
26
30
|
};
|
|
27
31
|
export type ElenaElementConstructor = (new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & {
|
|
28
|
-
define(): void;
|
|
32
|
+
define(registry?: CustomElementRegistry): void;
|
|
29
33
|
readonly observedAttributes: string[];
|
|
30
34
|
tagName?: string;
|
|
31
35
|
props?: (string | ElenaPropObject)[];
|
|
@@ -33,6 +37,7 @@ export type ElenaElementConstructor = (new (...args: any[]) => HTMLElement & Ele
|
|
|
33
37
|
element?: string;
|
|
34
38
|
shadow?: "open" | "closed";
|
|
35
39
|
styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[];
|
|
40
|
+
registry?: CustomElementRegistry;
|
|
36
41
|
};
|
|
37
42
|
import { html } from "./common/utils.js";
|
|
38
43
|
import { unsafeHTML } from "./common/utils.js";
|
package/dist/elena.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elena.d.ts","sourceRoot":"","sources":["../src/elena.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elena.d.ts","sourceRoot":"","sources":["../src/elena.js"],"names":[],"mappings":"AAmEA;;;;;;;;;GASG;AACH,kCAHW,gBAAgB,GACd,uBAAuB,CAgfnC;+BArhBY,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW;mCAInC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,IAAI,IAAI,CAAC;IAAC,UAAU,IAAI,IAAI,CAAC;IAAC,YAAY,IAAI,IAAI,CAAC;IAAC,OAAO,IAAI,IAAI,CAAC;IAAC,aAAa,IAAI,IAAI,CAAC;IAAC,iBAAiB,IAAI,IAAI,CAAC;IAAC,oBAAoB,IAAI,IAAI,CAAC;IAAC,eAAe,IAAI,IAAI,CAAC;IAAC,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;CAAE;8BAIhW;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,CAAC,QAAQ,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACnD,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;IAC7D,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;qBA7CmE,mBAAmB;2BAAnB,mBAAmB;wBAAnB,mBAAmB"}
|
package/dist/elena.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @elenajs/core v1.0.0
|
|
2
|
+
* @elenajs/core v1.0.0
|
|
3
3
|
* (c) 2025-present Ariel Salminen and Elena contributors
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
|
-
import{
|
|
6
|
+
import{getPropValue as t,getProps as e,setProps as s,syncAttribute as i}from"./props.js";import{prefix as n,warn as r,defineElement as o}from"./utils.js";export{html,nothing,unsafeHTML}from"./utils.js";import{renderTemplate as h}from"./render.js";const a=new WeakSet,d=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);function l(l){return class extends l{element=null;attributeChangedCallback(s,i,n){if(super.attributeChangedCallback?.(s,i,n),"text"!==s){if(i!==n)if(this._hydrated&&!this._isRendering){const e=this._props.get(s),i=typeof e,r="string"===i?n??"":t(i,n,"toProp");r!==e&&this._props.set(s,r),this._safeRender()}else this._syncing=!0,e(this,s,i,n),this._syncing=!1}else this.text=n??""}static get observedAttributes(){if(this._observedAttrs)return this._observedAttrs;const t=(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._hydrated||void 0!==this._text||(this.text=this.textContent.trim()),this._attachShadow(),this._root=this._shadow??this.shadowRoot??this,this._runUpdate??=()=>{try{this._performUpdate()}catch(t){console.error(n,t)}},this.willUpdate(),this._applyRender(),this._syncProps(),this._delegateEvents(),this._hydrated||(this._hydrated=!0,this.setAttribute("hydrated",""),this.firstUpdated()),this.updated()}_setupStaticProps(){const t=this.constructor;if(a.has(t))return;const e=new Set,i=[];if(t.props){for(const s of t.props)"string"==typeof s?i.push(s):(i.push(s.name),!1===s.reflect&&e.add(s.name));i.includes("text")&&r('"text" is reserved.'),s(t.prototype,i,e)}if(t._propNames=i,t._noReflect=e,t._elenaEvents=t.events||null,t._elenaEvents)for(const e of t._elenaEvents)d(t.prototype,e)||(t.prototype[e]=function(...t){return this.element[e](...t)});var n;t._resolver=(n=t.element)?t=>t.querySelector(n):t=>t.firstElementChild,a.add(t)}_captureClassFieldDefaults(){this._syncing=!0;for(const t of this.constructor._propNames)if(d(this,t)){const e=this[t];delete this[t],this[t]=e}this._syncing=!1}_attachShadow(){const t=this.constructor;if(!t.shadow)return;if(!this._shadow&&!this.shadowRoot){const e={mode:t.shadow};t.registry&&(e.customElementRegistry=t.registry),this._shadow=this.attachShadow(e)}const e=this._shadow??this.shadowRoot;if(t.styles){if(!t._adoptedSheets){const e=[t.styles].flat();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.constructor,e=this._root,s=this.render();if(s&&s.strings&&h(e,s.strings,s.values)){const s=this.element;if(this.element=t._resolver(e),this._events&&s&&this.element!==s){const e=t._elenaEvents;for(const t of e)s.removeEventListener(t,this),this.element.addEventListener(t,this)}}this.element||(this.element=t._resolver(e),this.element||(t.element&&r("Element not found."),this.element=e.firstElementChild))}_syncProps(){if(this._props){const e=this.constructor._noReflect;for(const[s,n]of this._props){if(e.has(s))continue;const r=t(typeof n,n,"toAttribute");(null!==r||this.hasAttribute(s))&&i(this,s,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)}else r("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.bubbles&&(t.composed||this._root===this)||this.dispatchEvent(new Event(t.type,{bubbles:t.bubbles})))}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(t){const e=this.tagName;e?o(e,this,t):r("define() without a tagName.")}_safeRender(){this._isRendering||this._renderPending||(this._renderPending=!0,queueMicrotask(this._runUpdate))}_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._renderPending?(this._updateComplete||(this._updateComplete=new Promise(t=>{this._resolveUpdate=t})),this._updateComplete):Promise.resolve()}requestUpdate(){this._hydrated&&!this._isRendering&&this._safeRender()}}}export{l as Elena};
|
package/dist/render.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isArray as t,toPlainText as e,isRaw as n,
|
|
1
|
+
import{isArray as t,toPlainText as e,isRaw as n,nothing as r,collapseWhitespace as o,resolveValue as l}from"./utils.js";const s=new WeakMap,a="e"+Math.random().toString(36).slice(2),c=()=>document.createElement("template"),i=t=>document.createTreeWalker(t,128);function u(u,m,f){return!function(o,l,s){if(o._templateStrings!==l||!o._templateParts)return!1;const a=o._templateParts,c=o._templateValues;for(let o=0;o<s.length;o++){const l=s[o],i=t(l)?e(l):l;if(i===c[o])continue;if(n(l)&&l!==r)return!1;const u=a[o];if(!u)return!1;c[o]=i;const p=String(i??"");u.nodeType?u.textContent=p:u[0].setAttribute(u[1],p)}return!0}(u,m,f)&&(function(u,m,f){let h=s.get(m);if(!h){const t=m.map(o);h={_strings:t,_template:f.length>0?p(t,f.length):null},s.set(m,h)}if(h._template)u._templateParts=function(o,s,u){const{_tpl:p,_attrs:m}=s,f=p.content.cloneNode(!0),h=i(f),g=Array(u.length),_=[];let N;for(;N=h.nextNode();)N.data===a&&_.push(N);let x=0;for(let o=0;o<u.length;o++){const s=m[o];if(s){const n=f.querySelector(`[${s}="${a+"_"+o}"]`);if(n){const r=u[o],l=String((t(r)?e(r):r)??"");n.setAttribute(s,l),g[o]=[n,s]}}else{const t=_[x++],s=u[o];if(n(s)&&s!==r){const e=c();e.innerHTML=l(s),t.parentNode.replaceChild(e.content,t)}else{const n=document.createTextNode(e(s));t.parentNode.replaceChild(n,t),g[o]=n}}}return o._templateStrings?(d(o,f.childNodes),null):(o.replaceChildren(f),g)}(u,h._template,f);else{const t=f.map(l),e=h._strings.reduce((e,n,r)=>e+n+(t[r]??""),"").replace(/>\s+</g,"><").trim(),n=c();n.innerHTML=e,d(u,n.content.childNodes),u._templateParts=null}u._templateStrings=m,u._templateValues=f.map(n=>t(n)?e(n):n)}(u,m,f),!0)}function p(t,e){const n=`\x3c!--${a}--\x3e`,r=[];let o="";for(let l=0;l<t.length;l++)if(o+=t[l],l<e){const e=t[l].match(/([^\s"'>/=]+)\s*=\s*["']$/);e?(r.push(e[1]),o+=a+"_"+l):(r.push(null),o+=n)}const l=c();l.innerHTML=o.trim();const s=i(l.content);let u=0;for(;s.nextNode();)s.currentNode.data===a&&u++;return u!==r.filter(t=>null===t).length?null:{_tpl:l,_attrs:r}}function d(t,e){const n=Array.from(t.childNodes),r=Array.from(e),o=Math.max(n.length,r.length);for(let e=0;e<o;e++){const o=n[e],l=r[e];o?l?o.nodeType!==l.nodeType||1===o.nodeType&&o.tagName!==l.tagName?t.replaceChild(l,o):3===o.nodeType?o.textContent!==l.textContent&&(o.textContent=l.textContent):1===o.nodeType&&(m(o,l),d(o,l.childNodes)):t.removeChild(o):t.appendChild(l)}}function m(t,e){for(let n=t.attributes.length-1;n>=0;n--){const{name:r}=t.attributes[n];e.hasAttribute(r)||t.removeAttribute(r)}for(let n=0;n<e.attributes.length;n++){const{name:r,value:o}=e.attributes[n];t.getAttribute(r)!==o&&t.setAttribute(r,o)}}export{u as renderTemplate};
|
package/dist/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const t="░█ [ELENA]: ",n=Array.isArray,r=Symbol("elena.raw"),s=n=>console.warn(t+n);function e(t,n,r){const s=r??globalThis.customElements;s?.get(t)||s?.define(t,n)}const o={"&":"&","<":"<",">":">",'"':""","'":"'"};function i(t){return String(t).replace(/[&<>"']/g,t=>o[t])}function c(t){return n(t)?t.map(u).join(""):u(t)}function u(t){return t?.[r]?String(t):i(t??"")}class l{constructor(t,n){this.strings=t,this.values=n}toString(){return null==this._str&&(this._str=this.strings.reduce((t,n,r)=>t+n+c(this.values[r]),"")),this._str}}function a(t,...n){return new l(t,n)}function g(t){return{[r]:!0,toString:()=>t??""}}l.prototype[r]=!0;const p={[r]:!0,toString:()=>""},f=t=>n(t)?t.some(t=>t?.[r]):!!t?.[r],h=t=>n(t)?t.join(""):String(t??"");function S(t){return t.replace(/(>)\n\s*|\n\s*(<)/g,"$1$2").replace(/\n\s*/g," ").replace(/>\s+</g,"><")}export{S as collapseWhitespace,e as defineElement,i as escapeHtml,a as html,n as isArray,f as isRaw,p as nothing,t as prefix,c as resolveValue,h as toPlainText,g as unsafeHTML,s as warn};
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elenajs/core",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"keywords": [
|
|
8
|
+
"progressive web components",
|
|
9
|
+
"custom elements manifest",
|
|
10
|
+
"progressive enhancement",
|
|
11
|
+
"custom elements",
|
|
12
|
+
"web components",
|
|
13
|
+
"design system",
|
|
14
|
+
"javascript",
|
|
15
|
+
"typescript",
|
|
16
|
+
"elena",
|
|
17
|
+
"ssr"
|
|
18
|
+
],
|
|
7
19
|
"repository": {
|
|
8
20
|
"type": "git",
|
|
9
21
|
"url": "git+https://github.com/getelena/elena.git",
|
|
@@ -43,18 +55,18 @@
|
|
|
43
55
|
"clean": "rm -rf dist/"
|
|
44
56
|
},
|
|
45
57
|
"devDependencies": {
|
|
46
|
-
"@playwright/test": "1.
|
|
58
|
+
"@playwright/test": "1.59.1",
|
|
47
59
|
"@rollup/plugin-terser": "1.0.0",
|
|
48
|
-
"@vitest/browser": "4.1.
|
|
49
|
-
"@vitest/browser-playwright": "4.1.
|
|
50
|
-
"@vitest/coverage-v8": "4.1.
|
|
51
|
-
"happy-dom": "20.8.
|
|
60
|
+
"@vitest/browser": "4.1.2",
|
|
61
|
+
"@vitest/browser-playwright": "4.1.2",
|
|
62
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
63
|
+
"happy-dom": "20.8.9",
|
|
52
64
|
"lit": "3.3.2",
|
|
53
|
-
"rollup": "4.
|
|
65
|
+
"rollup": "4.60.1",
|
|
54
66
|
"rollup-plugin-summary": "3.0.1",
|
|
55
67
|
"serve": "14.2.6",
|
|
56
|
-
"typescript": "
|
|
57
|
-
"vitest": "4.1.
|
|
68
|
+
"typescript": "6.0.2",
|
|
69
|
+
"vitest": "4.1.2"
|
|
58
70
|
},
|
|
59
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "ef466853f84deb6559ea66b54cd85b7e043e9f36"
|
|
60
72
|
}
|
package/src/common/props.js
CHANGED
|
@@ -68,7 +68,7 @@ export function syncAttribute(element, name, value) {
|
|
|
68
68
|
* at class-creation time. Values are stored per-instance
|
|
69
69
|
* via a `_props` Map that is lazily created.
|
|
70
70
|
*
|
|
71
|
-
* @param {
|
|
71
|
+
* @param {Object} proto - The class prototype
|
|
72
72
|
* @param {string[]} propNames - Prop names to define
|
|
73
73
|
* @param {Set<string>} [noReflect] - Props that should not reflect to attributes
|
|
74
74
|
*/
|
package/src/common/render.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { collapseWhitespace, isArray, isRaw, resolveValue, toPlainText } from "./utils.js";
|
|
1
|
+
import { collapseWhitespace, isArray, isRaw, nothing, resolveValue, toPlainText } from "./utils.js";
|
|
2
2
|
|
|
3
3
|
const stringsCache = new WeakMap();
|
|
4
|
-
const markerKey = "e" +
|
|
4
|
+
const markerKey = "e" + Math.random().toString(36).slice(2);
|
|
5
5
|
const SHOW_COMMENT = 128;
|
|
6
6
|
const ELEMENT_NODE = 1;
|
|
7
7
|
const TEXT_NODE = 3;
|
|
@@ -11,8 +11,8 @@ const treeWalker = node => document.createTreeWalker(node, SHOW_COMMENT);
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Render a tagged template into an Elena Element with DOM diffing.
|
|
14
|
-
* Returns true if the DOM was fully rebuilt, false if
|
|
15
|
-
*
|
|
14
|
+
* Returns true if the DOM was fully rebuilt, false if parts were
|
|
15
|
+
* patched in place.
|
|
16
16
|
*
|
|
17
17
|
* @param {HTMLElement} element
|
|
18
18
|
* @param {TemplateStringsArray} strings - Static parts of the tagged template
|
|
@@ -20,54 +20,69 @@ const treeWalker = node => document.createTreeWalker(node, SHOW_COMMENT);
|
|
|
20
20
|
* @returns {boolean}
|
|
21
21
|
*/
|
|
22
22
|
export function renderTemplate(element, strings, values) {
|
|
23
|
-
if (
|
|
23
|
+
if (patch(element, strings, values)) {
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
morph(element, strings, values);
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Patch only changed text nodes and attribute values.
|
|
32
32
|
*
|
|
33
33
|
* @param {HTMLElement} element - The host element with cached template state
|
|
34
34
|
* @param {TemplateStringsArray} strings - Static parts of the tagged template
|
|
35
35
|
* @param {Array} values - Dynamic interpolated values
|
|
36
|
-
* @returns {boolean} Whether patching was sufficient (false =
|
|
36
|
+
* @returns {boolean} Whether patching was sufficient (false = do morph instead)
|
|
37
37
|
*/
|
|
38
|
-
function
|
|
38
|
+
function patch(element, strings, values) {
|
|
39
39
|
// Only works when re-rendering the same template shape
|
|
40
40
|
if (element._templateStrings !== strings || !element._templateParts) {
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const parts = element._templateParts;
|
|
45
|
+
const cached = element._templateValues;
|
|
46
|
+
|
|
44
47
|
for (let i = 0; i < values.length; i++) {
|
|
45
48
|
const v = values[i];
|
|
46
49
|
const comparable = isArray(v) ? toPlainText(v) : v;
|
|
47
50
|
|
|
48
|
-
if (comparable ===
|
|
51
|
+
if (comparable === cached[i]) {
|
|
49
52
|
continue;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
if (isRaw(v)
|
|
55
|
+
if (isRaw(v) && v !== nothing) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const part = parts[i];
|
|
60
|
+
|
|
61
|
+
if (!part) {
|
|
53
62
|
return false;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
cached[i] = comparable;
|
|
66
|
+
const str = String(comparable ?? "");
|
|
67
|
+
|
|
68
|
+
if (part.nodeType) {
|
|
69
|
+
part.textContent = str;
|
|
70
|
+
} else {
|
|
71
|
+
part[0].setAttribute(part[1], str);
|
|
72
|
+
}
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
return true;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
/**
|
|
64
|
-
*
|
|
79
|
+
* Clone a cached <template> and morph in new structure.
|
|
65
80
|
*
|
|
66
81
|
* @param {HTMLElement} element - The host element to render into
|
|
67
82
|
* @param {TemplateStringsArray} strings - Static parts of the tagged template
|
|
68
83
|
* @param {Array} values - Dynamic interpolated values
|
|
69
84
|
*/
|
|
70
|
-
function
|
|
85
|
+
function morph(element, strings, values) {
|
|
71
86
|
let entry = stringsCache.get(strings);
|
|
72
87
|
|
|
73
88
|
if (!entry) {
|
|
@@ -82,7 +97,7 @@ function fullRender(element, strings, values) {
|
|
|
82
97
|
if (entry._template) {
|
|
83
98
|
element._templateParts = cloneAndPatch(element, entry._template, values);
|
|
84
99
|
} else {
|
|
85
|
-
// Fallback for
|
|
100
|
+
// Fallback for static templates or templates where marker detection failed.
|
|
86
101
|
// White space collapsing here protects against Vue SSR mismatches.
|
|
87
102
|
const renderedValues = values.map(resolveValue);
|
|
88
103
|
const markup = entry._strings
|
|
@@ -102,45 +117,66 @@ function fullRender(element, strings, values) {
|
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
/**
|
|
105
|
-
*
|
|
120
|
+
* Create a <template> element with comment markers and string placeholders.
|
|
106
121
|
*
|
|
107
122
|
* @param {string[]} _strings - Whitespace-collapsed static parts
|
|
108
123
|
* @param {number} valueCount - Number of dynamic values
|
|
109
|
-
* @returns {HTMLTemplateElement | null}
|
|
124
|
+
* @returns {{ _tpl: HTMLTemplateElement, _attrs: (string|null)[] } | null}
|
|
110
125
|
*/
|
|
111
126
|
function createTemplate(_strings, valueCount) {
|
|
112
127
|
const marker = `<!--${markerKey}-->`;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const attrs = [];
|
|
129
|
+
let markup = "";
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < _strings.length; i++) {
|
|
132
|
+
markup += _strings[i];
|
|
133
|
+
|
|
134
|
+
if (i < valueCount) {
|
|
135
|
+
const match = _strings[i].match(/([^\s"'>/=]+)\s*=\s*["']$/);
|
|
136
|
+
|
|
137
|
+
if (match) {
|
|
138
|
+
attrs.push(match[1]);
|
|
139
|
+
markup += markerKey + "_" + i;
|
|
140
|
+
} else {
|
|
141
|
+
attrs.push(null);
|
|
142
|
+
markup += marker;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
116
146
|
|
|
117
147
|
const template = newTemplate();
|
|
118
|
-
template.innerHTML = markup;
|
|
148
|
+
template.innerHTML = markup.trim();
|
|
119
149
|
|
|
120
150
|
// Mismatch means this template shape cannot use the clone path.
|
|
121
151
|
const walker = treeWalker(template.content);
|
|
122
|
-
let
|
|
152
|
+
let commentCount = 0;
|
|
123
153
|
|
|
124
154
|
while (walker.nextNode()) {
|
|
125
155
|
if (walker.currentNode.data === markerKey) {
|
|
126
|
-
|
|
156
|
+
commentCount++;
|
|
127
157
|
}
|
|
128
158
|
}
|
|
129
159
|
|
|
130
|
-
|
|
160
|
+
const expectedComments = attrs.filter(n => n === null).length;
|
|
161
|
+
|
|
162
|
+
if (commentCount !== expectedComments) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { _tpl: template, _attrs: attrs };
|
|
131
167
|
}
|
|
132
168
|
|
|
133
169
|
/**
|
|
134
|
-
* Clone a cached template and replace
|
|
135
|
-
* with actual content.
|
|
170
|
+
* Clone a cached template and replace markers with actual content.
|
|
136
171
|
*
|
|
137
172
|
* @param {HTMLElement} element - The host element to render into
|
|
138
|
-
* @param {
|
|
173
|
+
* @param {{ _tpl: HTMLTemplateElement, _attrs: (string|null)[] }} templateInfo
|
|
139
174
|
* @param {Array} values - Raw interpolated values
|
|
140
|
-
* @returns {Array<Text |
|
|
175
|
+
* @returns {Array<Text | [Element, string] | undefined> | null}
|
|
141
176
|
*/
|
|
142
|
-
function cloneAndPatch(element,
|
|
143
|
-
const
|
|
177
|
+
function cloneAndPatch(element, templateInfo, values) {
|
|
178
|
+
const { _tpl, _attrs } = templateInfo;
|
|
179
|
+
const clone = _tpl.content.cloneNode(true);
|
|
144
180
|
const walker = treeWalker(clone);
|
|
145
181
|
const parts = Array(values.length);
|
|
146
182
|
const markers = [];
|
|
@@ -153,24 +189,46 @@ function cloneAndPatch(element, template, values) {
|
|
|
153
189
|
}
|
|
154
190
|
}
|
|
155
191
|
|
|
156
|
-
|
|
157
|
-
const value = values[i];
|
|
192
|
+
let contentIdx = 0;
|
|
158
193
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
for (let i = 0; i < values.length; i++) {
|
|
195
|
+
const attr = _attrs[i];
|
|
196
|
+
|
|
197
|
+
if (attr) {
|
|
198
|
+
// Find the element with the placeholder value
|
|
199
|
+
const placeholder = markerKey + "_" + i;
|
|
200
|
+
const el = clone.querySelector(`[${attr}="${placeholder}"]`);
|
|
201
|
+
|
|
202
|
+
if (el) {
|
|
203
|
+
const value = values[i];
|
|
204
|
+
const str = String((isArray(value) ? toPlainText(value) : value) ?? "");
|
|
205
|
+
el.setAttribute(attr, str);
|
|
206
|
+
parts[i] = [el, attr];
|
|
207
|
+
}
|
|
166
208
|
} else {
|
|
167
|
-
//
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
209
|
+
// Replace comment marker with value
|
|
210
|
+
const marker = markers[contentIdx++];
|
|
211
|
+
const value = values[i];
|
|
212
|
+
|
|
213
|
+
// Parse and insert raw HTML as a fragment
|
|
214
|
+
if (isRaw(value) && value !== nothing) {
|
|
215
|
+
const tmp = newTemplate();
|
|
216
|
+
tmp.innerHTML = resolveValue(value);
|
|
217
|
+
marker.parentNode.replaceChild(tmp.content, marker);
|
|
218
|
+
|
|
219
|
+
// Create text node with unescaped content
|
|
220
|
+
} else {
|
|
221
|
+
const textNode = document.createTextNode(toPlainText(value));
|
|
222
|
+
marker.parentNode.replaceChild(textNode, marker);
|
|
223
|
+
parts[i] = textNode;
|
|
224
|
+
}
|
|
171
225
|
}
|
|
172
226
|
}
|
|
173
227
|
|
|
228
|
+
if (element._templateStrings) {
|
|
229
|
+
morphContent(element, clone.childNodes);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
174
232
|
element.replaceChildren(clone);
|
|
175
233
|
return parts;
|
|
176
234
|
}
|
|
@@ -212,7 +270,7 @@ function morphContent(parent, nextNodes) {
|
|
|
212
270
|
}
|
|
213
271
|
|
|
214
272
|
/**
|
|
215
|
-
*
|
|
273
|
+
* Morph element’s attributes without rebuilding the DOM.
|
|
216
274
|
*
|
|
217
275
|
* @param {Element} current - The current existing DOM element
|
|
218
276
|
* @param {Element} next - The desired element from the new render
|
package/src/common/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const prefix = "░█ [ELENA]: ";
|
|
2
2
|
const isArray = Array.isArray;
|
|
3
|
+
const RAW = Symbol("elena.raw");
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @param {string} msg
|
|
@@ -14,9 +15,9 @@ export { prefix, isArray };
|
|
|
14
15
|
* @param {string} tagName
|
|
15
16
|
* @param {Function} Element
|
|
16
17
|
*/
|
|
17
|
-
export function defineElement(tagName, Element) {
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
export function defineElement(tagName, Element, registry) {
|
|
19
|
+
const reg = registry ?? globalThis.customElements;
|
|
20
|
+
reg?.get(tagName) || reg?.define(tagName, Element);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -52,51 +53,59 @@ export function resolveValue(value) {
|
|
|
52
53
|
* @returns {string}
|
|
53
54
|
*/
|
|
54
55
|
function resolveItem(value) {
|
|
55
|
-
return value?.
|
|
56
|
+
return value?.[RAW] ? String(value) : escapeHtml(value ?? "");
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Lightweight template result.
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
class HtmlResult {
|
|
65
|
+
constructor(strings, values) {
|
|
66
|
+
this.strings = strings;
|
|
67
|
+
this.values = values;
|
|
68
|
+
}
|
|
69
|
+
toString() {
|
|
70
|
+
if (this._str == null) {
|
|
71
|
+
this._str = this.strings.reduce((acc, s, i) => {
|
|
72
|
+
return acc + s + resolveValue(this.values[i]);
|
|
73
|
+
}, "");
|
|
74
|
+
}
|
|
75
|
+
return this._str;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
HtmlResult.prototype[RAW] = true;
|
|
79
|
+
|
|
58
80
|
/**
|
|
59
81
|
* Tagged template for trusted HTML. Use as the return value
|
|
60
82
|
* of render(), or for sub-fragments inside render methods.
|
|
61
83
|
*
|
|
62
84
|
* @param {TemplateStringsArray} strings
|
|
63
85
|
* @param {...*} values
|
|
64
|
-
* @returns {{
|
|
86
|
+
* @returns {{ strings: TemplateStringsArray, values: Array, toString(): string }}
|
|
65
87
|
*/
|
|
66
88
|
export function html(strings, ...values) {
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
__raw: true,
|
|
70
|
-
strings,
|
|
71
|
-
values,
|
|
72
|
-
toString: () => {
|
|
73
|
-
if (str == null) {
|
|
74
|
-
str = strings.reduce((acc, s, i) => {
|
|
75
|
-
return acc + s + resolveValue(values[i]);
|
|
76
|
-
}, "");
|
|
77
|
-
}
|
|
78
|
-
return str;
|
|
79
|
-
},
|
|
80
|
-
};
|
|
89
|
+
return new HtmlResult(strings, values);
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
/**
|
|
84
93
|
* Renders a string as HTML rather than text.
|
|
85
94
|
*
|
|
86
95
|
* @param {string} str - The raw HTML string to trust.
|
|
87
|
-
* @returns {{
|
|
96
|
+
* @returns {{ toString(): string }}
|
|
88
97
|
*/
|
|
89
98
|
export function unsafeHTML(str) {
|
|
90
|
-
return {
|
|
99
|
+
return { [RAW]: true, toString: () => str ?? "" };
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
/**
|
|
94
103
|
* A placeholder you can return from a conditional expression
|
|
95
104
|
* inside a template to render nothing.
|
|
96
105
|
*
|
|
97
|
-
* @type {{
|
|
106
|
+
* @type {{ toString(): string }}
|
|
98
107
|
*/
|
|
99
|
-
export const nothing = {
|
|
108
|
+
export const nothing = { [RAW]: true, toString: () => "" };
|
|
100
109
|
|
|
101
110
|
/**
|
|
102
111
|
* Check if a value contains trusted HTML fragments.
|
|
@@ -104,7 +113,7 @@ export const nothing = { __raw: true, toString: () => "" };
|
|
|
104
113
|
* @param {*} value
|
|
105
114
|
* @returns {boolean}
|
|
106
115
|
*/
|
|
107
|
-
export const isRaw = value => (isArray(value) ? value.some(item => item?.
|
|
116
|
+
export const isRaw = value => (isArray(value) ? value.some(item => item?.[RAW]) : !!value?.[RAW]);
|
|
108
117
|
|
|
109
118
|
/**
|
|
110
119
|
* Convert a value to its plain text string.
|
package/src/elena.js
CHANGED
|
@@ -40,7 +40,7 @@ function elementResolver(selector) {
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* @typedef {{ text: string, element: HTMLElement | null, render(): void, willUpdate(): void, firstUpdated(): void, updated(): void, connectedCallback(): void, disconnectedCallback(): void }} ElenaInstanceMembers
|
|
43
|
+
* @typedef {{ text: string, element: HTMLElement | null, updateComplete: Promise<void>, render(): void, willUpdate(): void, firstUpdated(): void, updated(): void, requestUpdate(): void, connectedCallback(): void, disconnectedCallback(): void, adoptedCallback(): void, attributeChangedCallback(prop: string, oldValue: string | null, newValue: string | null): void }} ElenaInstanceMembers
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -49,7 +49,7 @@ function elementResolver(selector) {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* @typedef {(new (...args: any[]) => HTMLElement & ElenaInstanceMembers) & {
|
|
52
|
-
* define(): void,
|
|
52
|
+
* define(registry?: CustomElementRegistry): void,
|
|
53
53
|
* readonly observedAttributes: string[],
|
|
54
54
|
* tagName?: string,
|
|
55
55
|
* props?: (string | ElenaPropObject)[],
|
|
@@ -57,6 +57,7 @@ function elementResolver(selector) {
|
|
|
57
57
|
* element?: string,
|
|
58
58
|
* shadow?: "open" | "closed",
|
|
59
59
|
* styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[],
|
|
60
|
+
* registry?: CustomElementRegistry,
|
|
60
61
|
* }} ElenaElementConstructor
|
|
61
62
|
*/
|
|
62
63
|
|
|
@@ -91,8 +92,8 @@ export function Elena(superClass) {
|
|
|
91
92
|
* Updates the matching prop and re-renders if needed.
|
|
92
93
|
*
|
|
93
94
|
* @param {string} prop
|
|
94
|
-
* @param {string} oldValue
|
|
95
|
-
* @param {string} newValue
|
|
95
|
+
* @param {string | null} oldValue
|
|
96
|
+
* @param {string | null} newValue
|
|
96
97
|
*/
|
|
97
98
|
attributeChangedCallback(prop, oldValue, newValue) {
|
|
98
99
|
super.attributeChangedCallback?.(prop, oldValue, newValue);
|
|
@@ -102,17 +103,29 @@ export function Elena(superClass) {
|
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
getProps(this, prop, oldValue, newValue);
|
|
109
|
-
this._syncing = false;
|
|
106
|
+
if (oldValue === newValue) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
if (this._hydrated && !this._isRendering) {
|
|
111
|
+
// The attribute is already set and we just need the coerced
|
|
112
|
+
// prop value stored for the next render.
|
|
113
|
+
const current = this._props.get(prop);
|
|
114
|
+
const type = typeof current;
|
|
115
|
+
const coerced =
|
|
116
|
+
type === "string" ? (newValue ?? "") : getPropValue(type, newValue, "toProp");
|
|
117
|
+
|
|
118
|
+
if (coerced !== current) {
|
|
119
|
+
this._props.set(prop, coerced);
|
|
120
|
+
}
|
|
115
121
|
this._safeRender();
|
|
122
|
+
|
|
123
|
+
// Runs pre-hydration or during render.
|
|
124
|
+
// Goes through the setter so _props is initialized correctly.
|
|
125
|
+
} else {
|
|
126
|
+
this._syncing = true;
|
|
127
|
+
getProps(this, prop, oldValue, newValue);
|
|
128
|
+
this._syncing = false;
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
|
|
@@ -141,6 +154,16 @@ export function Elena(superClass) {
|
|
|
141
154
|
this.text = this.textContent.trim();
|
|
142
155
|
}
|
|
143
156
|
this._attachShadow();
|
|
157
|
+
this._root = this._shadow ?? this.shadowRoot ?? this;
|
|
158
|
+
|
|
159
|
+
this._runUpdate ??= () => {
|
|
160
|
+
try {
|
|
161
|
+
this._performUpdate();
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(prefix, e);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
144
167
|
this.willUpdate();
|
|
145
168
|
this._applyRender();
|
|
146
169
|
this._syncProps();
|
|
@@ -228,16 +251,6 @@ export function Elena(superClass) {
|
|
|
228
251
|
this._syncing = false;
|
|
229
252
|
}
|
|
230
253
|
|
|
231
|
-
/**
|
|
232
|
-
* The root node to render into. Returns the shadow root when shadow mode
|
|
233
|
-
* is enabled, otherwise the host element itself.
|
|
234
|
-
*
|
|
235
|
-
* @type {ShadowRoot | HTMLElement}
|
|
236
|
-
*/
|
|
237
|
-
get _renderRoot() {
|
|
238
|
-
return this._shadow ?? this.shadowRoot ?? this;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
254
|
/**
|
|
242
255
|
* Attaches a shadow root and adopts styles on first connect.
|
|
243
256
|
* Only runs when `static shadow` is set on the component class.
|
|
@@ -255,7 +268,11 @@ export function Elena(superClass) {
|
|
|
255
268
|
// In that case skip attachShadow() but still adopt styles below.
|
|
256
269
|
// Store the reference so closed shadow roots remain accessible.
|
|
257
270
|
if (!this._shadow && !this.shadowRoot) {
|
|
258
|
-
|
|
271
|
+
const options = { mode: component.shadow };
|
|
272
|
+
if (component.registry) {
|
|
273
|
+
options.customElementRegistry = component.registry;
|
|
274
|
+
}
|
|
275
|
+
this._shadow = this.attachShadow(options);
|
|
259
276
|
}
|
|
260
277
|
|
|
261
278
|
const shadowRoot = this._shadow ?? this.shadowRoot;
|
|
@@ -290,14 +307,14 @@ export function Elena(superClass) {
|
|
|
290
307
|
*/
|
|
291
308
|
_applyRender() {
|
|
292
309
|
const constructor = this.constructor;
|
|
293
|
-
const root = this.
|
|
310
|
+
const root = this._root;
|
|
294
311
|
const result = this.render();
|
|
295
312
|
|
|
296
313
|
if (result && result.strings) {
|
|
297
314
|
const rebuilt = renderTemplate(root, result.strings, result.values);
|
|
298
315
|
|
|
299
316
|
// Re-resolve element ref when the DOM was fully rebuilt.
|
|
300
|
-
//
|
|
317
|
+
// patch() and morph() leave the DOM structure intact,
|
|
301
318
|
// so the existing ref is still valid.
|
|
302
319
|
if (rebuilt) {
|
|
303
320
|
const oldElement = this.element;
|
|
@@ -430,6 +447,7 @@ export function Elena(superClass) {
|
|
|
430
447
|
* events in Shadow DOM (change, submit, reset).
|
|
431
448
|
* Composed bubbling events (click, input) pass through on their own.
|
|
432
449
|
*
|
|
450
|
+
* @param {Event} event
|
|
433
451
|
* @internal
|
|
434
452
|
*/
|
|
435
453
|
handleEvent(event) {
|
|
@@ -437,7 +455,7 @@ export function Elena(superClass) {
|
|
|
437
455
|
return;
|
|
438
456
|
}
|
|
439
457
|
|
|
440
|
-
if (!event.bubbles || (!event.composed && this.
|
|
458
|
+
if (!event.bubbles || (!event.composed && this._root !== this)) {
|
|
441
459
|
/** @internal */
|
|
442
460
|
this.dispatchEvent(new Event(event.type, { bubbles: event.bubbles }));
|
|
443
461
|
}
|
|
@@ -467,11 +485,14 @@ export function Elena(superClass) {
|
|
|
467
485
|
* Registers the component as a custom element using `static tagName`.
|
|
468
486
|
* Call this on your component class after the class body is defined,
|
|
469
487
|
* not on the Elena mixin itself.
|
|
488
|
+
*
|
|
489
|
+
* @param {CustomElementRegistry} [registry] - A scoped registry to register in.
|
|
490
|
+
* When omitted, registers in the global `customElements` registry.
|
|
470
491
|
*/
|
|
471
|
-
static define() {
|
|
492
|
+
static define(registry) {
|
|
472
493
|
const tag = this.tagName;
|
|
473
494
|
if (tag) {
|
|
474
|
-
defineElement(tag, this);
|
|
495
|
+
defineElement(tag, this, registry);
|
|
475
496
|
} else {
|
|
476
497
|
warn("define() without a tagName.");
|
|
477
498
|
}
|
|
@@ -489,16 +510,7 @@ export function Elena(superClass) {
|
|
|
489
510
|
}
|
|
490
511
|
if (!this._renderPending) {
|
|
491
512
|
this._renderPending = true;
|
|
492
|
-
this.
|
|
493
|
-
this._resolveUpdate = resolve;
|
|
494
|
-
});
|
|
495
|
-
queueMicrotask(() => {
|
|
496
|
-
try {
|
|
497
|
-
this._performUpdate();
|
|
498
|
-
} catch (e) {
|
|
499
|
-
console.error(prefix, e);
|
|
500
|
-
}
|
|
501
|
-
});
|
|
513
|
+
queueMicrotask(this._runUpdate);
|
|
502
514
|
}
|
|
503
515
|
}
|
|
504
516
|
|
|
@@ -523,7 +535,7 @@ export function Elena(superClass) {
|
|
|
523
535
|
this.updated();
|
|
524
536
|
} finally {
|
|
525
537
|
this._updateComplete = null;
|
|
526
|
-
resolve();
|
|
538
|
+
resolve?.();
|
|
527
539
|
}
|
|
528
540
|
}
|
|
529
541
|
|
|
@@ -534,7 +546,15 @@ export function Elena(superClass) {
|
|
|
534
546
|
* @type {Promise<void>}
|
|
535
547
|
*/
|
|
536
548
|
get updateComplete() {
|
|
537
|
-
|
|
549
|
+
if (!this._renderPending) {
|
|
550
|
+
return Promise.resolve();
|
|
551
|
+
}
|
|
552
|
+
if (!this._updateComplete) {
|
|
553
|
+
this._updateComplete = new Promise(resolve => {
|
|
554
|
+
this._resolveUpdate = resolve;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
return this._updateComplete;
|
|
538
558
|
}
|
|
539
559
|
|
|
540
560
|
/**
|