diff --git a/src/events.js b/src/events.js index 92c8186..9350a31 100644 --- a/src/events.js +++ b/src/events.js @@ -16,13 +16,23 @@ import { onAbort } from './helpers.js'; //TODO: cleanUp when event before abort? on.connected= function(listener, options){ return function registerElement(element){ + if(typeof element.connectedCallback === "function"){ + element.addEventListener("dde:connected", listener, options); + return element; + } const c= onAbort(options && options.signal, ()=> c_ch_o.offConnected(element, listener)); - if(c) c_ch_o.onConnected(element, listener); + if(!c) return element; + if(element.isConnected) listener(new Event("dde:connected")); + else c_ch_o.onConnected(element, listener); return element; }; }; on.disconnected= function(listener, options){ return function registerElement(element){ + if(typeof element.disconnectedCallback === "function"){ + element.addEventListener("dde:disconnected", listener, options); + return element; + } const c= onAbort(options && options.signal, ()=> c_ch_o.offDisconnected(element, listener)); if(c) c_ch_o.onDisconnected(element, listener); return element; diff --git a/test/components/webComponent.js b/test/components/webComponent.js index 2e1c9b7..bbb9630 100644 --- a/test/components/webComponent.js +++ b/test/components/webComponent.js @@ -1,49 +1,112 @@ import { el } from "../../index.js"; import { S } from "../../src/signals.js"; +const store= new WeakMap(); Object.assign(S, { customElementParams(_this){ - console.log("zde"); - return getAttributes(_this); + const observedProperties= store.get(_this.constructor).observedProperties; + observedProperties.forEach(p=> _this[p]); + return store.has(_this) ? store.get(_this) : getAttributes(_this); }, customElementPrototype(cls){ } }); -const store= new WeakMap(); /** * Compatible with `npx-wca test/components/webComponent.js` - * @prop {string} test * */ -class CustomHTMLTestElement extends HTMLElement{ - static get tagName(){ - return "custom-test"; - } +export class CustomHTMLTestElement extends HTMLElement{ static get observedAttributes(){ return [ "name" ]; } - constructor(){ - super(); - customElementInit(this, this.attachShadow({ mode: "open" })); - } connectedCallback(){ - customElementRender(this, render); + customElementRender(this, this.attachShadow({ mode: "open" }), this.render); + } + + render({ name, test }, host){ + host(on.connected(console.log)); + return el("p", { className: test, textContent: name }); } } -S.customElementPrototype(CustomHTMLTestElement); -customElements.define(CustomHTMLTestElement.tagName, CustomHTMLTestElement); +customElementsAssign( + CustomHTMLTestElement, + reflectObservedAttributes, + lifecycleToEvents(true), + attrsPropsToSignals([ "test" ]) +); +customElements.define("custom-test", CustomHTMLTestElement); -function render({ name }, host){ - return el("p", name); -} -function customElementInit(_this, root= _this){ +function customElementRender(_this, root, render){ const host= (...a)=> a.length ? a[0](_this) : _this; - store.set(_this, { host, root }); -} -function customElementRender(_this, render){ - const { host, root }= store.get(_this); const attrs= S.customElementParams ? S.customElementParams(_this) : getAttributes(_this); root.appendChild(render(attrs, host)); } function getAttributes(_this){ return Object.fromEntries(_this.getAttributeNames().map(n=> [ n, _this.getAttribute(n) ])); } +/** @returns {HTMLElement} */ +function customElementsAssign(class_base, ...automatize){ + automatize.forEach(a=> a(class_base, getStore)); + function getStore(t){ + if(store.has(t)) return store.get(t); + const s= {}; + store.set(t, s); + return s; + } +} +function reflectObservedAttributes(c){ + for(const name of c.observedAttributes) + Reflect.defineProperty(c.prototype, name, { + get(){ return this.getAttribute(name); }, + set(value){ this.setAttribute(name, value); } + }); +} +function lifecycleToEvents(is_attrs){ + return function(c){ + wrapMethod(c.prototype, "connectedCallback", function(target, thisArg, detail){ + target.apply(thisArg, detail); + thisArg.dispatchEvent(new Event("dde:connected")); + }); + wrapMethod(c.prototype, "disconnectedCallback", function(target, thisArg, detail){ + target.apply(thisArg, detail); + thisArg.dispatchEvent(new Event("dde:disconnected")); + }); + if(is_attrs) + wrapMethod(c.prototype, "attributeChangedCallback", function(target, thisArg, detail){ + thisArg.dispatchEvent(new CustomEvent("dde:attribute", { detail })); + target.apply(thisArg, detail); + }); + }; +} +function attrsPropsToSignals(props= []){ + return function(c, getStore){ + const store= getStore(c); + store.observedProperties= props; + wrapMethod(c.prototype, "attributeChangedCallback", function(target, thisArg, detail){ + const [ name, _, value ]= detail; + const s= getStore(thisArg); + if(s[name]) s[name](value); + else s[name]= S(value); + + target.apply(thisArg, detail); + }); + for(const name of props){ + Reflect.defineProperty(c.prototype, name, { + get(){ + const s= getStore(this); + if(s[name]) return s[name](); + const out= S(undefined); + s[name]= out; + return out(); + }, + set(value){ + const s= getStore(this); + if(s[name]) s[name](value); + else s[name]= S(value); + } + }); + } + }; +} +function wrapMethod(obj, method, apply){ + obj[method]= new Proxy(obj[method] || (()=> {}), { apply }); +} diff --git a/test/index.js b/test/index.js index 3e01347..4bfe947 100644 --- a/test/index.js +++ b/test/index.js @@ -2,11 +2,11 @@ import { style, el } from './exports.js'; document.head.append(style.element); import { fullNameComponent } from './components/fullNameComponent.js'; import { todosComponent } from './components/todosComponent.js'; -import "./components/webComponent.js"; +import { CustomHTMLTestElement } from "./components/webComponent.js"; document.body.append( el("h1", "Experiments:"), el(fullNameComponent), el(todosComponent), - el("custom-test", { name: "attr" }) + el(customElements.getName(CustomHTMLTestElement), { name: "attr" }) );