1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-01 04:12:14 +02:00
This commit is contained in:
2024-11-22 11:00:15 +01:00
parent e157d2843e
commit 0f91166811
78 changed files with 17 additions and 34032 deletions

View File

@ -0,0 +1,23 @@
import { customElementWithDDE, el, on } from "deka-dom-el";
export class HTMLCustomElement extends HTMLElement{
static tagName= "custom-element";
connectedCallback(){
this.append(
el("p", "Hello from custom element!")
);
}
}
customElementWithDDE(HTMLCustomElement);
customElements.define(HTMLCustomElement.tagName, HTMLCustomElement);
const instance= el(HTMLCustomElement.tagName);
on.connected( // preffered
e=> console.log("Element connected to the DOM (v1):", e)
)(instance);
instance.addEventListener(
"dde:connected",
e=> console.log("Element connected to the DOM (v2):", e)
);
document.body.append(
instance,
);

View File

@ -0,0 +1,33 @@
import {
customElementRender,
customElementWithDDE,
} from "deka-dom-el";
export class HTMLCustomElement extends HTMLElement{
static tagName= "custom-element";
static observedAttributes= [ "attr" ];
connectedCallback(){
customElementRender(
this,
this.attachShadow({ mode: "open" }),
ddeComponent
);
}
set attr(value){ this.setAttribute("attr", value); }
get attr(){ return this.getAttribute("attr"); }
}
import { el, on, scope } from "deka-dom-el";
function ddeComponent({ attr }){
scope.host(
on.connected(e=> console.log(e.target.outerHTML)),
);
return el().append(
el("p", `Hello from Custom Element with attribute '${attr}'`)
);
}
customElementWithDDE(HTMLCustomElement);
customElements.define(HTMLCustomElement.tagName, HTMLCustomElement);
document.body.append(
el(HTMLCustomElement.tagName, { attr: "Attribute" })
);

View File

@ -0,0 +1,12 @@
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
import {
customElementRender,
customElementWithDDE,
observedAttributes,
} from "deka-dom-el";
/** @type {ddePublicElementTagNameMap} */
import { S } from "deka-dom-el/signals";
S.observedAttributes;
// “internal” utils
import { lifecyclesToEvents } from "deka-dom-el";

View File

@ -0,0 +1,21 @@
export class HTMLCustomElement extends HTMLElement{
static tagName= "custom-element"; // just suggestion, we can use `el(HTMLCustomElement.tagName)`
static observedAttributes= [ "custom-attribute" ];
constructor(){
super();
// nice place to prepare custom element
}
connectedCallback(){
// nice place to render custom element
}
attributeChangedCallback(name, oldValue, newValue){
// listen to attribute changes (see `observedAttributes`)
}
disconnectedCallback(){
// nice place to clean up
}
// for example, we can mirror get/set prop to attribute
get customAttribute(){ return this.getAttribute("custom-attribute"); }
set customAttribute(value){ this.setAttribute("custom-attribute", value); }
}
customElements.define(HTMLCustomElement.tagName, HTMLCustomElement);

View File

@ -0,0 +1,42 @@
import {
customElementRender,
customElementWithDDE,
observedAttributes,
el, on, scope
} from "deka-dom-el";
import { S } from "deka-dom-el/signals";
export class HTMLCustomElement extends HTMLElement{
static tagName= "custom-element";
static observedAttributes= [ "attr" ];
connectedCallback(){
console.log(observedAttributes(this));
customElementRender(
this,
this.attachShadow({ mode: "open" }),
ddeComponent,
S.observedAttributes
);
}
set attr(value){ this.setAttribute("attr", value); }
get attr(){ return this.getAttribute("attr"); }
}
/** @param {{ attr: ddeSignal<string, {}> }} props */
function ddeComponent({ attr }){
scope.host(
on.connected(e=> console.log(e.target.outerHTML)),
);
return el().append(
el("p", S(()=> `Hello from Custom Element with attribute '${attr()}'`))
);
}
customElementWithDDE(HTMLCustomElement);
customElements.define(HTMLCustomElement.tagName, HTMLCustomElement);
document.body.append(
el(HTMLCustomElement.tagName, { attr: "Attribute" })
);
setTimeout(
()=> document.querySelector(HTMLCustomElement.tagName).setAttribute("attr", "New Value"),
3*750
);

View File

@ -0,0 +1,69 @@
import {
el,
customElementRender,
customElementWithDDE,
} from "deka-dom-el";
function ddeComponent(){
return el().append(
el("style", `
.red{ color: firebrick; }
`),
el("p", { className: "red" }).append(
"Hello from ", el("slot", "Custom Element"), "!"
)
);
}
export class A extends HTMLElement{
static tagName= "custom-element-without";
connectedCallback(){
customElementRender(
this,
this,
ddeComponent
);
}
}
customElementWithDDE(A);
customElements.define(A.tagName, A);
export class B extends HTMLElement{
static tagName= "custom-element-open";
connectedCallback(){
customElementRender(
this,
this.attachShadow({ mode: "open" }),
ddeComponent
);
}
}
customElementWithDDE(B);
customElements.define(B.tagName, B);
export class C extends HTMLElement{
static tagName= "custom-element-closed";
connectedCallback(){
customElementRender(
this,
this.attachShadow({ mode: "closed" }),
ddeComponent
);
}
}
customElementWithDDE(C);
customElements.define(C.tagName, C);
document.body.append(
el(A.tagName).append("Without shadowRoot"),
el("hr"),
el(B.tagName).append("Open shadowRoot"),
el("hr"),
el(C.tagName).append("Closed shadowRoot"),
el("style", `
.red{ color: crimson; }
`),
);
console.log(A.tagName, "expect modifications");
document.body.querySelector(A.tagName).querySelector("p").textContent+= " (editable with JS)";
console.log(B.tagName, "expect modifications");
document.body.querySelector(B.tagName).shadowRoot.querySelector("p").textContent+= " (editable with JS)";
console.log(C.tagName, "expect error ↓");
document.body.querySelector(C.tagName).querySelector("p").textContent+= " (editable with JS)";

View File

@ -0,0 +1,41 @@
import {
customElementRender,
customElementWithDDE,
el,
simulateSlots
} from "deka-dom-el";
export class HTMLCustomElement extends HTMLElement{
static tagName= "custom-slotting";
connectedCallback(){
const c= ()=> simulateSlots(this, ddeComponent());
customElementRender(this, this, c);
}
}
customElementWithDDE(HTMLCustomElement);
customElements.define(HTMLCustomElement.tagName, HTMLCustomElement);
document.body.append(
el(HTMLCustomElement.tagName),
el(HTMLCustomElement.tagName).append(
"Slot"
),
el(ddeComponentSlot),
el(ddeComponentSlot).append(
"Slot"
),
);
function ddeComponent(){
return el().append(
el("p").append(
"Hello ", el("slot", "World")
)
);
}
function ddeComponentSlot(){
return simulateSlots(el().append(
el("p").append(
"Hello ", el("slot", "World")
)
));
}

View File

@ -0,0 +1,35 @@
import { el } from "deka-dom-el";
document.head.append(
el("style").append(
"tr, td{ border: 1px solid red; padding: 1em; }",
"table{ border-collapse: collapse; }"
)
);
document.body.append(
el("p", "Example of a complex template. Using for example nesting lists:"),
el("ul").append(
el("li", "List item 1"),
el("li").append(
el("ul").append(
el("li", "Nested list item 1"),
)
)
),
el("table").append(
el("tr").append(
el("td", "Row 1 Col 1"),
el("td", "Row 1 Col 2")
)
)
);
import { chainableAppend } from "deka-dom-el";
/** @param {keyof HTMLElementTagNameMap} tag */
const createElement= tag=> chainableAppend(document.createElement(tag));
document.body.append(
createElement("p").append(
createElement("em").append(
"You can also use `chainableAppend`!"
)
)
);

View File

@ -0,0 +1,33 @@
import { assign, assignAttribute, classListDeclarative } from "deka-dom-el";
const paragraph= document.createElement("p");
assignAttribute(paragraph, "textContent", "Hello, world!");
assignAttribute(paragraph, "style", "color: red; font-weight: bold;");
assignAttribute(paragraph, "style", { color: "navy" });
assignAttribute(paragraph, "dataTest1", "v1");
assignAttribute(paragraph, "dataset", { test2: "v2" });
assign(paragraph, { //textContent and style see above
ariaLabel: "v1", //data* see above
ariaset: { role: "none" }, // dataset see above
"=onclick": "console.log(event)",
onmouseout: console.info,
".something": "something",
classList: {} //see below
});
classListDeclarative(paragraph, {
classAdd: true,
classRemove: false,
classAdd1: 1,
classRemove1: 0,
classToggle: -1
});
console.log(paragraph.outerHTML);
console.log("paragraph.something=", paragraph.something);
document.body.append(
paragraph
);

View File

@ -0,0 +1,17 @@
import { el } from "deka-dom-el";
document.head.append(
el("style").append(
".class1{ font-weight: bold; }",
".class2{ color: purple; }"
)
);
document.body.append(
el(component, { className: "class2", textContent: "Hello World!" }),
component({ className: "class2", textContent: "Hello World!" })
);
function component({ className, textContent }){
return el("div", { className: [ "class1", className ] }).append(
el("p", textContent)
);
}

View File

@ -0,0 +1,11 @@
import { el, assign } from "deka-dom-el";
const color= "lightcoral";
document.body.append(
el("p", { textContent: "Hello (first time)", style: { color } })
);
document.body.append(
assign(
document.createElement("p"),
{ textContent: "Hello (second time)", style: { color } }
)
);

View File

@ -0,0 +1,11 @@
import { elNS, assign } from "deka-dom-el";
const elSVG= elNS("http://www.w3.org/2000/svg");
const elMath= elNS("http://www.w3.org/1998/Math/MathML");
document.body.append(
elSVG("svg"), // see https://developer.mozilla.org/en-US/docs/Web/SVG and https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
elMath("math") // see https://developer.mozilla.org/en-US/docs/Web/MathML and https://developer.mozilla.org/en-US/docs/Web/MathML/Global_attributes
);
console.log(
document.body.innerHTML.includes("<svg></svg><math></math>")
)

View File

@ -0,0 +1,14 @@
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
import {
assign,
el, createElement,
elNS, createElementNS
} from "deka-dom-el";
el===createElement
elNS===createElementNS
// “internal” utils
import {
assignAttribute,
classListDeclarative,
chainableAppend
} from "deka-dom-el";

View File

@ -0,0 +1,11 @@
document.body.append(
document.createElement("div"),
document.createElement("span"),
document.createElement("main")
);
console.log(document.body.innerHTML.includes("<div></div><span></span><main></main>"));
const template= document.createElement("main").append(
document.createElement("div"),
document.createElement("span"),
);
console.log(typeof template==="undefined");

View File

@ -0,0 +1,14 @@
document.body.append(
document.createElement("div")
);
console.log(
"Emty div is generated inside <body>:",
document.body.innerHTML.includes("<div></div>")
);
document.body.append(
Object.assign(
document.createElement("p"),
{ textContent: "Elements text content.", style: "color: coral;" }
)
);

View File

@ -0,0 +1,13 @@
import { el, on } from "deka-dom-el";
const log= mark=> console.log.bind(console, mark);
const abort_controller= new AbortController();
const { signal }= abort_controller;
const button= el("button", "Test click");
button.addEventListener("click", log("`addEventListener`"), { signal });
on("click", log("`on`"), { signal })(button);
document.body.append(
button, " ", el("button", { textContent: "Off", onclick: ()=> abort_controller.abort() })
);

View File

@ -0,0 +1,10 @@
import { el, on } from "deka-dom-el";
const log= mark=> console.log.bind(console, mark);
const button= el("button", "Test click");
button.addEventListener("click", log("`addEventListener`"), { once: true });
on("click", log("`on`"), { once: true })(button);
document.body.append(
button
);

View File

@ -0,0 +1,24 @@
import { el, on, dispatchEvent } from "deka-dom-el";
document.body.append(
el("p", "Listenning to `test` event.", on("test", console.log)).append(
el("br"),
el("button", "native", on("click", native)),
" ",
el("button", "dde", on("click", dde)),
" ",
el("button", "dde with options", on("click", ddeOptions))
)
);
function native(){
this.dispatchEvent(
new CustomEvent("test",
{ bubbles: true, detail: "hi" }
)
);
}
function dde(){
dispatchEvent("test")(this.parentElement, "hi");
}
function ddeOptions(){
dispatchEvent("test", { bubbles: true })(this, "hi");
}

View File

@ -0,0 +1,4 @@
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm.js
import { on, dispatchEvent } from "deka-dom-el";
/** @type {ddeElementAddon} */

View File

@ -0,0 +1,18 @@
import { el, on } from "deka-dom-el";
const paragraph= el("p", "See live-cycle events in console.",
el=> log({ type: "dde:created", detail: el }),
on.connected(log),
on.disconnected(log),
on.attributeChanged(log));
document.body.append(
paragraph,
el("button", "Update attribute", on("click", ()=> paragraph.setAttribute("test", Math.random().toString()))),
" ",
el("button", "Remove", on("click", ()=> paragraph.remove()))
);
/** @param {Partial<CustomEvent>} event */
function log({ type, detail }){
console.log({ _this: this, type, detail });
}

View File

@ -0,0 +1,24 @@
import { el, on } from "deka-dom-el";
const abort_controller= new AbortController();
const { signal }= abort_controller;
/** @type {ddeElementAddon<HTMLButtonElement>} */
const onclickOff= on("click", ()=> abort_controller.abort(), { signal });
/** @type {(ref?: HTMLOutputElement)=> HTMLOutputElement | null} */
const ref= (store=> ref=> ref ? (store= ref) : store)(null);
document.body.append(
el("button", "Test `on`",
on("click", console.log, { signal }),
on("click", update, { signal }),
on("mouseup", update, { signal })),
" ",
el("button", "Off", onclickOff),
el("output", { style: { display: "block", whiteSpace: "pre" } }, ref)
);
/** @param {MouseEvent} event @this {HTMLButtonElement} */
function update(event){
ref().append(
event.type,
"\n"
);
}

View File

@ -0,0 +1,6 @@
// pseudo code!
const onchage=
event=>
console.log("Reacting to the:", event); // A
input.addEventListener("change", onchange); // B
input.dispatchEvent(new Event("change")); // C

View File

@ -0,0 +1,15 @@
import { el } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
const clicks= S(0); // A
document.body.append(
el().append(
el("p", S(()=>
"Hello World "+"🎉".repeat(clicks()) // B
)),
el("button", {
type: "button",
onclick: ()=> clicks(clicks()+1), // C
textContent: "Fire",
})
)
);

View File

@ -0,0 +1,44 @@
import { el } from "deka-dom-el";
class Test {
constructor(params){
this._params= params;
}
render(){
return el("div").append(
this._params.textContent
);
}
}
document.body.append(
elClass(Test, { textContent: "Hello World" })
);
import { chainableAppend, scope } from "deka-dom-el";
function elClass(_class, attributes, ...addons){
let element, element_host;
scope.push({
scope: _class, //just informative purposes
host: (...addons_append)=> addons_append.length
? (
!element
? addons.unshift(...addons_append)
: addons_append.forEach(c=> c(element_host))
, undefined)
: element_host
});
const instance= new _class(attributes);
element= instance.render();
const is_fragment= element instanceof DocumentFragment;
const el_mark= el.mark({ //this creates html comment `<dde:mark …/>`
type: "class-component",
name: _class.name,
host: is_fragment ? "this" : "parentElement",
});
element.prepend(el_mark);
if(is_fragment) element_host= el_mark;
chainableAppend(element);
addons.forEach(c=> c(element_host));
scope.pop();
return element;
}

View File

@ -0,0 +1,18 @@
import { el, empty, on } from "deka-dom-el";
document.body.append(
el(component),
el("button", {
textContent: "Remove",
onclick: ()=> empty(document.body),
type: "button"
})
);
import { S } from "deka-dom-el/signals";
function component(){
const textContent= S("Click to change text.");
const onclickChange= on("click", function redispatch(){
textContent("Text changed! "+(new Date()).toString())
});
return el("p", textContent, onclickChange);
}

View File

@ -0,0 +1,35 @@
/* PSEUDO-CODE!!! */
import { el } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function component(){
/* prepare changeable data */
const dataA= S("data");
const dataB= S("data");
/* define data flow (can be asynchronous) */
fetchAPI().then(data_new=> dataA(data_new));
setTimeout(()=> dataB("DATA"));
/* declarative UI */
return el().append(
el("h1", {
textContent: "Example",
/* declarative attribute(s) */
classList: { declarative: dataB }
}),
el("ul").append(
/* declarative element(s) */
S.el(dataA, data=> data.map(d=> el("li", d)))
),
el("ul").append(
/* declarative component(s) */
S.el(dataA, data=> data.map(d=> el(subcomponent, d)))
)
);
}
function subcomponent({ id }){
/* prepare changeable data */
const textContent= S("…");
/* define data flow (can be asynchronous) */
fetchAPI(id).then(text=> textContent(text));
/* declarative UI */
return el("li", { textContent, dataId: id });
}

View File

@ -0,0 +1,18 @@
import { el, scope, on, dispatchEvent } from "deka-dom-el";
document.body.append(
el(component)
);
function component(){
const { host }= scope; // good practise!
host(
console.log,
on("click", function redispatch(){
// this `host` ↘ still corresponds to the host ↖ of the component
dispatchEvent("redispatch")(host());
})
);
// this `host` ↘ still corresponds to the host ↖ of the component
setTimeout(()=> dispatchEvent("timeout")(host()), 750)
return el("p", "Clickable paragraph!");
}

View File

@ -0,0 +1,31 @@
/* PSEUDO-CODE!!! */
import { el, on, scope } from "deka-dom-el";
function component(){
const { host }= scope;
const ul= el("ul");
const ac= new AbortController();
fetchAPI({ signal: ac.signal }).then(data=> {
data.forEach(d=> ul.append(el("li", d)));
});
host(
/* element was remove before data fetched */
on.disconnected(()=> ac.abort())
);
return ul;
/**
* NEVER EVER!!
* let data;
* fetchAPI().then(d=> data= O(d));
*
* OR NEVER EVER!!
* const ul= el("ul");
* fetchAPI().then(d=> {
* const data= O("data");
* ul.append(el("li", data));
* });
*
* // THE HOST IS PROBABLY DIFFERENT THAN
* // YOU EXPECT AND OBSERVABLES MAY BE
* // UNEXPECTEDLY REMOVED!!!
* */
}

View File

@ -0,0 +1,3 @@
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
import { scope, el } from "deka-dom-el";
/** @type {ddeElementAddon} */

View File

@ -0,0 +1,33 @@
import { el, on, scope } from "deka-dom-el";
const { host }= scope;
host(
element=> console.log(
"This represents Addon/oninit for root",
element.outerHTML
)
);
console.log(
"This represents the reference to the host element of root",
host().outerHTML
);
document.body.append(
el(component)
);
function component(){
const { host }= scope;
host(
element=> console.log(
"This represents Addon/oninit for the component",
element.outerHTML
)
);
const onclick= on("click", function(ev){
console.log(
"This represents the reference to the host element of the component",
host().outerHTML
);
})
return el("div", null, onclick).append(
el("strong", "Component")
);
}

View File

@ -0,0 +1,18 @@
import { S } from "deka-dom-el/signals";
const signal= S(0, {
increaseOnlyOdd(add){
console.info(add);
if(add%2 === 0) return this.stopPropagation();
this.value+= add;
}
});
S.on(signal, console.log);
const oninterval= ()=>
S.action(signal, "increaseOnlyOdd", Math.floor(Math.random()*100));
const interval= 5*1000;
setTimeout(
clearInterval,
10*interval,
setInterval(oninterval, interval)
);

View File

@ -0,0 +1,56 @@
import { S } from "deka-dom-el/signals";
const todos= S([], {
push(item){
this.value.push(S(item));
},
pop(){
const removed= this.value.pop();
if(removed) S.clear(removed);
},
[S.symbols.onclear](){ // this covers `O.clear(todos)`
S.clear(...this.value);
}
});
import { el, on } from "deka-dom-el";
/** @type {ddeElementAddon<HTMLFormElement>} */
const onsubmit= on("submit", function(event){
event.preventDefault();
const data= new FormData(this);
switch (data.get("op")){
case "A"/*dd*/:
S.action(todos, "push", data.get("todo"));
break;
case "E"/*dit*/: {
const last= todos().at(-1);
if(!last) break;
last(data.get("todo"));
break;
}
case "R"/*emove*/:
S.action(todos, "pop");
break;
}
});
document.body.append(
el("ul").append(
S.el(todos, todos=>
todos.map(textContent=> el("li", textContent)))
),
el("form", null, onsubmit).append(
el("input", { type: "text", name: "todo", placeholder: "Todos text" }),
el(radio, { textContent: "Add", checked: true }),
el(radio, { textContent: "Edit last" }),
el(radio, { textContent: "Remove" }),
el("button", "Submit")
)
);
document.head.append(
el("style", "form{ display: flex; flex-flow: column nowrap; }")
);
function radio({ textContent, checked= false }){
return el("label").append(
el("input", { type: "radio", name: "op", value: textContent[0], checked }),
" ",textContent
)
}

View File

@ -0,0 +1,16 @@
import { S } from "deka-dom-el/signals";
const signal= S(0);
// computation pattern
const double= S(()=> 2*signal());
const ac= new AbortController();
S.on(signal, v=> console.log("signal", v), { signal: ac.signal });
S.on(double, v=> console.log("double", v), { signal: ac.signal });
signal(signal()+1);
const interval= 5 * 1000;
const id= setInterval(()=> signal(signal()+1), interval);
ac.signal.addEventListener("abort",
()=> setTimeout(()=> clearInterval(id), 2*interval));
setTimeout(()=> ac.abort(), 3*interval)

View File

@ -0,0 +1,15 @@
import { S } from "deka-dom-el/signals";
const count= S(0);
import { el } from "deka-dom-el";
document.body.append(
el("p", S(()=> "Currently: "+count())),
el("p", { classList: { red: S(()=> count()%2) }, dataset: { count }, textContent: "Attributes example" })
);
document.head.append(
el("style", ".red { color: red; }")
);
const interval= 5 * 1000;
setTimeout(clearInterval, 10*interval,
setInterval(()=> count(count()+1), interval));

View File

@ -0,0 +1,26 @@
import { S } from "deka-dom-el/signals";
const count= S(0, {
add(){ this.value= this.value + Math.round(Math.random()*10); }
});
const numbers= S([ count() ], {
push(next){ this.value.push(next); }
});
import { el } from "deka-dom-el";
document.body.append(
S.el(count, count=> count%2
? el("p", "Last number is odd.")
: el()
),
el("p", "Lucky numbers:"),
el("ul").append(
S.el(numbers, numbers=> numbers.toReversed()
.map(n=> el("li", n)))
)
);
const interval= 5*1000;
setTimeout(clearInterval, 10*interval, setInterval(function(){
S.action(count, "add");
S.action(numbers, "push", count());
}, interval));

View File

@ -0,0 +1,6 @@
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
import { S, signal } from "deka-dom-el/signals";
S===signal
/** @type {ddeSignal} */
/** @type {ddeAction} */
/** @type {ddeActions} */

View File

@ -0,0 +1,12 @@
import { S } from "deka-dom-el/signals";
// α — `signal` represents a reactive value
const signal= S(0);
// β — just reacts on signal changes
S.on(signal, console.log);
// γ — just updates the value
const update= ()=> signal(signal()+1);
update();
const interval= 5*1000;
setTimeout(clearInterval, 10*interval,
setInterval(update, interval));