diff --git a/README.md b/README.md
index 8e6c2ba..1575841 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,113 @@
**WIP** (the experimentation phase) | [source code on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | [*mirrored* on GitHub](https://github.com/jaandrle/deka-dom-el)
# Deka DOM Elements
-This is reimplementation of [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM. Subrepository for https://github.com/jaandrle/jaaJSU ($dom namespace)](https://github.com/jaandrle/dollar_dom_component).
+This is reimplementation of [jaandrle/dollar_dom_component: Functional DOM components without JSX and virtual DOM.](https://github.com/jaandrle/dollar_dom_component).
The goal is to be even more close to the native JavaScript.
+
+# Native JavaScript DOM elements creations
+Let’s go through all patterns we would like to use and what needs to be improved for better experience.
+
+## Creating element and DOM templates natively
+```js
+document.body.append(
+ document.createElement("div"),
+ document.createElement("span"),
+ document.createElement("main")
+);
+//=> HTML output:
+const template= document.createElement("main").append(
+ document.createElement("div"),
+ document.createElement("span"),
+);
+//=> ★:: typeof template==="undefined"
+```
+**Pitfalls**:
+- there is lots of text
+- `.append` methdo returns `void`⇒ it cannot be chained (see ★)
+
+## Set properties of created element
+```js
+const element= Object.assign(document.createElement("p"), { className: "red", textContent: "paragraph" });
+document.body.append(element);
+//=> HTML output: paragraph
+```
+**Pitfalls**:
+- there is lots of text
+- `Object.assign` isn’t ideal as it can set only (some) [IDL](https://developer.mozilla.org/en-US/docs/Glossary/IDL)
+
+# Events and dynamic parts
+```js
+const input= document.createElement("input");
+const output= document.createElement("output");
+input.addEventListener("change", function(event){
+ output.value= event.target.value;
+});
+document.body.append(
+ output,
+ input
+);
+//=> HTML output:
+```
+**Pitfalls**:
+- there is lots of text
+- very hard to organize code
+
+# Helpers and modifications
+Now, let's introduce library helpers and modifications.
+
+## `.append`
+The `.append` is owerwrote to always returns element. This seem to be the best way to do it as it is very hard
+to create Proxy around `HTMLElement`, ….
+```js
+document.body.append(
+ document.createElement("main").append(
+ document.createElement("div"),
+ document.createElement("span"),
+ )
+);
+//=> HTML output:
+```
+
+## `el` and `assign` functions
+```js
+const element= assign(document.createElement("a"), {
+ className: "red",
+ dataTest: "test",
+ href: "www.seznam.cz",
+ textContent: "Link",
+ style: { color: "blue" }
+});
+document.body.append(element);
+assign(element, { style: undefined });
+//=> HTML output: Link
+```
+…but for elements/template creations `el` is even better:
+```js
+document.body.append(
+ el("div").append(
+ el("p").append(
+ el("#text", { textContent: "Link: " }),
+ el("a", {
+ href: "www.seznam.cz",
+ textContent: "example",
+ })
+ )
+ )
+);
+```
+
+## Events and dynamic parts
+```js
+const output_dynamic= (function(){
+ const element= el("span", { style: { fontWeight: "bold" }, textContent: "" });
+ return {
+ element,
+ onchange: listen("change", event=> assign(element, { textContent: event.target.value }))
+ };
+})();
+document.body.append(
+ output_dynamic.element,
+ el("input", { type: "text" }, output_dynamic.onchange)
+);
+```
diff --git a/index.js b/index.js
index 39efeaa..d4732cd 100644
--- a/index.js
+++ b/index.js
@@ -2,74 +2,5 @@
const { append }= c.prototype;
c.prototype.append= function(...els){ append.apply(this, els); return this; };
});
-
-export function createElement(tag, attributes){
- if(tag==="<>") return document.createDocumentFragment();
- if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
- return assign(document.createElement(tag), attributes);
-}
-export { createElement as el };
-export function createElementNS(tag, attributes, attributes_todo){
- let namespace= "svg";
- if(typeof attributes_todo !== "undefined"){
- namespace= tag; tag= attributes; attributes= attributes_todo; }
- if(tag==="<>") return document.createDocumentFragment();
- if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
- return assign(document.createElementNS(namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace, tag), attributes);
-}
-export { createElementNS as elNS };
-
-export function assign(element, ...attributes){
- // prefers https://developer.mozilla.org/en-US/docs/Glossary/IDL
- if(!attributes.length) return element;
- const is_svg= element instanceof SVGElement;
- const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
-
- Object.entries(Object.assign({}, ...attributes)).forEach(function([ key, attr ]){
- if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
- if(key[0]===".") return setDelete(element, key.slice(1), attr);
- if(typeof attr === "object"){
- switch(key){
- case "style": return forEachEntries(attr, setRemove.bind(null, element.style, "Property"))
- case "dataset": return forEachEntries(attr, setDelete.bind(null, element.dataset));
- case "ariaset": return forEachEntries(attr, (key, val)=> setRemoveAttr("aria-"+key, val));
- case "classList": return classListDeclartive(element, attr);
- default: return Reflect.set(element, key, attr);
- }
- }
- if(/(aria|data)([A-Z])/.test(key)){
- key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
- return setRemoveAttr(key, attr);
- }
- switch(key){
- case "href" || "src" || "style":
- return setRemoveAttr(key, attr);
- case "xlink:href":
- return setRemoveAttr(key, attr, "http://www.w3.org/1999/xlink");
- case "textContent" || "innerText":
- if(!is_svg) break;
- return element.appendChild(document.createTextNode(attr));
- }
- if(key in element && !is_svg)
- return setDelete(element, key, attr);
- return setRemoveAttr(key, attr);
- });
- return element;
-}
-export function classListDeclartive(element, toggle){
- if(typeof toggle !== "object") return element;
-
- forEachEntries(toggle,
- (class_name, val)=>
- element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)))
- return element;
-}
-
-export function empty(el){ Array.from(el.children).forEach(el=> el.remove()); return el; }
-
-function forEachEntries(obj, cb){ return Object.entries(obj).forEach(([ key, val ])=> cb(key, val)); }
-function isUndef(value){ return typeof value==="undefined"; }
-
-function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); }
-function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); }
-function setDelete(obj, prop, val){ return Reflect[ isUndef(val) ? "deleteProperty" : "set" ](obj, prop, val); }
+export * from "./src/dom.js";
+export * from "./src/events.js";
diff --git a/src/dom.js b/src/dom.js
new file mode 100644
index 0000000..ec1fe71
--- /dev/null
+++ b/src/dom.js
@@ -0,0 +1,75 @@
+export function createElement(tag, attributes, ...connect){
+ let el;
+ switch(true){
+ case typeof tag==="function": el= tag(attributes || undefined); break;
+ case tag==="<>": el= document.createDocumentFragment(); break;
+ case tag==="#text": el= assign(document.createTextNode(""), attributes); break;
+ default: el= assign(document.createElement(tag), attributes);
+ }
+ connect.forEach(c=> c(el));
+ return el;
+}
+export { createElement as el };
+export function createElementNS(tag, attributes, attributes_todo){
+ let namespace= "svg";
+ if(typeof attributes_todo !== "undefined"){
+ namespace= tag; tag= attributes; attributes= attributes_todo; }
+ if(tag==="<>") return document.createDocumentFragment();
+ if(tag==="") return document.createTextNode(attributes.textContent ?? attributes.innerText ?? attributes.innerHTML);
+ return assign(document.createElementNS(namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace, tag), attributes);
+}
+export { createElementNS as elNS };
+
+export function assign(element, ...attributes){
+ if(!attributes.length) return element;
+ const is_svg= element instanceof SVGElement;
+ const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
+
+ Object.entries(Object.assign({}, ...attributes)).forEach(function([ key, attr ]){
+ if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
+ if(key[0]===".") return setDelete(element, key.slice(1), attr);
+ if(typeof attr === "object"){
+ switch(key){
+ case "style": return forEachEntries(attr, setRemove.bind(null, element.style, "Property"))
+ case "dataset": return forEachEntries(attr, setDelete.bind(null, element.dataset));
+ case "ariaset": return forEachEntries(attr, (key, val)=> setRemoveAttr("aria-"+key, val));
+ case "classList": return classListDeclartive(element, attr);
+ default: return Reflect.set(element, key, attr);
+ }
+ }
+ if(/(aria|data)([A-Z])/.test(key)){
+ key= key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
+ return setRemoveAttr(key, attr);
+ }
+ switch(key){
+ case "href" || "src" || "style":
+ return setRemoveAttr(key, attr);
+ case "xlink:href":
+ return setRemoveAttr(key, attr, "http://www.w3.org/1999/xlink");
+ case "textContent" || "innerText":
+ if(!is_svg) break;
+ return element.appendChild(document.createTextNode(attr));
+ }
+ if(key in element && !is_svg)
+ return setDelete(element, key, attr);
+ return setRemoveAttr(key, attr);
+ });
+ return element;
+}
+export function classListDeclartive(element, toggle){
+ if(typeof toggle !== "object") return element;
+
+ forEachEntries(toggle,
+ (class_name, val)=>
+ element.classList.toggle(class_name, val===-1 ? undefined : Boolean(val)))
+ return element;
+}
+
+export function empty(el){ Array.from(el.children).forEach(el=> el.remove()); return el; }
+
+function forEachEntries(obj, cb){ return Object.entries(obj).forEach(([ key, val ])=> cb(key, val)); }
+function isUndef(value){ return typeof value==="undefined"; }
+
+function setRemove(obj, prop, key, val){ return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val); }
+function setRemoveNS(obj, prop, key, val, ns= null){ return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val); }
+function setDelete(obj, prop, val){ return Reflect[ isUndef(val) ? "deleteProperty" : "set" ](obj, prop, val); }
diff --git a/src/events.js b/src/events.js
new file mode 100644
index 0000000..d20f733
--- /dev/null
+++ b/src/events.js
@@ -0,0 +1,8 @@
+export function listen(event, listener, options){
+ return element=> element.addEventListener(event, listener, options);
+}
+export function dispatch(event, detail){
+ if(typeof event === "string")
+ event= typeof detail==="undefined" ? new Event(event) : new CustomEvent(event, { detail });
+ return element=> element.dispatchEvent(event);
+}
diff --git a/test/index.js b/test/index.js
index fa85fdb..35acdb6 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,17 +1,63 @@
-import { el, elNS, assign } from "../index.js";
-Object.assign(globalThis, { el, elNS, assign });
+import { el, elNS, assign, listen, dispatch } from "../index.js";
+Object.assign(globalThis, { el, elNS, assign, listen, dispatch });
-console.log(el("p", { className: "red", textContent: "Hello "}));
-console.log(el("p", { className: "red", textContent: "Hello "}) instanceof HTMLParagraphElement);
+const { style, css }= createStyle();
+globalThis.test= console.log;
+const app= el(component, null, listen("change", globalThis.test));
+dispatch("change", "Peter")(app);
+console.log(app, app instanceof HTMLDivElement);
-document.head.append(
- el("style", { textContent: `
- .red{ color: red; }
- ` })
-)
-document.body.append(
- el("p", { className: "red" }).append(
- el("", { textContent: "Hello " }),
- el("strong", { textContent: "World" })
+document.head.append(style);
+document.body.append(app);
+
+function component({ value= "World" }= {}){
+ const name= "naiveForm";
+ css`
+ .${name}{
+ display: flex;
+ flex-flow: column nowrap;
+ }
+ .${name} input{
+ margin-inline-start: .5em;
+ }
+ `;
+
+ const output= (function(){
+ const element= el("strong", { textContent: value });
+ return {
+ element,
+ onchange: listen("change", function(event){
+ assign(element, { textContent: event.target.value });
+ })
+ }
+ })();
+ const input= (function(){
+ const element= el("input", { type: "text", value }, output.onchange);
+ return {
+ element,
+ onchange: listen("change", function(event){
+ assign(element, { value: event.detail });
+ dispatch("change")(element);
+ })
+ };
+ })();
+ return el("div", { className: name }, input.onchange).append(
+ el("p").append(
+ el("#text", { textContent: "Hello " }),
+ output.element,
+ ),
+ el("label").append(
+ el("#text", { textContent: "Set name:" }),
+ input.element
+ )
)
-);
+}
+function createStyle(){
+ const style= el("style");
+ return {
+ style,
+ css(...args){
+ style.appendChild(el("#text", { textContent: String.raw(...args) }));
+ }
+ };
+}