A Reactive frontend library.
yarn: yarn add uele
npm: npm i uele
module: https://unpkg.com/uele?module
cdn: https://unpkg.com/uele/lite
module: https://unpkg.com/uele/lite?module
import { h, r, is, get, api, add, props, useLive } from "uele/lite";
// @preact/signals, ulive, usignal api config
api.effect = effect;
api.is = (v) => v.peek;
api.get = (v) => v.value;- Tiny
- Simple API
- Fast
- JSX
- Fragment
- Components
- SVG and MATH ML
- Refs
- Style Maps
- Lazy Components
- Promise
- AsyncIterable
- Control Flow Components - If, For, Show, Switch, Match
- Extend with any reactive library using api - effect, is, get
- Efficient Array Diffing with For and map
- Add Any Diffing - Using api.diff
- Automatic Cleanup for Subscriptions
- Extend html attributes and props
import { h, useLive } from "uele";
let [count, setCount] = useLive(0);
document.body.append(
h("main", [
h("button", { onclick: () => setCount((c) => c - 1) }, "-"),
count,
h("button", { onclick: () => setCount((c) => c + 1) }, "+"),
])
);import { h, useLive } from "uele";
import htm from "htm";
const html = htm.bind(h);
let [count, setCount] = useLive(0);
document.body.append(
html`
<main>
<button onClick=${() => setCount((c) => c - 1)}>-</button>
${count}
<button onClick=${() => setCount((c) => c + 1)}>+</button>
</main>
`
);import { h, useLive, f } from "uele";
const { button, main } = f(h);
let [count, setCount] = useLive(0);
document.body.append(
main([
button({ onclick: () => setCount((c) => c - 1) }, "-"),
count,
button({ onclick: () => setCount((c) => c + 1) }, "+"),
])
);import { h, useLive } from "uele";
let [count, setCount] = useLive(0);
document.body.append(
<main>
<button onClick={() => setCount((c) => c - 1)}>-</button>
{count}
<button onClick={() => setCount((c) => c + 1)}>+</button>
</main>
);let frag = (
<>
{asyncIterable} or {promise} or {any html node or component} or {any reactive signal or library}
</>
);Load a component lazily with an optional fallback
const LazyComp = lazy(() => import("./SomeComp"), <div>Failed</div>);Efficient diffing of an array of items
import { map } from "uele";
let items = o([1, 2, 3]);
const Items = () => {
return map(
items,
(item, i) => (
<div>
{item} {i}
</div>
),
<div>No items</div>
);
};import {
h,
lazy,
api,
If,
For,
map,
get,
r,
useLive,
props,
} from "uele";
import { o, effect, memo } from "ulive/fn"; // Or any other reactive library
// ulive settings
api.effect = effect;
api.is = (v) => v?.peek;
api.get = (v) => v();
// Check below for other reactive library settings
const LazyComp = lazy(() => import("./lazy"));
// Async Component
const TodoItem = async ({ id }) => {
let api = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
let todo = await api.json();
return (
<li>
<span>{todo.title}</span>
<input type="checkbox" checked={todo.completed} />
</li>
);
};
const count = o(0);
const inc = () => count(count() + 1);
const dec = () => count(count() - 1);
const Counter = () => {
let square = memo(() => count() * count());
let cube = memo(() => square() * count());
effect(() => console.log(count(), square(), cube()));
return (
<div>
<div>
Count: {count} {square} {cube}
</div>
<button onclick={inc}>+</button>
<button onclick={dec}>-</button>
</div>
);
};
// AsyncIterator
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 200)
return new Promise((ok) =>
setTimeout(
() =>
ok({
value: <div>{this.i++}</div>,
done: false,
}),
10
)
);
return new Promise((ok) => ok({ done: true }));
},
};
},
};
let items = o([1, 2, 3]);
const App = () => (
<main>
<Counter />
<LazyComp />
<TodoItem />
{asyncIterable}
{map(items, (item, i) => (
<div>
{item} {i}
</div>
))}
<svg>...</svg>
</main>
);
document.body.append(<App />);A utility function to handle and unwrap values of signals, observables, etc., especially functions.
import { get } from "uele";
let value = get(someReactiveSignal);The r function handles reactive subscriptions, providing a callback when the value changes.
import { r } from "uele";
r(someObservable, (value, is) => {
console.log("Value changed to", value, is);
});The f function is a utility that provides a convenient shorthand for creating elements using the h function. It returns a proxy that allows you to call HTML elements as functions directly.
import { h, useLive, f } from "uele";
const { button, main } = f(h);
const [count, setCount] = useLive(0);
document.body.append(
main([
button({ onclick: () => setCount((c) => c - 1) }, "-"),
count,
button({ onclick: () => setCount((c) => c + 1) }, "+"),
])
);useLive helps manage dynamic parts of the DOM by tracking specific start and end markers.
import { useLive } from "uele";
let [live, setLive] = useLive("....");
let c = 0;
setInterval(() => {
setLive(c++);
}, 1000);
const App = () => <main>{live}</main>;The props object allows setting custom properties and event handlers on elements.
import { props } from "uele";
props.set("autofocus", (tag, value, name, attrs) => {
setTimeout(() => tag.focus(), 0);
});
props.set("validate", (node, validateFn) => {
node.addEventListener("input", () => {
const error = validateFn(node.value);
if (error) {
console.log("error", error);
} else {
console.log("no err");
}
});
});
props.set("tooltip", (node, value) => {
const tooltip = <div class="tooltip">{value}</div>;
node.addEventListener("mouseenter", () => {
document.body.appendChild(tooltip);
const { left, top } = node.getBoundingClientRect();
tooltip.style.position = "absolute";
tooltip.style.left = `${left}px`;
tooltip.style.top = `${top - 20}px`;
});
node.addEventListener("mouseleave", () => {
tooltip.remove();
});
});
props.set("unmount", (node, cleanupFn) => {
// Check if the node has a parent node
const setupObserver = () => {
if (node.parentNode) {
// Create a MutationObserver to watch for DOM changes (removals)
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
mutation.removedNodes.forEach((removedNode) => {
if (removedNode === node) {
// Node has been removed, trigger the cleanup function
cleanupFn();
observer.disconnect(); // Stop observing
}
});
}
});
// Start observing the parent node for childList changes (node removals)
observer.observe(node.parentNode, { childList: true });
} else {
// If node has no parentNode, retry after DOM insertion
requestAnimationFrame(setupObserver); // Try again on the next frame
}
};
setupObserver(); // Initialize the observer
});
<input autofocus/>
<input validate={(val) => (val.length < 3 ? "Too short" : "")} />
<button tooltip={{<div>click me!</div>}}>Hover me</button>
<div ref={(v) => { setTimeout(() => v.remove(), 2000)}}
unmount={() => console.log("unmounted!")}>
Remove me
</div>Control flow components accept boolean or reactive values for conditions.
import { If, Show } from 'uele';
<If when={cond} fallback={<div>False</div>}>
<div>True</div>
</If>
<Show when={cond} fallback={<div>False</div>}>
<div>True</div>
</Show>import { For } from "uele";
<For each={[1, 2, 3]} fallback={<div>No Items</div>}>
{(val) => <div>{val}</div>}
</For>;import { Switch, Match } from "uele";
<Switch fallback={<div>Default case</div>}>
<Match when={condition1}>
<div>Case 1</div>
</Match>
<Match when={condition2}>
<div>Case 2</div>
</Match>
</Switch>;Subscriptions and side-effects in UEle are automatically cleaned up when elements are garbage collected using FinalizationRegistry. You don't need to manually clean up.
UEle can be configured to work with any reactive library by setting the api object accordingly.
Refer more at usub
// preact/signals-core or usignal settings
api.effect = effect;
api.is = (v) => v instanceof Signal; // or preact signals
api.get = (v) => v?.value;
// oby or sinuous settings
api.effect = effect; // or api.effect = subscribe
api.is = (v) => isObservable(v); // or api.is = (v) => v?.$o;
api.get = (v) => v?.();
// solid-js settings
api.effect = createEffect;
api.is = (v) => v?.name?.includes("readSignal");
api.get = (v) => v?.();Set the api object to match the library's functions:
import { api } from 'uele';
api.effect = ...; // Function to create an effect
api.is = (v) => ...; // Function to check if a value is reactive
api.get = (v) => ...; // Function to get the current value
api.cleanup = ...; // Explicit cleanup function for solid.js, sinuous and similar
api.any = ...; // Go crazy with anything.
api.diff = ...; // Any diffing library
api.udom = ...; //udomdiff get methodMIT