1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-24 17:39:36 +01:00

💥 customElement (#11)

* 🎉

* Update customElement.js

* 💥 `observedAttributes`
This commit is contained in:
Jan Andrle 2024-01-05 16:49:05 +01:00 committed by GitHub
parent eb920f7bbd
commit e88a495525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 133 additions and 103 deletions

File diff suppressed because one or more lines are too long

29
dist/dde.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

View File

@ -121,7 +121,7 @@ document.head.append(
const interval= 5 * 1000; const interval= 5 * 1000;
setTimeout(clearInterval, 10*interval, setTimeout(clearInterval, 10*interval,
setInterval(()=> count(count()+1), interval)); setInterval(()=> count(count()+1), interval));
</code></div><script>Flems(document.getElementById("code-example-1-ehcq40v0h5k"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0);\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", O(()=> \\\"Currently: \\\"+count())),\\n\\tel(\\\"p\\\", { classList: { red: O(()=> count()%2) }, dataset: { count }, textContent: \\\"Attributes example\\\" })\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\".red { color: red; }\\\")\\n);\\n\\nconst interval= 5 * 1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(()=> count(count()+1), interval));\\n\"}],\"toolbar\":false}"));</script><p>To derived attribute based on value of observable variable just use the observable as a&nbsp;value of the attribute (<code>assign(element, { attribute: O('value') })</code>). <code>assign</code>/<code>el</code> provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in attributes <code>dataset</code>, <code>ariaset</code> and <code>classList</code>.</p><p>For computation, you can use the derived observable (see above) like <code>assign(element, { textContent: O(()=&gt; 'Hello '+WorldObservable()) })</code>.</p><p>To represent part of the template filled dynamically based on the observable value use <code>O.el(observable, DOMgenerator)</code>. This was already used in the todo example above or see:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js"; </code></div><script>Flems(document.getElementById("code-example-1-ehcq40v0h5k"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0);\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", O(()=> \\\"Currently: \\\"+count())),\\n\\tel(\\\"p\\\", { classList: { red: O(()=> count()%2) }, dataset: { count }, textContent: \\\"Attributes example\\\" })\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\".red { color: red; }\\\")\\n);\\n\\nconst interval= 5 * 1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(()=> count(count()+1), interval));\\n\"}],\"toolbar\":false}"));</script><p>To derived attribute based on value of observable variable just use the observable as a&nbsp;value of the attribute (<code>assign(element, { attribute: O('value') })</code>). <code>assign</code>/<code>el</code> provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in attributes <code>dataset</code>, <code>ariaset</code> and <code>classList</code>.</p><p>For computation, you can use the derived observable (see above) like <code>assign(element, { textContent: O(()=&gt; 'Hello '+WorldObservable()) })</code>. This is read-only observable its value is computed based on given function and updated when any observable used in the function changes.</p><p>To represent part of the template filled dynamically based on the observable value use <code>O.el(observable, DOMgenerator)</code>. This was already used in the todo example above or see:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><div id="code-example-2-8r8qappf8mo" class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const count= O(0, { const count= O(0, {
add(){ this.value= this.value + Math.round(Math.random()*10); } add(){ this.value= this.value + Math.round(Math.random()*10); }
}); });
@ -147,4 +147,4 @@ setTimeout(clearInterval, 10*interval, setInterval(function(){
O.action(count, "add"); O.action(count, "add");
O.action(numbers, "push", count()); O.action(numbers, "push", count());
}, interval)); }, interval));
</code></div><script>Flems(document.getElementById("code-example-2-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0, {\\n\\tadd(){ this.value= this.value + Math.round(Math.random()*10); }\\n});\\nconst numbers= O([ count() ], {\\n\\tpush(next){ this.value.push(next); }\\n});\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tO.el(count, count=> count%2\\n\\t\\t? el(\\\"p\\\", \\\"Last number is odd.\\\")\\n\\t\\t: el()\\n\\t),\\n\\tel(\\\"p\\\", \\\"Lucky numbers:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(numbers, numbers=> numbers.toReversed()\\n\\t\\t\\t.map(n=> el(\\\"li\\\", n)))\\n\\t)\\n);\\n\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval, setInterval(function(){\\n\\tO.action(count, \\\"add\\\");\\n\\tO.action(numbers, \\\"push\\\", count());\\n}, interval));\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(&lt;value&gt;)</code> — observable: reactive value</li><li><code>O(()=&gt; &lt;computation&gt;)</code> — observable: reactive value dependent on calculation using other observables</li><li><code>O.on(&lt;observable&gt;, &lt;listener&gt;[, &lt;options&gt;])</code> — listen to the observable value changes</li><li><code>O.clear(...&lt;observables&gt;)</code> — off and clear observables</li><li><code>O(&lt;value&gt;, &lt;actions&gt;)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(&lt;observable&gt;, &lt;action-name&gt;, ...&lt;action-arguments&gt;)</code> — invoke an&nbsp;action for given observable</li><li><code>O.el(&lt;observable&gt;, &lt;function-returning-dom&gt;)</code> — render partial dom structure (template) based on the current observable value</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><a rel="next" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Scopes and components</a></div></main></body></html> </code></div><script>Flems(document.getElementById("code-example-2-8r8qappf8mo"), JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0, {\\n\\tadd(){ this.value= this.value + Math.round(Math.random()*10); }\\n});\\nconst numbers= O([ count() ], {\\n\\tpush(next){ this.value.push(next); }\\n});\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tO.el(count, count=> count%2\\n\\t\\t? el(\\\"p\\\", \\\"Last number is odd.\\\")\\n\\t\\t: el()\\n\\t),\\n\\tel(\\\"p\\\", \\\"Lucky numbers:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(numbers, numbers=> numbers.toReversed()\\n\\t\\t\\t.map(n=> el(\\\"li\\\", n)))\\n\\t)\\n);\\n\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval, setInterval(function(){\\n\\tO.action(count, \\\"add\\\");\\n\\tO.action(numbers, \\\"push\\\", count());\\n}, interval));\\n\"}],\"toolbar\":false}"));</script><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(&lt;value&gt;)</code> — observable: reactive value</li><li><code>O(()=&gt; &lt;computation&gt;)</code>read-only observable: reactive value dependent on calculation using other observables</li><li><code>O.on(&lt;observable&gt;, &lt;listener&gt;[, &lt;options&gt;])</code> — listen to the observable value changes</li><li><code>O.clear(...&lt;observables&gt;)</code> — off and clear observables</li><li><code>O(&lt;value&gt;, &lt;actions&gt;)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(&lt;observable&gt;, &lt;action-name&gt;, ...&lt;action-arguments&gt;)</code> — invoke an&nbsp;action for given observable</li><li><code>O.el(&lt;observable&gt;, &lt;function-returning-dom&gt;)</code> — render partial dom structure (template) based on the current observable value</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p03-events" title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><a rel="next" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Scopes and components</a></div></main></body></html>

View File

@ -7,7 +7,7 @@ export function mnemonic(){
el("code", "O(<value>)"), " — observable: reactive value", el("code", "O(<value>)"), " — observable: reactive value",
), ),
el("li").append( el("li").append(
el("code", "O(()=> <computation>)"), " — observable: reactive value dependent on calculation using other observables", el("code", "O(()=> <computation>)"), " — read-only observable: reactive value dependent on calculation using other observables",
), ),
el("li").append( el("li").append(
el("code", "O.on(<observable>, <listener>[, <options>])"), " — listen to the observable value changes", el("code", "O.on(<observable>, <listener>[, <options>])"), " — listen to the observable value changes",

View File

@ -88,7 +88,9 @@ export function page({ pkg, info }){
el("code", "ariaset"), " and ", el("code", "classList"), "." el("code", "ariaset"), " and ", el("code", "classList"), "."
), ),
el("p").append( el("p").append(
"For computation, you can use the derived observable (see above) like ", el("code", "assign(element, { textContent: O(()=> 'Hello '+WorldObservable()) })"), "." "For computation, you can use the “derived observable” (see above) like ", el("code", "assign(element, { textContent: O(()=> 'Hello '+WorldObservable()) })"), ".",
" ",
"This is read-only observable its value is computed based on given function and updated when any observable used in the function changes."
), ),
el("p").append( el("p").append(
"To represent part of the template filled dynamically based on the observable value use ", el("code", "O.el(observable, DOMgenerator)"), ".", "To represent part of the template filled dynamically based on the observable value use ", el("code", "O.el(observable, DOMgenerator)"), ".",

View File

@ -1,4 +1,4 @@
import { style, el, O } from '../exports.js'; import { style, el, O, isObservable } from '../exports.js';
const className= style.host(thirdParty).css` const className= style.host(thirdParty).css`
:host { :host {
color: green; color: green;
@ -22,12 +22,13 @@ export function thirdParty(){
// Array.from((new URL(location)).searchParams.entries()) // Array.from((new URL(location)).searchParams.entries())
// .forEach(([ key, value ])=> O.action(store, "set", key, value)); // .forEach(([ key, value ])=> O.action(store, "set", key, value));
// O.on(store, data=> history.replaceState("", "", "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString())); // O.on(store, data=> history.replaceState("", "", "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString()));
useAdapter(store, store_adapter, { useStore(store_adapter, {
onread(data){ onread(data){
Array.from(data.entries()) Array.from(data.entries())
.forEach(([ key, value ])=> O.action(store, "set", key, value)); .forEach(([ key, value ])=> O.action(store, "set", key, value));
return store;
} }
}); })();
return el("input", { return el("input", {
className, className,
value: store().value(), value: store().value(),
@ -36,9 +37,14 @@ export function thirdParty(){
}); });
} }
function useAdapter(observable, adapter, { onread, onbeforewrite }= {}){ function useStore(adapter_in, { onread, onbeforewrite }= {}){
if(!onread) onread= observable; const adapter= typeof adapter_in === "function" ? { read: adapter_in } : adapter_in;
if(!onread) onread= O;
if(!onbeforewrite) onbeforewrite= data=> JSON.parse(JSON.stringify(data)); if(!onbeforewrite) onbeforewrite= data=> JSON.parse(JSON.stringify(data));
onread(adapter.read()); //TODO OK as synchronous return function useStoreInner(data_read){
O.on(observable, data=> adapter.write(onbeforewrite(data))); const observable= onread(adapter.read(data_read)); //TODO OK as synchronous
if(adapter.write && isObservable(observable))
O.on(observable, data=> adapter.write(onbeforewrite(data)));
return observable;
};
} }

View File

@ -13,24 +13,29 @@ export class CustomHTMLTestElement extends HTMLElement{
connectedCallback(){ connectedCallback(){
if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default"); if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default");
this.attachShadow({ mode: "open" }).append( this.attachShadow({ mode: "open" }).append(
customElementRender(this, this.render) customElementRender(this, this.render, this.attributes)
); );
} }
render({ test }){ attributes(element){
const observed= O.observedAttributes(element);
return Object.assign({ test: element.test }, observed);
}
render({ name, preName, test }){
console.log(scope.state); console.log(scope.state);
scope.host( scope.host(
on.connected(()=> console.log(CustomHTMLTestElement)), on.connected(()=> console.log(CustomHTMLTestElement)),
on.attributeChanged(e=> console.log(e)), on.attributeChanged(e=> console.log(e)),
on.disconnected(()=> console.log(CustomHTMLTestElement)) on.disconnected(()=> console.log(CustomHTMLTestElement))
); );
const name= O.attribute("name"); const text= text=> el().append(
const preName= O.attribute("pre-name"); el("#text", text),
" | "
console.log({ name, test, preName}); );
return el("p").append( return el("p").append(
el("#text", name), text(test),
el("#text", preName), text(name),
text(preName),
el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName("Ahoj") }) el("button", { type: "button", textContent: "pre-name", onclick: ()=> preName("Ahoj") })
); );
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.7.5", "version": "0.7.6",
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", "description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
"author": "Jan Andrle <andrle.jan@centrum.cz>", "author": "Jan Andrle <andrle.jan@centrum.cz>",
"license": "MIT", "license": "MIT",
@ -46,6 +46,7 @@
"browser": true, "browser": true,
"undef": "true", "undef": "true",
"latedef": "true", "latedef": "true",
"-W014": true,
"maxparams": 5, "maxparams": 5,
"maxdepth": 3, "maxdepth": 3,
"maxcomplexity": 14, "maxcomplexity": 14,
@ -58,7 +59,7 @@
"size-limit": [ "size-limit": [
{ {
"path": "./index.js", "path": "./index.js",
"limit": "9.75 kB", "limit": "9.85 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false

View File

@ -1,10 +1,11 @@
import { scope } from "./dom.js"; import { scope } from "./dom.js";
export function customElementRender(custom_element, render, props= custom_element){ export function customElementRender(custom_element, render, props= observedAttributes){
scope.push({ scope.push({
scope: custom_element, scope: custom_element,
host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element, host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element,
custom_element custom_element
}); });
if(typeof props==="function") props= props.call(custom_element, custom_element);
const out= render.call(custom_element, props); const out= render.call(custom_element, props);
scope.pop(); scope.pop();
return out; return out;
@ -31,3 +32,8 @@ export { lifecycleToEvents as customElementWithDDE };
function wrapMethod(obj, method, apply){ function wrapMethod(obj, method, apply){
obj[method]= new Proxy(obj[method] || (()=> {}), { apply }); obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
} }
import { observedAttributes as oA } from "./helpers.js";
export function observedAttributes(instance){
return oA(instance, (i, n)=> i.getAttribute(n));
}

View File

@ -84,6 +84,7 @@ on.attributeChanged= function(listener, options){
}); });
const c= onAbort(options.signal, ()=> observer.disconnect()); const c= onAbort(options.signal, ()=> observer.disconnect());
if(c) observer.observe(element, { attributes: true }); if(c) observer.observe(element, { attributes: true });
//TODO: clean up when element disconnected
return element; return element;
}; };
}; };

View File

@ -15,3 +15,12 @@ export function onAbort(signal, listener){
signal.removeEventListener("abort", listener); signal.removeEventListener("abort", listener);
}; };
} }
export function observedAttributes(instance, observedAttribute){
const { observedAttributes= [] }= instance.constructor;
return observedAttributes
.reduce(function(out, name){
Reflect.set(out, kebabToCamel(name), observedAttribute(instance, name));
return out;
}, {});
}
function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); }

View File

@ -9,7 +9,7 @@ const stack_watch= [];
/** /**
* ### `WeakMap<function, Set<ddeObservable<any, any>>>` * ### `WeakMap<function, Set<ddeObservable<any, any>>>`
* The `Set` is in the form of `[ source, ...depended observables (DSs) ]`. * The `Set` is in the form of `[ source, ...depended observables (DSs) ]`.
* When the DS is cleaned (`S.clear`) it is removed from DSs, * When the DS is cleaned (`O.clear`) it is removed from DSs,
* if remains only one (`source`) it is cleared too. * if remains only one (`source`) it is cleared too.
* ### `WeakMap<object, function>` * ### `WeakMap<object, function>`
* This is used for revesed deps, the `function` is also key for `deps`. * This is used for revesed deps, the `function` is also key for `deps`.
@ -18,16 +18,16 @@ const stack_watch= [];
const deps= new WeakMap(); const deps= new WeakMap();
export function observable(value, actions){ export function observable(value, actions){
if(typeof value!=="function") if(typeof value!=="function")
return create(value, actions); return create(false, value, actions);
if(isObservable(value)) return value; if(isObservable(value)) return value;
const out= create(); const out= create(true);
const contextReWatch= function(){ const contextReWatch= function(){
const [ origin, ...deps_old ]= deps.get(contextReWatch); const [ origin, ...deps_old ]= deps.get(contextReWatch);
deps.set(contextReWatch, new Set([ origin ])); deps.set(contextReWatch, new Set([ origin ]));
stack_watch.push(contextReWatch); stack_watch.push(contextReWatch);
out(value()); write(out, value());
stack_watch.pop(); stack_watch.pop();
if(!deps_old.length) return; if(!deps_old.length) return;
@ -113,41 +113,38 @@ observable.el= function(o, map){
return out; return out;
}; };
import { on } from "./events.js"; import { on } from "./events.js";
import { observedAttributes } from "./helpers.js";
function observedAttribute(instance, name){
const out= (...args)=> !args.length
? instance.getAttribute(name)
: instance.setAttribute(name, ...args);
out.attribute= name;
return out;
}
const key_attributes= "__dde_attributes"; const key_attributes= "__dde_attributes";
observable.attribute= function(name, initial= null){ observable.observedAttributes= function(element){
//TODO host=element & reuse existing const attrs= observedAttributes(element, observedAttribute);
const out= observable(initial); const store= element[key_attributes]= {};
let element; const actions= {
scope.host(el=> { _set(value){ this.value= value; },
element= el; };
if(elementAttribute(element, "has", name)) out(elementAttribute(element, "get", name)); Object.keys(attrs).forEach(name=> {
else if(initial!==null) elementAttribute(element, "set", name, initial); const attr= attrs[name]= toObservable(attrs[name], attrs[name](), actions);
store[attr.attribute]= attr;
if(el[key_attributes]){
el[key_attributes][name]= out;
return;
}
element[key_attributes]= { [name]: out };
on.attributeChanged(function attributeChangeToObservable({ detail }){
/*! This maps attributes to observables (`S.attribute`).
* Investigate `__dde_attributes` key of the element.*/
const [ name, value ]= detail;
const curr= element[key_attributes][name];
if(curr) return curr(value);
})(element);
on.disconnected(function(){
/*! This removes all observables mapped to attributes (`S.attribute`).
* Investigate `__dde_attributes` key of the element.*/
observable.clear(...Object.values(element[key_attributes]));
})(element);
});
return new Proxy(out, {
apply(target, _, args){
if(!args.length) return target();
const value= args[0];
return elementAttribute(element, "set", name, value);
}
}); });
on.attributeChanged(function attributeChangeToObservable({ detail }){
/*! This maps attributes to observables (`O.observedAttributes`).
* Investigate `__dde_attributes` key of the element.*/
const [ name, value ]= detail;
const curr= element[key_attributes][name];
if(curr) return observable.action(curr, "_set", value);
})(element);
on.disconnected(function(){
/*! This removes all observables mapped to attributes (`O.observedAttributes`).
* Investigate `__dde_attributes` key of the element.*/
observable.clear(...Object.values(element[key_attributes]));
})(element);
return attrs;
}; };
import { typeOf } from './helpers.js'; import { typeOf } from './helpers.js';
@ -169,7 +166,7 @@ function removeObservablesFromElements(o, listener, ...notes){
element[key_reactive]= []; element[key_reactive]= [];
on.disconnected(()=> on.disconnected(()=>
/*! /*!
* Clears all Observables listeners added in the current scope/host (`S.el`, `assign`, ?). * Clears all Observables listeners added in the current scope/host (`O.el`, `assign`, ?).
* You can investigate the `__dde_reactive` key of the element. * You can investigate the `__dde_reactive` key of the element.
* */ * */
element[key_reactive].forEach(([ [ o, listener ] ])=> element[key_reactive].forEach(([ [ o, listener ] ])=>
@ -180,9 +177,10 @@ function removeObservablesFromElements(o, listener, ...notes){
}); });
} }
function create(value, actions){ function create(is_readonly, value, actions){
const o= (...value)=> const o= is_readonly
value.length ? write(o, ...value) : read(o); ? ()=> read(o)
: (...value)=> value.length ? write(o, ...value) : read(o);
return toObservable(o, value, actions); return toObservable(o, value, actions);
} }
const protoSigal= Object.assign(Object.create(null), { const protoSigal= Object.assign(Object.create(null), {