A rebooted version from elemxx. Licensed under MIT, and published to both npm and JSR.
portable webcomponent on the go, heavily inspired by Lit. Eel doesn't use shadow dom, directly exposing HTMLElement class
...and some reactive objects!
The difference? (comparing to Lit)
- Since eel dosen't use shadow dom, the
static Eel.cssobject has:medirective for CSS styling
Or, you could even use your own styling processor, and replace your own directive with
Eel.localName!
- Eel uses event-based object (without using EventEmitter) to make it reactive. Like; getter and setters, and simple array of listening event functions. We do have cleanup for those events
Example
// define & extend your class from Eel
...
// add the Track object here
areYouOk = this.track("yes, i am!")
...
// so, whenever you want to print to console
// everytime it was changed, you can do this
this.areYouOk.watch((v)=>{console.log(v)})
// alternatively, you can access the value directly
console.log(this.areYouOk.value)
// now, you can change it like this.
// It would trigger the events that we put earlier
this.areYouOk.value = "no, im not 😭"
...
// close the classConsider looking the
Trackinterface type in ./index.ts
-
Your element is ok! Dosen't really isolated from HTMLElement bindings
-
These are your replacements from default Webcomponent function! (no need for
super[function here]())- Instead of
connectedCallback, you can useonMount - Instead of
disconnectedCallback, you can useonUnmount - Instead of
(string[]) observedAttributes, you can useattrList - Instead of
attributeChangedCallback, you can use({[key:string]:Track<string|null>}) attrsand listen usingattrs["<attribute name>"].watch((value)=>{}) - Instead of
(boolean) isConnected, you can use(boolean) mounted. It would be changed during mount changes
- Instead of
This package are universal! Supports JSR (for deno and bun), npm, and even browser!
Just run this for Node, nothing else and ready to be used!
npm install @linusified/eelIf your Deno/Bun project was using some of the nodejs frameworks like vite, svelte, etc, you need to use these commands below.
# for deno
deno install npm:@linusified/eel
# for bun
bun add @linusified/eel(If you're using Node, skip this step and go to the (Setup npm)[###setup-npm] section instead)
Open https://jsr.io/@linusified/isjsruntime, then see on the right pane and pick on your environment. It will show the commands, so you can run and install it asap!
JSR also supports pnpm, yarn, deno, and even bun!
Since this package are using ESM, download the index.js file and use it straight away!
Don't forget to set your <script> type as a "module" when using it
(UMD isn't supported for now)
For example:
- On script.js
import * as js from "/path/to/eel/index.js";- On your index.html
...
<script src="/path/to/script.js" type="module"></script>
...Details
// replace /path/to/eel/index.js with the imported eel module
import { Eel } from "/path/to/eel/index.js";
// define simple sleep function, instead of nesting in setTimeout
const sleep = (ms)=>new Promise((res, _)=>setTimeout(res, ms))
// make the new typing elem
class TypingAnim extends Eel {
// this would listen to the arguments from the Eel element, then placed it under `this.attrs.sentence`
static attrList = ["sentence"]
// track the text
sentence = ""
// this is a reactive variable. Used the same parts as `static attrList` but its purpose to just do it locally. Also it can be placed whenever you like
_text = this.track("")
// this will stop the animation if set otherwise
_anim = this.track(true)
// the css style
static css = `
@keyframes blinkTextCursor {
from{color: black;}
to{color: transparent;}
}
// the :me is a special directive to reference itself
// since eel dosent use shadow dom
:me {
display:flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
:me > h2 {
text-shadow: gray 0px 1px 1px;
}
:me > h2 > span.cursor {
animation: blinkTextCursor 0.8s steps(44) infinite normal;
}
`;
constructor() {
super();
// since `this.attrs` dosent cleaned when unmounted, we put them in here
// so it dosent re-trigger on mount changes
// by the way, this only listen. Dosent trigger once
this.attrs.sentence.watch((value)=>{
// if elem isnt mounted, return
if (!this.mounted) return
// we need to require users to use the `sentence` attribute
if (value===null) throw new TypeError("attribute `sentence` is required")
// set to our own sentence object
this.sentence = value
})
}
render() {
// create new header text
const h2 = document.createElement("h2")
// create new span (for text)
const animText = document.createElement("span")
// add to header
h2.appendChild(animText)
// create new span (for cursor)
const cursor = document.createElement("span")
// add cursor class so the CSS pick up
cursor.classList.add("cursor")
cursor.innerText = "|"
// add to header
h2.appendChild(cursor)
// set innerText once, and listen to set innerText again
// (just like doWhile without looping)
this._text.observe((text) => {
animText.innerText = text
})
const button = document.createElement("button")
button.addEventListener("click", () => this._toggle())
// set the button text and trigger animation if true, and listen to set that again
// (just like doWhile without looping)
this._anim.observe((v) => {
button.innerText = v ? "Stop animation" : "Play animation"
if (v) this._triggerAnim()
})
// add header to :me
this.appendChild(h2)
// add button to :me
this.appendChild(button)
}
// if mounted, render the element
onMount () {
this.render()
}
// if unmounted, stop the animation
onUnmount () {
this._anim.value = false
}
// trigger animation
async _triggerAnim() {
while (this._anim.value) {
const sentence = this.sentence
await sleep(2000)
for (let i = 0; i <= sentence.length; i++) {
this._text.value = sentence.substring(0, i)
await sleep(150)
if (!this._anim.value) return
}
await sleep(3000)
if (!this._anim) return
for (let i = sentence.length; i >= 0; i--) {
this._text.value = sentence.substring(0, i)
await sleep(50)
if (!this._anim.value) return
}
await sleep(500)
if (!this._anim.value) return
}
}
// toggle the animation to play/stop
_toggle() {
this._anim.value = !this._anim.value
}
};
customElements.define("typing-anim", TypingAnim)HTML
<html>
<head>
<!-- add the script -->
<script type="module" src="/path/to/script.js"></script>
</head>
<body>
<!-- add our custom elem -->
<typing-anim sentence="Hello World"></typing-anim>
</body>
</html>Details
import { Eel } from "@linusified/eel";
import { css, define } from "@linusified/eel/decorators";
@define("simple-hello")
@css(`
:me {
background-color:#FFEBCD;
padding: 10px;
border-radius: 10px;
font-size: larger;
}
`)
export class SimpleHello extends Eel {
override onMount (){
// create new span
const span = document.createElement("span")
// add text to span
span.innerText = "hello everyone!"
// add span to :me
this.appendChild(span)
}
}