mirror of
https://github.com/jaandrle/deka-dom-el
synced 2024-11-24 17:39:36 +01:00
💥 Signals are now optional + reactive element
- `registerReactivity` can be used to register custom behavior - Signals are automatically registered when `signals.js` is imported; - `el("<>", signal, map)`
This commit is contained in:
parent
a36786042b
commit
f5adefbc9c
1
index.js
1
index.js
@ -4,4 +4,3 @@
|
|||||||
});
|
});
|
||||||
export * from "./src/dom.js";
|
export * from "./src/dom.js";
|
||||||
export * from "./src/events.js";
|
export * from "./src/events.js";
|
||||||
export * from "./src/signals.js";
|
|
||||||
|
21
src/dom.js
21
src/dom.js
@ -1,3 +1,5 @@
|
|||||||
|
import { signals } from "./signals-common.js";
|
||||||
|
|
||||||
let namespace_curr= "html";
|
let namespace_curr= "html";
|
||||||
export function namespace(namespace){
|
export function namespace(namespace){
|
||||||
namespace_curr= namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace;
|
namespace_curr= namespace==="svg" ? "http://www.w3.org/2000/svg" : namespace;
|
||||||
@ -5,15 +7,17 @@ export function namespace(namespace){
|
|||||||
append(el){ namespace_curr= "html"; return el; }
|
append(el){ namespace_curr= "html"; return el; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
import { typeOf } from './helpers.js';
|
|
||||||
import { isSignal, valueOfSignal } from './signals-common.js';
|
|
||||||
export function createElement(tag, attributes, ...connect){
|
export function createElement(tag, attributes, ...connect){
|
||||||
if(typeOf(attributes)!=="[object Object]" || ( isSignal(attributes) && typeOf(valueOfSignal(attributes))!=="[object Object]" ))
|
|
||||||
attributes= { textContent: attributes };
|
|
||||||
let el;
|
let el;
|
||||||
|
if("<>"===tag){
|
||||||
|
if(signals.isReactiveAtrribute(attributes))
|
||||||
|
return signals.reactiveElement(attributes, ...connect);
|
||||||
|
el= document.createDocumentFragment();
|
||||||
|
}
|
||||||
|
if(signals.isTextContent(attributes))
|
||||||
|
attributes= { textContent: attributes };
|
||||||
switch(true){
|
switch(true){
|
||||||
case typeof tag==="function": el= tag(attributes || undefined); break;
|
case typeof tag==="function": el= tag(attributes || undefined); break;
|
||||||
case tag==="<>": el= document.createDocumentFragment(); break;
|
|
||||||
case tag==="#text": el= assign(document.createTextNode(""), attributes); break;
|
case tag==="#text": el= assign(document.createTextNode(""), attributes); break;
|
||||||
case namespace_curr!=="html": el= assign(document.createElementNS(namespace_curr, tag), attributes); break;
|
case namespace_curr!=="html": el= assign(document.createElementNS(namespace_curr, tag), attributes); break;
|
||||||
default: el= assign(document.createElement(tag), attributes);
|
default: el= assign(document.createElement(tag), attributes);
|
||||||
@ -23,17 +27,14 @@ export function createElement(tag, attributes, ...connect){
|
|||||||
}
|
}
|
||||||
export { createElement as el };
|
export { createElement as el };
|
||||||
|
|
||||||
import { addSignalListener } from './signals-common.js';
|
|
||||||
export function assign(element, ...attributes){
|
export function assign(element, ...attributes){
|
||||||
if(!attributes.length) return element;
|
if(!attributes.length) return element;
|
||||||
const is_svg= element instanceof SVGElement;
|
const is_svg= element instanceof SVGElement;
|
||||||
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
const setRemoveAttr= (is_svg ? setRemoveNS : setRemove).bind(null, element, "Attribute");
|
||||||
|
|
||||||
Object.entries(Object.assign({}, ...attributes)).forEach(function assignNth([ key, attr ]){
|
Object.entries(Object.assign({}, ...attributes)).forEach(function assignNth([ key, attr ]){
|
||||||
if(isSignal(attr)){ //TODO: unmounted
|
if(signals.isReactiveAtrribute(attr, key))
|
||||||
addSignalListener(attr, attr=> assignNth([ key, attr ]));
|
attr= signals.process(key, attr, assignNth);
|
||||||
attr= attr();
|
|
||||||
}
|
|
||||||
if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
|
if(key[0]==="=") return setRemoveAttr(key.slice(1), attr);
|
||||||
if(key[0]===".") return setDelete(element, key.slice(1), attr);
|
if(key[0]===".") return setDelete(element, key.slice(1), attr);
|
||||||
if(typeof attr === "object"){
|
if(typeof attr === "object"){
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { isSignal, addSignalListener, removeSignalListener } from './signals-common.js';
|
import { signals } from './signals-common.js';
|
||||||
|
export { registerReactivity } from './signals-common.js';
|
||||||
|
|
||||||
export function on(event, listener, options){
|
export function on(event, listener, options){
|
||||||
if(!isSignal(event))
|
if(!signals.isReactiveAtrribute(event))
|
||||||
return element=> element.addEventListener(event, listener, options);
|
return element=> element.addEventListener(event, listener, options);
|
||||||
//TODO cleanup when signal removed (also TODO)
|
//TODO cleanup when signal removed (also TODO)
|
||||||
if(options && options.signal)
|
if(options && options.signal)
|
||||||
options.signal.addEventListener("abort", ()=> removeSignalListener(event, listener));
|
options.signal.addEventListener("abort", ()=> signals.off(event, listener));
|
||||||
return addSignalListener(event, listener);
|
return signals.on(event, listener);
|
||||||
}
|
}
|
||||||
export function off(){//TODO is needed?
|
export function off(){//TODO is needed?
|
||||||
const abort= new AbortController();
|
const abort= new AbortController();
|
||||||
|
@ -1,23 +1,12 @@
|
|||||||
export const mark= Symbol.for("signal");
|
import { typeOf } from './helpers.js';
|
||||||
|
export const signals= {
|
||||||
export function isSignal(candidate){
|
isReactiveAtrribute(attr, key){ return false; },
|
||||||
try{ return Reflect.has(candidate, mark); }
|
isTextContent(attributes){ return typeOf(attributes)!=="[object Object]"; },
|
||||||
catch(e){ return false; }
|
process(key, attr, assignNth){ return false; },
|
||||||
}
|
on(signal, listener){ return false; },
|
||||||
export function valueOfSignal(signal){
|
off(signal, listener){ return false; },
|
||||||
return signal[mark].value;
|
reactiveElement(attributes, ...connect){ return document.createDocumentFragment(); }
|
||||||
}
|
|
||||||
export function toSignal(signal, value){
|
|
||||||
signal[mark]= {
|
|
||||||
value,
|
|
||||||
listeners: new Set()
|
|
||||||
};
|
};
|
||||||
return signal;
|
export function registerReactivity(def){
|
||||||
}
|
return Object.assign(signals, def);
|
||||||
|
|
||||||
export function addSignalListener(signal, listener){
|
|
||||||
return signal[mark].listeners.add(listener);
|
|
||||||
}
|
|
||||||
export function removeSignalListener(signal, listener){
|
|
||||||
return signal[mark].listeners.delete(listener);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,41 @@
|
|||||||
import { mark, isSignal, toSignal, addSignalListener } from "./signals-common.js";
|
export const mark= Symbol.for("signal");
|
||||||
export { isSignal, addSignalListener };
|
|
||||||
|
export function isSignal(candidate){
|
||||||
|
try{ return Reflect.has(candidate, mark); }
|
||||||
|
catch(e){ return false; }
|
||||||
|
}
|
||||||
|
import { typeOf } from './helpers.js';
|
||||||
|
import { registerReactivity } from "./signals-common.js";
|
||||||
|
registerReactivity({
|
||||||
|
isReactiveAtrribute(attr, key){ return isSignal(attr); },
|
||||||
|
isTextContent(attributes){
|
||||||
|
//TODO FIX el(…, S.reactive(…))
|
||||||
|
return typeOf(attributes)!=="[object Object]" || ( isSignal(attributes) && typeOf(valueOfSignal(attributes))!=="[object Object]" );
|
||||||
|
},
|
||||||
|
process(key, attr, assignNth){ //TODO: unmounted
|
||||||
|
addSignalListener(attr, attr=> assignNth([ key, attr ]));
|
||||||
|
return attr();
|
||||||
|
},
|
||||||
|
on: addSignalListener,
|
||||||
|
off: removeSignalListener,
|
||||||
|
reactiveElement(signal, map){
|
||||||
|
const mark= document.createComment("reactive");
|
||||||
|
const out= document.createDocumentFragment();
|
||||||
|
out.append(mark);
|
||||||
|
let cache;
|
||||||
|
const toEls= v=> {
|
||||||
|
let els= map(v);
|
||||||
|
if(!Array.isArray(els))
|
||||||
|
els= [ els ];
|
||||||
|
if(cache) cache.forEach(el=> el.remove());
|
||||||
|
cache= els;
|
||||||
|
mark.before(...els);
|
||||||
|
};
|
||||||
|
addSignalListener(signal, toEls);
|
||||||
|
toEls(signal());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function S(value){
|
export function S(value){
|
||||||
if(typeof value!=="function")
|
if(typeof value!=="function")
|
||||||
@ -34,6 +70,14 @@ function reactive(data){
|
|||||||
value.length ? write(signal, reactive(value[0])) : read(signal[mark]);
|
value.length ? write(signal, reactive(value[0])) : read(signal[mark]);
|
||||||
return createWrapObject(type, toSignal(signal, data));
|
return createWrapObject(type, toSignal(signal, data));
|
||||||
};
|
};
|
||||||
|
function toSignal(signal, value){
|
||||||
|
signal[mark]= {
|
||||||
|
value,
|
||||||
|
listeners: new Set()
|
||||||
|
};
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
const stack= [];
|
const stack= [];
|
||||||
export function watch(context){
|
export function watch(context){
|
||||||
stack.push(function contextReWatch(){
|
stack.push(function contextReWatch(){
|
||||||
@ -98,3 +142,12 @@ function write(signal, value){
|
|||||||
signal[mark].listeners.forEach(fn=> fn(value))
|
signal[mark].listeners.forEach(fn=> fn(value))
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
function valueOfSignal(signal){
|
||||||
|
return signal[mark].value;
|
||||||
|
}
|
||||||
|
export function addSignalListener(signal, listener){
|
||||||
|
return signal[mark].listeners.add(listener);
|
||||||
|
}
|
||||||
|
export function removeSignalListener(signal, listener){
|
||||||
|
return signal[mark].listeners.delete(listener);
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { S, el, on, off } from "../index.js";
|
import { el, on, off } from "../index.js";
|
||||||
|
import { S } from "../src/signals.js";
|
||||||
//import { empty, namespace, on, dispatch } from "../index.js";
|
//import { empty, namespace, on, dispatch } from "../index.js";
|
||||||
Object.assign(globalThis, { S, el, on, off });
|
Object.assign(globalThis, { S, el, on, off });
|
||||||
|
|
||||||
@ -39,8 +40,7 @@ function todosComponent({ todos= [] }= {}){
|
|||||||
return el("div", { className }).append(
|
return el("div", { className }).append(
|
||||||
el("div").append(
|
el("div").append(
|
||||||
el("h1", "Todos:"),
|
el("h1", "Todos:"),
|
||||||
elR(todos,
|
el("<>", todos, ts=> !ts.length
|
||||||
ts=> !ts.length
|
|
||||||
? el("p", "No todos yet")
|
? el("p", "No todos yet")
|
||||||
: ts.map((t, i)=> el(todoComponent, { textContent: t, value: i, className }, onremove)))
|
: ts.map((t, i)=> el(todoComponent, { textContent: t, value: i, className }, onremove)))
|
||||||
),
|
),
|
||||||
@ -64,22 +64,6 @@ function todoComponent({ textContent, className, value }){
|
|||||||
el("button", { type: "button", value, textContent: "-" })
|
el("button", { type: "button", value, textContent: "-" })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function elR(signal, map){
|
|
||||||
const mark= document.createComment("reactive");
|
|
||||||
const out= el("<>").append(mark);
|
|
||||||
let cache;
|
|
||||||
const toEls= v=> {
|
|
||||||
let els= map(v);
|
|
||||||
if(!Array.isArray(els))
|
|
||||||
els= [ els ];
|
|
||||||
if(cache) cache.forEach(el=> el.remove());
|
|
||||||
cache= els;
|
|
||||||
mark.before(...els);
|
|
||||||
};
|
|
||||||
on(signal, toEls);
|
|
||||||
toEls(signal());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
function createStyle(){
|
function createStyle(){
|
||||||
const element= el("style");
|
const element= el("style");
|
||||||
const store= new WeakSet();
|
const store= new WeakSet();
|
||||||
|
Loading…
Reference in New Issue
Block a user