diff --git a/docs_src/components/mnemonic/observables-init.js b/docs_src/components/mnemonic/observables-init.js index d05b100..2dea569 100644 --- a/docs_src/components/mnemonic/observables-init.js +++ b/docs_src/components/mnemonic/observables-init.js @@ -7,7 +7,7 @@ export function mnemonic(){ el("code", "O()"), " — observable: reactive value", ), el("li").append( - el("code", "O(()=> )"), " — observable: reactive value dependent on calculation using other observables", + el("code", "O(()=> )"), " — read-only observable: reactive value dependent on calculation using other observables", ), el("li").append( el("code", "O.on(, [, ])"), " — listen to the observable value changes", diff --git a/docs_src/p04-observables.html.js b/docs_src/p04-observables.html.js index 90940a6..d6c0753 100644 --- a/docs_src/p04-observables.html.js +++ b/docs_src/p04-observables.html.js @@ -88,7 +88,9 @@ export function page({ pkg, info }){ el("code", "ariaset"), " and ", el("code", "classList"), "." ), 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( "To represent part of the template filled dynamically based on the observable value use ", el("code", "O.el(observable, DOMgenerator)"), ".", diff --git a/examples/components/3rd-party.js b/examples/components/3rd-party.js index 722ef5f..332ba35 100644 --- a/examples/components/3rd-party.js +++ b/examples/components/3rd-party.js @@ -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` :host { color: green; @@ -22,12 +22,13 @@ export function thirdParty(){ // Array.from((new URL(location)).searchParams.entries()) // .forEach(([ key, value ])=> O.action(store, "set", key, value)); // O.on(store, data=> history.replaceState("", "", "?"+(new URLSearchParams(JSON.parse(JSON.stringify(data)))).toString())); - useAdapter(store, store_adapter, { + useStore(store_adapter, { onread(data){ Array.from(data.entries()) .forEach(([ key, value ])=> O.action(store, "set", key, value)); + return store; } - }); + })(); return el("input", { className, value: store().value(), @@ -36,9 +37,14 @@ export function thirdParty(){ }); } -function useAdapter(observable, adapter, { onread, onbeforewrite }= {}){ - if(!onread) onread= observable; +function useStore(adapter_in, { onread, onbeforewrite }= {}){ + const adapter= typeof adapter_in === "function" ? { read: adapter_in } : adapter_in; + if(!onread) onread= O; if(!onbeforewrite) onbeforewrite= data=> JSON.parse(JSON.stringify(data)); - onread(adapter.read()); //TODO OK as synchronous - O.on(observable, data=> adapter.write(onbeforewrite(data))); + return function useStoreInner(data_read){ + 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; + }; } diff --git a/examples/components/webComponent.js b/examples/components/webComponent.js index 00d2b8f..f1c994c 100644 --- a/examples/components/webComponent.js +++ b/examples/components/webComponent.js @@ -12,12 +12,13 @@ export class CustomHTMLTestElement extends HTMLElement{ } connectedCallback(){ if(!this.hasAttribute("pre-name")) this.setAttribute("pre-name", "default"); + console.log(observedAttributes(this)); this.attachShadow({ mode: "open" }).append( customElementRender(this, this.render) ); } - render({ test }){ + render(test){ console.log(scope.state); scope.host( on.connected(()=> console.log(CustomHTMLTestElement)), diff --git a/src/customElement.js b/src/customElement.js index e0c117b..80d3881 100644 --- a/src/customElement.js +++ b/src/customElement.js @@ -1,10 +1,11 @@ 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: custom_element, host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element, custom_element }); + if(typeof props==="function") props= props(custom_element); const out= render.call(custom_element, props); scope.pop(); return out; @@ -31,3 +32,17 @@ export { lifecycleToEvents as customElementWithDDE }; function wrapMethod(obj, method, apply){ obj[method]= new Proxy(obj[method] || (()=> {}), { apply }); } + +function observedAttribute(instance, name){ + const out= (...args)=> !args.length + ? instance.getAttribute(name) + : instance.setAttribute(name, ...args); + out.attribute= name; + return out; +} +export function observedAttributes(instance){ + const { observedAttributes= [] }= instance.constructor; + return observedAttributes + .map(name=> [ name.replace(/-./g, x=> x[1].toUpperCase()), name ]) + .reduce((out, [ key, name ])=> ( Reflect.set(out, key, observedAttribute(instance, name)), out ), {}); +} diff --git a/src/observables-lib.js b/src/observables-lib.js index 53ddc0f..58b621d 100644 --- a/src/observables-lib.js +++ b/src/observables-lib.js @@ -18,16 +18,16 @@ const stack_watch= []; const deps= new WeakMap(); export function observable(value, actions){ if(typeof value!=="function") - return create(value, actions); + return create(false, value, actions); if(isObservable(value)) return value; - const out= create(); + const out= create(true); const contextReWatch= function(){ const [ origin, ...deps_old ]= deps.get(contextReWatch); deps.set(contextReWatch, new Set([ origin ])); stack_watch.push(contextReWatch); - out(value()); + write(out, value()); stack_watch.pop(); if(!deps_old.length) return; @@ -180,9 +180,10 @@ function removeObservablesFromElements(o, listener, ...notes){ }); } -function create(value, actions){ - const o= (...value)=> - value.length ? write(o, ...value) : read(o); +function create(is_readonly, value, actions){ + const o= is_readonly + ? ()=> read(o) + : (...value)=> value.length ? write(o, ...value) : read(o); return toObservable(o, value, actions); } const protoSigal= Object.assign(Object.create(null), {