mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-29 07:00:16 +02:00
⚡ Replace “observable” term with “signal” (#19)
* ⚡ refact docs to make editing (now renaming observables to signal) easier * ⚡ ⚡ use signal(s) term isntead of observable(s) * ⚡ 🔤 version + typo * 🐛 customElement example (0→S) * 📺 version in package-lock.json
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
export const mark= "__dde_observable";
|
||||
export const mark= "__dde_signal";
|
||||
import { hasOwn } from "./helpers.js";
|
||||
|
||||
export function isObservable(candidate){
|
||||
export function isSignal(candidate){
|
||||
try{ return hasOwn(candidate, mark); }
|
||||
catch(e){ return false; }
|
||||
}
|
||||
/** @type {function[]} */
|
||||
const stack_watch= [];
|
||||
/**
|
||||
* ### `WeakMap<function, Set<ddeObservable<any, any>>>`
|
||||
* The `Set` is in the form of `[ source, ...depended observables (DSs) ]`.
|
||||
* When the DS is cleaned (`O.clear`) it is removed from DSs,
|
||||
* ### `WeakMap<function, Set<ddeSignal<any, any>>>`
|
||||
* The `Set` is in the form of `[ source, ...depended signals (DSs) ]`.
|
||||
* When the DS is cleaned (`S.clear`) it is removed from DSs,
|
||||
* if remains only one (`source`) it is cleared too.
|
||||
* ### `WeakMap<object, function>`
|
||||
* This is used for revesed deps, the `function` is also key for `deps`.
|
||||
* @type {WeakMap<function|object,Set<ddeObservable<any, any>>|function>}
|
||||
* @type {WeakMap<function|object,Set<ddeSignal<any, any>>|function>}
|
||||
* */
|
||||
const deps= new WeakMap();
|
||||
export function observable(value, actions){
|
||||
export function signal(value, actions){
|
||||
if(typeof value!=="function")
|
||||
return create(false, value, actions);
|
||||
if(isObservable(value)) return value;
|
||||
if(isSignal(value)) return value;
|
||||
|
||||
const out= create(true);
|
||||
const contextReWatch= function(){
|
||||
@@ -33,9 +33,9 @@ export function observable(value, actions){
|
||||
|
||||
if(!deps_old.length) return;
|
||||
const deps_curr= deps.get(contextReWatch);
|
||||
for (const dep_observable of deps_old){
|
||||
if(deps_curr.has(dep_observable)) continue;
|
||||
removeObservableListener(dep_observable, contextReWatch);
|
||||
for (const dep_signal of deps_old){
|
||||
if(deps_curr.has(dep_signal)) continue;
|
||||
removeSignalListener(dep_signal, contextReWatch);
|
||||
}
|
||||
};
|
||||
deps.set(out[mark], contextReWatch);
|
||||
@@ -43,46 +43,46 @@ export function observable(value, actions){
|
||||
contextReWatch();
|
||||
return out;
|
||||
}
|
||||
export { observable as O };
|
||||
observable.action= function(o, name, ...a){
|
||||
const s= o[mark], { actions }= s;
|
||||
export { signal as S };
|
||||
signal.action= function(s, name, ...a){
|
||||
const M= s[mark], { actions }= M;
|
||||
if(!actions || !(name in actions))
|
||||
throw new Error(`'${o}' has no action with name '${name}'!`);
|
||||
actions[name].apply(s, a);
|
||||
if(s.skip) return (delete s.skip);
|
||||
s.listeners.forEach(l=> l(s.value));
|
||||
throw new Error(`'${s}' has no action with name '${name}'!`);
|
||||
actions[name].apply(M, a);
|
||||
if(M.skip) return (delete M.skip);
|
||||
M.listeners.forEach(l=> l(M.value));
|
||||
};
|
||||
observable.on= function on(o, listener, options= {}){
|
||||
signal.on= function on(s, listener, options= {}){
|
||||
const { signal: as }= options;
|
||||
if(as && as.aborted) return;
|
||||
if(Array.isArray(o)) return o.forEach(s=> on(s, listener, options));
|
||||
addObservableListener(o, listener);
|
||||
if(as) as.addEventListener("abort", ()=> removeObservableListener(o, listener));
|
||||
//TODO cleanup when observable removed
|
||||
if(Array.isArray(s)) return s.forEach(s=> on(s, listener, options));
|
||||
addSignalListener(s, listener);
|
||||
if(as) as.addEventListener("abort", ()=> removeSignalListener(s, listener));
|
||||
//TODO cleanup when signal removed
|
||||
};
|
||||
observable.symbols= {
|
||||
//observable: mark,
|
||||
onclear: Symbol.for("Observable.onclear")
|
||||
signal.symbols= {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
};
|
||||
observable.clear= function(...observables){
|
||||
for(const o of observables){
|
||||
const s= o[mark];
|
||||
if(!s) continue;
|
||||
delete o.toJSON;
|
||||
s.onclear.forEach(f=> f.call(s));
|
||||
clearListDeps(o, s);
|
||||
delete o[mark];
|
||||
signal.clear= function(...signals){
|
||||
for(const s of signals){
|
||||
const M= s[mark];
|
||||
if(!M) continue;
|
||||
delete s.toJSON;
|
||||
M.onclear.forEach(f=> f.call(M));
|
||||
clearListDeps(s, M);
|
||||
delete s[mark];
|
||||
}
|
||||
function clearListDeps(o, s){
|
||||
s.listeners.forEach(l=> {
|
||||
s.listeners.delete(l);
|
||||
function clearListDeps(s, o){
|
||||
o.listeners.forEach(l=> {
|
||||
o.listeners.delete(l);
|
||||
if(!deps.has(l)) return;
|
||||
|
||||
const ls= deps.get(l);
|
||||
ls.delete(o);
|
||||
ls.delete(s);
|
||||
if(ls.size>1) return;
|
||||
|
||||
o.clear(...ls);
|
||||
s.clear(...ls);
|
||||
deps.delete(l);
|
||||
});
|
||||
}
|
||||
@@ -92,7 +92,7 @@ import { enviroment as env } from "./dom-common.js";
|
||||
import { el } from "./dom.js";
|
||||
import { scope } from "./dom.js";
|
||||
// TODO: third argument for handle `cache_tmp` in re-render
|
||||
observable.el= function(o, map){
|
||||
signal.el= function(s, map){
|
||||
const mark_start= el.mark({ type: "reactive" }, true);
|
||||
const mark_end= mark_start.end;
|
||||
const out= env.D.createDocumentFragment();
|
||||
@@ -101,7 +101,7 @@ observable.el= function(o, map){
|
||||
let cache= {};
|
||||
const reRenderReactiveElement= v=> {
|
||||
if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasn’t yet rendered
|
||||
return removeObservableListener(o, reRenderReactiveElement);
|
||||
return removeSignalListener(s, reRenderReactiveElement);
|
||||
const cache_tmp= cache; // will be reused in the useCache or removed in the while loop on the end
|
||||
cache= {};
|
||||
scope.push(current);
|
||||
@@ -128,16 +128,16 @@ observable.el= function(o, map){
|
||||
if(mark_start.isConnected)
|
||||
requestCleanUpReactives(current.host());
|
||||
};
|
||||
addObservableListener(o, reRenderReactiveElement);
|
||||
removeObservablesFromElements(o, reRenderReactiveElement, mark_start, map);
|
||||
reRenderReactiveElement(o());
|
||||
addSignalListener(s, reRenderReactiveElement);
|
||||
removeSignalsFromElements(s, reRenderReactiveElement, mark_start, map);
|
||||
reRenderReactiveElement(s());
|
||||
return out;
|
||||
};
|
||||
function requestCleanUpReactives(host){
|
||||
if(!host || !host[key_reactive]) return;
|
||||
(requestIdleCallback || setTimeout)(function(){
|
||||
host[key_reactive]= host[key_reactive]
|
||||
.filter(([ o, el ])=> el.isConnected ? true : (removeObservableListener(...o), false));
|
||||
.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false));
|
||||
});
|
||||
}
|
||||
import { on } from "./events.js";
|
||||
@@ -147,49 +147,49 @@ const observedAttributeActions= {
|
||||
};
|
||||
function observedAttribute(store){
|
||||
return function(instance, name){
|
||||
const varO= (...args)=> !args.length
|
||||
? read(varO)
|
||||
const varS= (...args)=> !args.length
|
||||
? read(varS)
|
||||
: instance.setAttribute(name, ...args);
|
||||
const out= toObservable(varO, instance.getAttribute(name), observedAttributeActions);
|
||||
const out= toSignal(varS, instance.getAttribute(name), observedAttributeActions);
|
||||
store[name]= out;
|
||||
return out;
|
||||
};
|
||||
}
|
||||
const key_attributes= "__dde_attributes";
|
||||
observable.observedAttributes= function(element){
|
||||
signal.observedAttributes= function(element){
|
||||
const store= element[key_attributes]= {};
|
||||
const attrs= observedAttributes(element, observedAttribute(store));
|
||||
on.attributeChanged(function attributeChangeToObservable({ detail }){
|
||||
/*! This maps attributes to observables (`O.observedAttributes`).
|
||||
on.attributeChanged(function attributeChangeToSignal({ detail }){
|
||||
/*! This maps attributes to signals (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
const [ name, value ]= detail;
|
||||
const curr= this[key_attributes][name];
|
||||
if(curr) return observable.action(curr, "_set", value);
|
||||
if(curr) return signal.action(curr, "_set", value);
|
||||
})(element);
|
||||
on.disconnected(function(){
|
||||
/*! This removes all observables mapped to attributes (`O.observedAttributes`).
|
||||
/*! This removes all signals mapped to attributes (`S.observedAttributes`).
|
||||
* Investigate `__dde_attributes` key of the element.*/
|
||||
observable.clear(...Object.values(this[key_attributes]));
|
||||
signal.clear(...Object.values(this[key_attributes]));
|
||||
})(element);
|
||||
return attrs;
|
||||
};
|
||||
|
||||
import { typeOf } from './helpers.js';
|
||||
export const observables_config= {
|
||||
isObservable,
|
||||
export const signals_config= {
|
||||
isSignal,
|
||||
processReactiveAttribute(element, key, attrs, set){
|
||||
if(!isObservable(attrs)) return attrs;
|
||||
if(!isSignal(attrs)) return attrs;
|
||||
const l= attr=> {
|
||||
if(!element.isConnected)
|
||||
return removeObservableListener(attrs, l);
|
||||
return removeSignalListener(attrs, l);
|
||||
set(key, attr);
|
||||
};
|
||||
addObservableListener(attrs, l);
|
||||
removeObservablesFromElements(attrs, l, element, key);
|
||||
addSignalListener(attrs, l);
|
||||
removeSignalsFromElements(attrs, l, element, key);
|
||||
return attrs();
|
||||
}
|
||||
};
|
||||
function removeObservablesFromElements(o, listener, ...notes){
|
||||
function removeSignalsFromElements(s, listener, ...notes){
|
||||
const { current }= scope;
|
||||
if(current.prevent) return;
|
||||
current.host(function(element){
|
||||
@@ -197,29 +197,29 @@ function removeObservablesFromElements(o, listener, ...notes){
|
||||
element[key_reactive]= [];
|
||||
on.disconnected(()=>
|
||||
/*!
|
||||
* Clears all Observables listeners added in the current scope/host (`O.el`, `assign`, …?).
|
||||
* Clears all Signals listeners added in the current scope/host (`S.el`, `assign`, …?).
|
||||
* You can investigate the `__dde_reactive` key of the element.
|
||||
* */
|
||||
element[key_reactive].forEach(([ [ o, listener ] ])=>
|
||||
removeObservableListener(o, listener, o[mark] && o[mark].host && o[mark].host() === element))
|
||||
element[key_reactive].forEach(([ [ s, listener ] ])=>
|
||||
removeSignalListener(s, listener, s[mark] && s[mark].host && s[mark].host() === element))
|
||||
)(element);
|
||||
}
|
||||
element[key_reactive].push([ [ o, listener ], ...notes ]);
|
||||
element[key_reactive].push([ [ s, listener ], ...notes ]);
|
||||
});
|
||||
}
|
||||
|
||||
function create(is_readonly, value, actions){
|
||||
const varO= is_readonly
|
||||
? ()=> read(varO)
|
||||
: (...value)=> value.length ? write(varO, ...value) : read(varO);
|
||||
return toObservable(varO, value, actions, is_readonly);
|
||||
const varS= is_readonly
|
||||
? ()=> read(varS)
|
||||
: (...value)=> value.length ? write(varS, ...value) : read(varS);
|
||||
return toSignal(varS, value, actions, is_readonly);
|
||||
}
|
||||
const protoSigal= Object.assign(Object.create(null), {
|
||||
stopPropagation(){
|
||||
this.skip= true;
|
||||
}
|
||||
});
|
||||
class ObservableDefined extends Error{
|
||||
class SignalDefined extends Error{
|
||||
constructor(){
|
||||
super();
|
||||
const [ curr, ...rest ]= this.stack.split("\n");
|
||||
@@ -227,66 +227,66 @@ class ObservableDefined extends Error{
|
||||
this.stack= rest.find(l=> !l.includes(curr_file));
|
||||
}
|
||||
}
|
||||
function toObservable(o, value, actions, readonly= false){
|
||||
function toSignal(s, value, actions, readonly= false){
|
||||
const onclear= [];
|
||||
if(typeOf(actions)!=="[object Object]")
|
||||
actions= {};
|
||||
const { onclear: ocs }= observable.symbols;
|
||||
const { onclear: ocs }= signal.symbols;
|
||||
if(actions[ocs]){
|
||||
onclear.push(actions[ocs]);
|
||||
delete actions[ocs];
|
||||
}
|
||||
const { host }= scope;
|
||||
Reflect.defineProperty(o, mark, {
|
||||
Reflect.defineProperty(s, mark, {
|
||||
value: {
|
||||
value, actions, onclear, host,
|
||||
listeners: new Set(),
|
||||
defined: (new ObservableDefined()).stack,
|
||||
defined: (new SignalDefined()).stack,
|
||||
readonly
|
||||
},
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: true
|
||||
});
|
||||
o.toJSON= ()=> o();
|
||||
o.valueOf= ()=> o[mark] && o[mark].value;
|
||||
Object.setPrototypeOf(o[mark], protoSigal);
|
||||
return o;
|
||||
s.toJSON= ()=> s();
|
||||
s.valueOf= ()=> s[mark] && s[mark].value;
|
||||
Object.setPrototypeOf(s[mark], protoSigal);
|
||||
return s;
|
||||
}
|
||||
function currentContext(){
|
||||
return stack_watch[stack_watch.length - 1];
|
||||
}
|
||||
function read(o){
|
||||
if(!o[mark]) return;
|
||||
const { value, listeners }= o[mark];
|
||||
function read(s){
|
||||
if(!s[mark]) return;
|
||||
const { value, listeners }= s[mark];
|
||||
const context= currentContext();
|
||||
if(context) listeners.add(context);
|
||||
if(deps.has(context)) deps.get(context).add(o);
|
||||
if(deps.has(context)) deps.get(context).add(s);
|
||||
return value;
|
||||
}
|
||||
function write(o, value, force){
|
||||
if(!o[mark]) return;
|
||||
const s= o[mark];
|
||||
if(!force && s.value===value) return;
|
||||
s.value= value;
|
||||
s.listeners.forEach(l=> l(value));
|
||||
function write(s, value, force){
|
||||
if(!s[mark]) return;
|
||||
const M= s[mark];
|
||||
if(!force && M.value===value) return;
|
||||
M.value= value;
|
||||
M.listeners.forEach(l=> l(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
function addObservableListener(o, listener){
|
||||
if(!o[mark]) return;
|
||||
return o[mark].listeners.add(listener);
|
||||
function addSignalListener(s, listener){
|
||||
if(!s[mark]) return;
|
||||
return s[mark].listeners.add(listener);
|
||||
}
|
||||
function removeObservableListener(o, listener, clear_when_empty){
|
||||
const s= o[mark];
|
||||
if(!s) return;
|
||||
const out= s.listeners.delete(listener);
|
||||
if(clear_when_empty && !s.listeners.size){
|
||||
observable.clear(o);
|
||||
if(!deps.has(s)) return out;
|
||||
const c= deps.get(s);
|
||||
function removeSignalListener(s, listener, clear_when_empty){
|
||||
const M= s[mark];
|
||||
if(!M) return;
|
||||
const out= M.listeners.delete(listener);
|
||||
if(clear_when_empty && !M.listeners.size){
|
||||
signal.clear(s);
|
||||
if(!deps.has(M)) return out;
|
||||
const c= deps.get(M);
|
||||
if(!deps.has(c)) return out;
|
||||
deps.get(c).forEach(sig=> removeObservableListener(sig, c, true));
|
||||
deps.get(c).forEach(sig=> removeSignalListener(sig, c, true));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
Reference in New Issue
Block a user