tagu-tagu
v4.4.1
Published
A lightweight helper for vanilla `HTMLElement`, with reactivity.
Maintainers
Readme
tagu-tagu
A lightweight helper for vanilla HTMLElement, with reactivity. No config, no jsx — only clean javascript.
tagu-tagu is
just a helper for HTMLElement:
import {button} from "https://cdn.jsdelivr.net/npm/[email protected]/dist/bundle.min.js";
const myButton = button("Hello!");// `HTMLButtonElement`
document.body.appendChild(myButton);with reactivity!
import {button, span, div, useState} from "https://cdn.jsdelivr.net/npm/[email protected]/dist/bundle.min.js";
function CounterExample(){
const count = useState(4);
function decrementCount() {
count.set(count.get() - 1);
}
function incrementCount() {
count.set(count.get() + 1);
}
return div([
button("-", { on: { click: decrementCount } }),// `HTMLButtonElement`
span(count),// `HTMLSpanElement`
button("+", { on: { click: incrementCount } }),// `HTMLButtonElement`
])
}
document.body.appendChild(CounterExample());No need to compile. But typescript is supported.
Examples
Initializers
Elements are initialized by rest parameters. Arguments can be any order.
button("Hello!", {css: {background: "blue"}});
button({css: {background: "blue"}}, "Hello!");text initializer
import { div } from "tagu-tagu";
function SimpleTextExample() {
return div("Hello");
}
document.body.appendChild(SimpleTextExample());or
import { div } from "tagu-tagu";
// Element: textContent
function TextExample() {
return div({ text: "Hello" });
}
document.body.appendChild(TextExample());Children initializer
import { button, div, h1 } from "tagu-tagu";
// Element: append
function ChildrenExample() {
return div(["Hello", button("World!"), "HELLO", h1("WORLD!")]);
}
document.body.appendChild(ChildrenExample());
html initializer
import { div } from "tagu-tagu";
// Element: innerHTML
function HtmlExample() {
return div({ html: `<button>Hello World!</button>` });
}
document.body.appendChild(HtmlExample());
css initializer
import { button } from "tagu-tagu";
// element.style.setProperty
function CssExample() {
return button({
css: { width: "300px", height: "300px" },
});
}
document.body.appendChild(CssExample());attr initializer
import { input } from "tagu-tagu";
// Element: setAttribute
function AttrExample() {
return input({
attr: { type: "color" },
});
}
document.body.appendChild(AttrExample());prop initializer
import { option, select } from "tagu-tagu";
// Javascript properties
function PropExample() {
return select([option("One"), option("Two"), option("Three")], {
prop: { selectedIndex: 1 },
});
}
document.body.appendChild(PropExample());on initializer
import { button } from "tagu-tagu";
// Element: addEventListener
function OnExample() {
return button("Click Me", { on: { click: () => alert("Hello!") } });
}
document.body.appendChild(OnExample());data initializer
import { div, waitForData } from "tagu-tagu";
function DataExample() {
return div({ data: { "my-data-key": "Hello World!" } });
}
const element = DataExample();
console.log(await waitForData(element, "my-data-key")); // Hello World!import { div, Modify, waitForData } from "tagu-tagu";
/**
* Node can get data from ancestors.
* Node can't get data from descendants.
* */
function DataFromParentExample() {
const parent = div();
const element = div({ data: { "my-data-key": "Hello World!" } });
const child = div();
waitForData(parent, "my-data-key").then((data) => {
parent.textContent = `Parent: ${data}`; // never
});
waitForData(child, "my-data-key").then((data) => {
child.textContent = `Child: ${data}`; // displayed
});
return Modify(parent, [Modify(element, [child])]);
}
document.body.appendChild(DataFromParentExample());Modify existing element
You can use initializers for existing element.
import { Modify } from "tagu-tagu";
Modify(document.body, {
text: "💣",
css: {
background: "skyblue",
},
on: {
click: () => {
document.body.textContent = "💥";
},
},
});
$ initializer
<form>
<div>Name: <input id="name"></div>
<div>Age: <input id="age"></div>
<button id="submit">Submit</button>
</form>
<script type="module" src="index.ts"></script>import { Modify, style } from "tagu-tagu";
// Element: querySelector
function $Example() {
Modify("form", {
$: {
"#name": { prop: { defaultValue: "Einstein" } },
"#age": { attr: { type: "number" }, prop: { defaultValue: 26 } },
"#submit": [
style({
"#submit": {
background: "blue",
color: "white",
border: "none",
"border-radius": "10px",
},
"#submit:hover": {
background: "skyblue",
},
}),
],
},
});
}
$Example();$$ initializer
<meta charset="utf-8">
<div>
<h1>Unfertilized Eggs</h1>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
</div>
<div id="fertilized">
<h1>Click!</h1>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
<button>🥚</button>
</div>
<script type="module" src="index.ts"></script>import { Modify } from "tagu-tagu";
// Element: querySelectorAll
function $$Example() {
Modify("#fertilized", {
$$: {
button: {
on: {
click: (e) => {
(e.target as HTMLButtonElement).textContent = "🐣";
},
},
},
},
});
}
$$Example();or you can use ModifyAll
import { ModifyAll } from "tagu-tagu";
function ModifyAllExample() {
ModifyAll("#fertilized button", {
on: {
click: (e) => {
(e.target as HTMLButtonElement).textContent = "🐣";
},
},
});
}
ModifyAllExample();Callback initializer
import { button, div } from "tagu-tagu";
function InitializerCallbackExample() {
return div([
div([
div([
button("Deep", (button) =>
console.log("debug:", button, "is created!"),
),
]),
]),
]);
}
document.body.appendChild(InitializerCallbackExample());Animation
Signal
useState
import { button, div, useState } from "tagu-tagu";
function SimpleStateExample() {
const count = useState(0);
function incrementCount() {
count.set(count.get() + 1);
}
return div([div(count), button("+", { on: { click: incrementCount } })]);
}
document.body.appendChild(SimpleStateExample());useComputed
import { button, div, useState, useComputed } from "tagu-tagu";
function ComputedExample() {
const count = useState(0);
function incrementCount() {
count.set(count.get() + 1);
}
return div([
div(useComputed(() => (count.get() ? count.get() : "Zero"))),
button("+", { on: { click: incrementCount } }),
]);
}
document.body.appendChild(ComputedExample());import { button, div, useState, useComputed } from "tagu-tagu";
function TwoComputedSignalsExample() {
const count = useState(0);
function incrementCount() {
count.set(count.get() + 1);
}
return div([
div(
useComputed(() => (count.get() ? count.get() : "Zero")),
{
css: {
color: useComputed(() => (count.get() % 2 === 0 ? "blue" : "tan")),
},
},
),
button("+", { on: { click: incrementCount } }),
]);
}
document.body.appendChild(TwoComputedSignalsExample());useEffect
import { button, div, useEffect, useState } from "tagu-tagu";
function EffectExample() {
const count = useState(0);
function incrementCount() {
count.set(count.get() + 1);
}
useEffect(() => {
console.log("count:", count.get());
});
return div([
div("See console"),
button("+", { on: { click: incrementCount } }),
]);
}
document.body.appendChild(EffectExample());Cleanup in useEffect:
import { button, div, useEffect, useState } from "tagu-tagu";
function EffectWithCleanupExample() {
const count = useState(0);
function incrementCount() {
count.set(count.get() + 1);
}
useEffect((effect) => {
const countValue = count.get();
effect.onCleanup(() => {
console.log("Cleanup:", countValue);
});
console.log("Count:", countValue);
});
return div([
div("See console"),
button("+", { on: { click: incrementCount } }),
]);
}
document.body.appendChild(EffectWithCleanupExample());If
import { div, If, input, span, useState } from "tagu-tagu";
function IfExample() {
const isVisible = useState(false);
function toggle() {
isVisible.set(!isVisible.get());
}
return div([
input({
attr: { type: "checkbox" },
prop: { checked: isVisible },
on: { click: toggle },
}),
If(isVisible, () =>
div({
css: { background: "blue", width: "300px", height: "300px" },
}),
),
span("Check to show rectangle"),
]);
}
document.body.appendChild(IfExample());
Switch
import { button, div, Switch, useState } from "tagu-tagu";
function SwitchExample() {
const state = useState("triangle");
return div([
button("Triangle", { on: { click: () => state.set("triangle") } }),
button("Rectangle", { on: { click: () => state.set("rectangle") } }),
button("Circle", { on: { click: () => state.set("circle") } }),
button("Pentagon", { on: { click: () => state.set("pentagon") } }),
Switch(
state,
{
triangle: () => div("▲"),
rectangle: () => div("■"),
circle: () => div("●"),
},
() => div("?"),
),
]);
}
document.body.appendChild(SwitchExample());
For
import { button, div, For, useState } from "tagu-tagu";
function ForExample() {
const numbers = useState([1, 2, 3].map((n) => ({ n })));
let id = numbers.get().length;
function addNumber() {
id++;
numbers.set([...numbers.get(), { n: id }]);
}
function removeNumber(n: number) {
numbers.set(numbers.get().filter((value) => value.n !== n));
}
return div([
div([
For(numbers, (n) =>
button(`${n.n}`, {
on: { click: () => removeNumber(n.n) },
}),
),
]),
button("+", { on: { click: addNumber } }),
]);
}
document.body.appendChild(ForExample());
Await
import { div, span, Await, sleep } from "tagu-tagu";
function AwaitExample() {
async function asyncFunction(){
await sleep(2000);
return "Finished!";
}
return div([
Await(asyncFunction(), {
pending: () => span("Loading..."),
fulfilled: (value) => span(value)
})
]);
}
document.body.appendChild(AwaitExample());Promise<Element>
import { Await, div, sleep, span } from "tagu-tagu";
async function PromiseComponent() {
await sleep(2000);
return span("Finished!");
}
function AwaitPromiseOfElementExample() {
return div([
Await(PromiseComponent(), {
pending: () => span("Loading..."),
}),
]);
}
document.body.appendChild(AwaitPromiseOfElementExample());
Data binding
You can use data of ancestors.
import { button, div, useBinding, useState } from "tagu-tagu";
function Sky() {
return div("Sky", {
css: {
background: useBinding("theme", (theme) =>
theme === "dark" ? "darkblue" : "skyblue",
),
},
});
}
function DataBindingExample() {
const theme = useState("dark" as "dark" | "light");
return div({ data: { theme } }, [
Sky(),
button("dark", { on: { click: () => theme.set("dark") } }),
button("light", { on: { click: () => theme.set("light") } }),
]);
}
document.body.appendChild(DataBindingExample());
Seamless migration
Since tagu-tagu is just a helper, you can migrate from anywhere.
