Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Simple, and portable webcomponent on the go
Note: <0.5.2 is unstable. This version is the most stable
Elemxx
A simple, portable webcomponent on the go. Heavily inspired by Lit
It dosen't uses shadow dom, directly exposing HTMLElement class, and some reactive objects!
The difference? (comparing to Lit)
- Since we dosen't use shadow dom, the
static Elemxx.cssobject has:medirective
Or, you can even use your own styling processor, and replace your own directive with
Elemxx.localName!
- We use 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 Elemxx ... // 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 class
Consider looking the
Trackinterface type in ./index.ts
-
Your element is ok! Dosen't really isolated from HTMLElement bindings
-
Webcomponent replacement functions and objects! (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
Usage
Browser
You can use the raw version of ./index.js by downloading it (don't forget to obtain the ./LICENSE file 😉), or use the Github CDN https://raw.githubusercontent.com/teamdunno/elemxx/refs/heads/main/index.js
Also, no
d.ts. since we use jsr:@deno/emit to transpileindex.ts
Installation for Nodejs
For npm
$ npx jsr add @dunno/elemxx
For Yarn
Note: You need to upgrade to v4+, because when installing Yarn, the distribution for Linux only sticks to v1 (thats why the
dlxcommand dosent found)$ yarn upgrade
$ yarn dlx jsr add @dunno/elemxx
For pnpm
$ pnpm dlx jsr add @dunno/elemxx
Installation for non-Nodejs
For Bun
$ bunx jsr add @dunno/elemxx
For Deno
$ deno add jsr:@dunno/elemxx
Examples
Javascript examples
Typing animation
// replace /path/to/elemxx/index.js with the imported elemxx module import { Elemxx } from "/path/to/elemxx/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 Elemxx { // this would make a new Track object called `this.attrs.sentence` static attrList = ["sentence"] // track the text sentence = "" _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 elemxx 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>
Typescript examples
Simple text (with decorators)
Quote from the founder
Quote from vintheweirdass (founder of this package)
"For decorators: idk for nodejs users. Since i use Deno, it was enabled automatically. If nodejs users wants to volunteer on this, sure :)"
import { Elemxx } from "@dunno/elemxx"; import { css, define } from "@dunno/elemxx/decorators"; @define("simple-hello") @css(` :me { background-color:#FFEBCD; padding: 10px; border-radius: 10px; font-size: larger; } `) export class SimpleHello extends Elemxx { 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) } }
Add Package
deno add jsr:@dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";
Import directly with a jsr specifier
import * as elemxx from "jsr:@dunno/elemxx";
Add Package
pnpm i jsr:@dunno/elemxx
pnpm dlx jsr add @dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";
Add Package
yarn add jsr:@dunno/elemxx
yarn dlx jsr add @dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";
Add Package
vlt install jsr:@dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";
Add Package
npx jsr add @dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";
Add Package
bunx jsr add @dunno/elemxx
Import symbol
import * as elemxx from "@dunno/elemxx";