; }
diff --git a/dist/esm.js b/dist/esm.js
index d5e0b15..450f448 100644
--- a/dist/esm.js
+++ b/dist/esm.js
@@ -1 +1 @@
-var m={isObservable(t){return!1},processReactiveAttribute(t,e,n,c){return n}};function q(t,e=!0){return e?Object.assign(m,t):(Object.setPrototypeOf(t,m),t)}function _(t){return m.isPrototypeOf(t)&&t!==m?t:m}function E(t){return typeof t>"u"}function w(t,e){if(!t||!(t instanceof AbortSignal))return!0;if(!t.aborted)return t.addEventListener("abort",e),function(){t.removeEventListener("abort",e)}}function T(t,e){let{observedAttributes:n=[]}=t.constructor;return n.reduce(function(c,r){return Reflect.set(c,H(r),e(t,r)),c},{})}function H(t){return t.replace(/-./g,e=>e[1].toUpperCase())}var a={setDeleteAttr:z,ssr:"",D:globalThis.document,F:globalThis.DocumentFragment,H:globalThis.HTMLElement,S:globalThis.SVGElement,M:globalThis.MutationObserver};function z(t,e,n){if(Reflect.set(t,e,n),!!E(n)){if(Reflect.deleteProperty(t,e),t instanceof a.H&&t.getAttribute(e)==="undefined")return t.removeAttribute(e);if(Reflect.get(t,e)==="undefined")return Reflect.set(t,e,"")}}var g=[{get scope(){return a.D.body},host:t=>t?t(a.D.body):a.D.body,custom_element:!1,prevent:!0}],v={get current(){return g[g.length-1]},get host(){return this.current.host},preventDefault(){let{current:t}=this;return t.prevent=!0,t},get state(){return[...g]},push(t={}){return g.push(Object.assign({},this.current,{prevent:!1},t))},pushRoot(){return g.push(g[0])},pop(){if(g.length!==1)return g.pop()}};function L(...t){return this.appendOriginal(...t),this}function B(t){return t.append===L||(t.appendOriginal=t.append,t.append=L),t}var C;function N(t,e,...n){let c=_(this),r=0,o,s;switch((Object(e)!==e||c.isObservable(e))&&(e={textContent:e}),!0){case typeof t=="function":{r=1,v.push({scope:t,host:(...b)=>b.length?(r===1?n.unshift(...b):b.forEach(h=>h(s)),void 0):s}),o=t(e||void 0);let d=o instanceof a.F;if(o.nodeName==="#comment")break;let l=N.mark({type:"component",name:t.name,host:d?"this":"parentElement"});o.prepend(l),d&&(s=l);break}case t==="#text":o=A.call(this,a.D.createTextNode(""),e);break;case(t==="<>"||!t):o=A.call(this,a.D.createDocumentFragment(),e);break;case!!C:o=A.call(this,a.D.createElementNS(C,t),e);break;case!o:o=A.call(this,a.D.createElement(t),e)}return B(o),s||(s=o),n.forEach(d=>d(s)),r&&v.pop(),r=2,o}function st(t,e=t,n=void 0){let c=Symbol.for("default"),r=Array.from(e.querySelectorAll("slot")).reduce((s,d)=>Reflect.set(s,d.name||c,d)&&s,{}),o=Reflect.has(r,c);if(t.append=new Proxy(t.append,{apply(s,d,l){if(!l.length)return t;let b=a.D.createDocumentFragment();for(let h of l){if(!h||!h.slot){o&&b.appendChild(h);continue}let i=h.slot,f=r[i];G(h,"remove","slot"),f&&(I(f,h,n),Reflect.deleteProperty(r,i))}return o&&(r[c].replaceWith(b),Reflect.deleteProperty(r,c)),t.append=s,t}}),t!==e){let s=Array.from(t.childNodes);s.forEach(d=>d.remove()),t.append(...s)}return e}function I(t,e,n){n&&n(t,e);try{t.replaceWith(A(e,{className:[e.className,t.className],dataset:{...t.dataset}}))}catch{t.replaceWith(e)}}N.mark=function(t,e=!1){t=Object.entries(t).map(([r,o])=>r+`="${o}"`).join(" ");let n=e?"":"/",c=a.D.createComment(``);return e&&(c.end=a.D.createComment("")),c};function it(t){let e=this;return function(...c){C=t;let r=N.call(e,...c);return C=void 0,r}}var D=new WeakMap,{setDeleteAttr:M}=a;function A(t,...e){if(!e.length)return t;D.set(t,F(t,this));for(let[n,c]of Object.entries(Object.assign({},...e)))j.call(this,t,n,c);return D.delete(t),t}function j(t,e,n){let{setRemoveAttr:c,s:r}=F(t,this),o=this;n=r.processReactiveAttribute(t,e,n,(d,l)=>j.call(o,t,d,l));let[s]=e;if(s==="=")return c(e.slice(1),n);if(s===".")return W(t,e.slice(1),n);if(/(aria|data)([A-Z])/.test(e))return e=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),c(e,n);switch(e==="className"&&(e="class"),e){case"xlink:href":return c(e,n,"http://www.w3.org/1999/xlink");case"textContent":return M(t,e,n);case"style":if(typeof n!="object")break;case"dataset":return S(r,n,W.bind(null,t[e]));case"ariaset":return S(r,n,(d,l)=>c("aria-"+d,l));case"classList":return Z.call(o,t,n)}return V(t,e)?M(t,e,n):c(e,n)}function F(t,e){if(D.has(t))return D.get(t);let c=(t instanceof a.S?K:J).bind(null,t,"Attribute"),r=_(e);return{setRemoveAttr:c,s:r}}function Z(t,e){let n=_(this);return S(n,e,(c,r)=>t.classList.toggle(c,r===-1?void 0:!!r)),t}function ft(t){return Array.from(t.children).forEach(e=>e.remove()),t}function G(t,e,n,c){return t instanceof a.H?t[e+"Attribute"](n,c):t[e+"AttributeNS"](null,n,c)}function V(t,e){if(!Reflect.has(t,e))return!1;let n=$(t,e);return!E(n.set)}function $(t,e){if(t=Object.getPrototypeOf(t),!t)return{};let n=Object.getOwnPropertyDescriptor(t,e);return n||$(t,e)}function S(t,e,n){if(!(typeof e!="object"||e===null))return Object.entries(e).forEach(function([r,o]){r&&(o=t.processReactiveAttribute(e,r,o,n),n(r,o))})}function k(t){return Array.isArray(t)?t.filter(Boolean).join(" "):t}function J(t,e,n,c){return t[(E(c)?"remove":"set")+e](n,k(c))}function K(t,e,n,c,r=null){return t[(E(c)?"remove":"set")+e+"NS"](r,n,k(c))}function W(t,e,n){if(Reflect.set(t,e,n),!!E(n))return Reflect.deleteProperty(t,e)}function pt(t,e,n=Q){v.push({scope:t,host:(...r)=>r.length?r.forEach(o=>o(t)):t,custom_element:t}),typeof n=="function"&&(n=n.call(t,t));let c=e.call(t,n);return v.pop(),c}function lt(t){for(let n of["connected","disconnected"])U(t.prototype,n+"Callback",function(c,r,o){c.apply(r,o),r.dispatchEvent(new Event("dde:"+n))});let e="attributeChanged";return U(t.prototype,e+"Callback",function(n,c,r){let[o,,s]=r;c.dispatchEvent(new CustomEvent("dde:"+e,{detail:[o,s]})),n.apply(c,r)}),t.prototype.__dde_lifecycleToEvents=!0,t}function U(t,e,n){t[e]=new Proxy(t[e]||(()=>{}),{apply:n})}function Q(t){return T(t,(e,n)=>e.getAttribute(n))}function vt(t,e,n){return e||(e={}),function(r,...o){n&&(o.unshift(r),r=typeof n=="function"?n():n);let s=o.length?new CustomEvent(t,Object.assign({detail:o[0]},e)):new Event(t,e);return r.dispatchEvent(s)}}function y(t,e,n){return function(r){return r.addEventListener(t,e,n),r}}var R=a.M?Y():new Proxy({},{get(){return()=>{}}}),X=new WeakSet;y.connected=function(t,e){let{custom_element:n}=v.current,c="connected";return typeof e!="object"&&(e={}),e.once=!0,function(o){n&&(o=n);let s="dde:"+c;return o.addEventListener(s,t,e),o.__dde_lifecycleToEvents?o:o.isConnected?(o.dispatchEvent(new Event(s)),o):(w(e.signal,()=>R.offConnected(o,t))&&R.onConnected(o,t),o)}};y.disconnected=function(t,e){let{custom_element:n}=v.current,c="disconnected";return typeof e!="object"&&(e={}),e.once=!0,function(o){n&&(o=n);let s="dde:"+c;return o.addEventListener(s,t,e),o.__dde_lifecycleToEvents||w(e.signal,()=>R.offDisconnected(o,t))&&R.onDisconnected(o,t),o}};var P=new WeakMap;y.disconnectedAsAbort=function(t){if(P.has(t))return P.get(t);let e=new AbortController;return P.set(t,e),t(y.disconnected(()=>e.abort())),e};y.attributeChanged=function(t,e){let n="attributeChanged";return typeof e!="object"&&(e={}),function(r){let o="dde:"+n;if(r.addEventListener(o,t,e),r.__dde_lifecycleToEvents||X.has(r)||!a.M)return r;let s=new a.M(function(l){for(let{attributeName:b,target:h}of l)h.dispatchEvent(new CustomEvent(o,{detail:[b,h.getAttribute(b)]}))});return w(e.signal,()=>s.disconnect())&&s.observe(r,{attributes:!0}),r}};function Y(){let t=new Map,e=!1,n=new a.M(function(i){for(let f of i)if(f.type==="childList"){if(b(f.addedNodes,!0)){s();continue}h(f.removedNodes,!0)&&s()}});return{onConnected(i,f){o();let u=r(i);u.connected.has(f)||(u.connected.add(f),u.length_c+=1)},offConnected(i,f){if(!t.has(i))return;let u=t.get(i);u.connected.has(f)&&(u.connected.delete(f),u.length_c-=1,c(i,u))},onDisconnected(i,f){o();let u=r(i);u.disconnected.has(f)||(u.disconnected.add(f),u.length_d+=1)},offDisconnected(i,f){if(!t.has(i))return;let u=t.get(i);u.disconnected.has(f)&&(u.disconnected.delete(f),u.length_d-=1,c(i,u))}};function c(i,f){f.length_c||f.length_d||(t.delete(i),s())}function r(i){if(t.has(i))return t.get(i);let f={connected:new WeakSet,length_c:0,disconnected:new WeakSet,length_d:0};return t.set(i,f),f}function o(){e||(e=!0,n.observe(a.D.body,{childList:!0,subtree:!0}))}function s(){!e||t.size||(e=!1,n.disconnect())}function d(){return new Promise(function(i){(requestIdleCallback||requestAnimationFrame)(i)})}async function l(i,f){t.size>30&&await d();let u=[];if(!(i instanceof Node))return u;for(let p of t.keys())p===i||!(p instanceof Node)||f(p)||i.contains(p)&&u.push(p);return u}function b(i,f){let u=!1;for(let p of i){if(f&&l(p,O=>!O.isConnectedd).then(b),!t.has(p))continue;let x=t.get(p);x.length_c&&(p.dispatchEvent(new Event("dde:connected")),x.connected=new WeakSet,x.length_c=0,x.length_d||t.delete(p),u=!0)}return u}function h(i,f){let u=!1;for(let p of i)f&&l(p,O=>O.isConnectedd).then(h),!(!t.has(p)||!t.get(p).length_d)&&(p.dispatchEvent(new Event("dde:disconnected")),t.delete(p),u=!0);return u}}export{A as assign,j as assignAttribute,B as chainableAppend,Z as classListDeclarative,N as createElement,it as createElementNS,pt as customElementRender,lt as customElementWithDDE,vt as dispatchEvent,N as el,it as elNS,G as elementAttribute,ft as empty,lt as lifecycleToEvents,Q as observedAttributes,y as on,q as registerReactivity,v as scope,st as simulateSlots};
+var y={isObservable(t){return!1},processReactiveAttribute(t,e,n,r){return n}};function H(t,e=!0){return e?Object.assign(y,t):(Object.setPrototypeOf(t,y),t)}function D(t){return y.isPrototypeOf(t)&&t!==y?t:y}function E(t){return typeof t>"u"}function R(t,e){if(!t||!(t instanceof AbortSignal))return!0;if(!t.aborted)return t.addEventListener("abort",e),function(){t.removeEventListener("abort",e)}}function M(t,e){let{observedAttributes:n=[]}=t.constructor;return n.reduce(function(r,o){return Reflect.set(r,z(o),e(t,o)),r},{})}function z(t){return t.replace(/-./g,e=>e[1].toUpperCase())}var a={setDeleteAttr:B,ssr:"",D:globalThis.document,F:globalThis.DocumentFragment,H:globalThis.HTMLElement,S:globalThis.SVGElement,M:globalThis.MutationObserver};function B(t,e,n){if(Reflect.set(t,e,n),!!E(n)){if(Reflect.deleteProperty(t,e),t instanceof a.H&&t.getAttribute(e)==="undefined")return t.removeAttribute(e);if(Reflect.get(t,e)==="undefined")return Reflect.set(t,e,"")}}var x="__dde_lifecycleToEvents";var g=[{get scope(){return a.D.body},host:t=>t?t(a.D.body):a.D.body,custom_element:!1,prevent:!0}],v={get current(){return g[g.length-1]},get host(){return this.current.host},preventDefault(){let{current:t}=this;return t.prevent=!0,t},get state(){return[...g]},push(t={}){return g.push(Object.assign({},this.current,{prevent:!1},t))},pushRoot(){return g.push(g[0])},pop(){if(g.length!==1)return g.pop()}};function k(...t){return this.appendOriginal(...t),this}function I(t){return t.append===k||(t.appendOriginal=t.append,t.append=k),t}var O;function L(t,e,...n){let r=D(this),o=0,c,i;switch((Object(e)!==e||r.isObservable(e))&&(e={textContent:e}),!0){case typeof t=="function":{o=1,v.push({scope:t,host:(...h)=>h.length?(o===1?n.unshift(...h):h.forEach(l=>l(i)),void 0):i}),c=t(e||void 0);let d=c instanceof a.F;if(c.nodeName==="#comment")break;let p=L.mark({type:"component",name:t.name,host:d?"this":"parentElement"});c.prepend(p),d&&(i=p);break}case t==="#text":c=A.call(this,a.D.createTextNode(""),e);break;case(t==="<>"||!t):c=A.call(this,a.D.createDocumentFragment(),e);break;case!!O:c=A.call(this,a.D.createElementNS(O,t),e);break;case!c:c=A.call(this,a.D.createElement(t),e)}return I(c),i||(i=c),n.forEach(d=>d(i)),o&&v.pop(),o=2,c}function it(t,e=t,n=void 0){let r=Symbol.for("default"),o=Array.from(e.querySelectorAll("slot")).reduce((i,d)=>Reflect.set(i,d.name||r,d)&&i,{}),c=Reflect.has(o,r);if(t.append=new Proxy(t.append,{apply(i,d,p){if(!p.length)return t;let h=a.D.createDocumentFragment();for(let l of p){if(!l||!l.slot){c&&h.appendChild(l);continue}let C=l.slot,s=o[C];V(l,"remove","slot"),s&&(Z(s,l,n),Reflect.deleteProperty(o,C))}return c&&(o[r].replaceWith(h),Reflect.deleteProperty(o,r)),t.append=i,t}}),t!==e){let i=Array.from(t.childNodes);i.forEach(d=>d.remove()),t.append(...i)}return e}function Z(t,e,n){n&&n(t,e);try{t.replaceWith(A(e,{className:[e.className,t.className],dataset:{...t.dataset}}))}catch{t.replaceWith(e)}}L.mark=function(t,e=!1){t=Object.entries(t).map(([o,c])=>o+`="${c}"`).join(" ");let n=e?"":"/",r=a.D.createComment(``);return e&&(r.end=a.D.createComment("")),r};function ut(t){let e=this;return function(...r){O=t;let o=L.call(e,...r);return O=void 0,o}}var _=new WeakMap,{setDeleteAttr:W}=a;function A(t,...e){if(!e.length)return t;_.set(t,F(t,this));for(let[n,r]of Object.entries(Object.assign({},...e)))q.call(this,t,n,r);return _.delete(t),t}function q(t,e,n){let{setRemoveAttr:r,s:o}=F(t,this),c=this;n=o.processReactiveAttribute(t,e,n,(d,p)=>q.call(c,t,d,p));let[i]=e;if(i==="=")return r(e.slice(1),n);if(i===".")return j(t,e.slice(1),n);if(/(aria|data)([A-Z])/.test(e))return e=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),r(e,n);switch(e==="className"&&(e="class"),e){case"xlink:href":return r(e,n,"http://www.w3.org/1999/xlink");case"textContent":return W(t,e,n);case"style":if(typeof n!="object")break;case"dataset":return N(o,n,j.bind(null,t[e]));case"ariaset":return N(o,n,(d,p)=>r("aria-"+d,p));case"classList":return G.call(c,t,n)}return J(t,e)?W(t,e,n):r(e,n)}function F(t,e){if(_.has(t))return _.get(t);let r=(t instanceof a.S?Q:K).bind(null,t,"Attribute"),o=D(e);return{setRemoveAttr:r,s:o}}function G(t,e){let n=D(this);return N(n,e,(r,o)=>t.classList.toggle(r,o===-1?void 0:!!o)),t}function ft(t){return Array.from(t.children).forEach(e=>e.remove()),t}function V(t,e,n,r){return t instanceof a.H?t[e+"Attribute"](n,r):t[e+"AttributeNS"](null,n,r)}function J(t,e){if(!Reflect.has(t,e))return!1;let n=$(t,e);return!E(n.set)}function $(t,e){if(t=Object.getPrototypeOf(t),!t)return{};let n=Object.getOwnPropertyDescriptor(t,e);return n||$(t,e)}function N(t,e,n){if(!(typeof e!="object"||e===null))return Object.entries(e).forEach(function([o,c]){o&&(c=t.processReactiveAttribute(e,o,c,n),n(o,c))})}function U(t){return Array.isArray(t)?t.filter(Boolean).join(" "):t}function K(t,e,n,r){return t[(E(r)?"remove":"set")+e](n,U(r))}function Q(t,e,n,r,o=null){return t[(E(r)?"remove":"set")+e+"NS"](o,n,U(r))}function j(t,e,n){if(Reflect.set(t,e,n),!!E(n))return Reflect.deleteProperty(t,e)}function ht(t,e,n=X){v.push({scope:t,host:(...o)=>o.length?o.forEach(c=>c(t)):t,custom_element:t}),typeof n=="function"&&(n=n.call(t,t));let r=e.call(t,n);return v.pop(),r}function bt(t){return T(t.prototype,"connectedCallback",function(e,n,r){e.apply(n,r),n.dispatchEvent(new Event("dde:connected"))}),T(t.prototype,"disconnectedCallback",function(e,n,r){e.apply(n,r),(queueMicrotask||setTimeout)(()=>!n.isConnected&&n.dispatchEvent(new Event("dde:disconnected")))}),T(t.prototype,"attributeChangedCallback",function(e,n,r){let[o,,c]=r;n.dispatchEvent(new CustomEvent("dde:attributeChanged",{detail:[o,c]})),e.apply(n,r)}),t.prototype[x]=!0,t}function T(t,e,n){t[e]=new Proxy(t[e]||(()=>{}),{apply:n})}function X(t){return M(t,(e,n)=>e.getAttribute(n))}function xt(t,e,n){return e||(e={}),function(o,...c){n&&(c.unshift(o),o=typeof n=="function"?n():n);let i=c.length?new CustomEvent(t,Object.assign({detail:c[0]},e)):new Event(t,e);return o.dispatchEvent(i)}}function w(t,e,n){return function(o){return o.addEventListener(t,e,n),o}}var S=a.M?tt():new Proxy({},{get(){return()=>{}}}),Y=new WeakSet;w.connected=function(t,e){let{custom_element:n}=v.current,r="connected";return typeof e!="object"&&(e={}),e.once=!0,function(c){n&&(c=n);let i="dde:"+r;return c.addEventListener(i,t,e),c[x]?c:c.isConnected?(c.dispatchEvent(new Event(i)),c):(R(e.signal,()=>S.offConnected(c,t))&&S.onConnected(c,t),c)}};w.disconnected=function(t,e){let{custom_element:n}=v.current,r="disconnected";return typeof e!="object"&&(e={}),e.once=!0,function(c){n&&(c=n);let i="dde:"+r;return c.addEventListener(i,t,e),c[x]||R(e.signal,()=>S.offDisconnected(c,t))&&S.onDisconnected(c,t),c}};var P=new WeakMap;w.disconnectedAsAbort=function(t){if(P.has(t))return P.get(t);let e=new AbortController;return P.set(t,e),t(w.disconnected(()=>e.abort())),e};w.attributeChanged=function(t,e){let n="attributeChanged";return typeof e!="object"&&(e={}),function(o){let c="dde:"+n;if(o.addEventListener(c,t,e),o[x]||Y.has(o)||!a.M)return o;let i=new a.M(function(p){for(let{attributeName:h,target:l}of p)l.dispatchEvent(new CustomEvent(c,{detail:[h,l.getAttribute(h)]}))});return R(e.signal,()=>i.disconnect())&&i.observe(o,{attributes:!0}),o}};function tt(){let t=new Map,e=!1,n=new a.M(function(s){for(let u of s)if(u.type==="childList"){if(h(u.addedNodes,!0)){i();continue}l(u.removedNodes,!0)&&i()}});return{onConnected(s,u){c();let f=o(s);f.connected.has(u)||(f.connected.add(u),f.length_c+=1)},offConnected(s,u){if(!t.has(s))return;let f=t.get(s);f.connected.has(u)&&(f.connected.delete(u),f.length_c-=1,r(s,f))},onDisconnected(s,u){c();let f=o(s);f.disconnected.has(u)||(f.disconnected.add(u),f.length_d+=1)},offDisconnected(s,u){if(!t.has(s))return;let f=t.get(s);f.disconnected.has(u)&&(f.disconnected.delete(u),f.length_d-=1,r(s,f))}};function r(s,u){u.length_c||u.length_d||(t.delete(s),i())}function o(s){if(t.has(s))return t.get(s);let u={connected:new WeakSet,length_c:0,disconnected:new WeakSet,length_d:0};return t.set(s,u),u}function c(){e||(e=!0,n.observe(a.D.body,{childList:!0,subtree:!0}))}function i(){!e||t.size||(e=!1,n.disconnect())}function d(){return new Promise(function(s){(requestIdleCallback||requestAnimationFrame)(s)})}async function p(s){t.size>30&&await d();let u=[];if(!(s instanceof Node))return u;for(let f of t.keys())f===s||!(f instanceof Node)||s.contains(f)&&u.push(f);return u}function h(s,u){let f=!1;for(let b of s){if(u&&p(b).then(h),!t.has(b))continue;let m=t.get(b);m.length_c&&(b.dispatchEvent(new Event("dde:connected")),m.connected=new WeakSet,m.length_c=0,m.length_d||t.delete(b),f=!0)}return f}function l(s,u){let f=!1;for(let b of s)u&&p(b).then(l),!(!t.has(b)||!t.get(b).length_d)&&((queueMicrotask||setTimeout)(C(b)),f=!0);return f}function C(s){return()=>{s.isConnected||(s.dispatchEvent(new Event("dde:disconnected")),t.delete(s))}}}export{A as assign,q as assignAttribute,I as chainableAppend,G as classListDeclarative,L as createElement,ut as createElementNS,ht as customElementRender,bt as customElementWithDDE,xt as dispatchEvent,L as el,ut as elNS,V as elementAttribute,ft as empty,bt as lifecycleToEvents,X as observedAttributes,w as on,H as registerReactivity,v as scope,it as simulateSlots};
diff --git a/docs/p03-events.html b/docs/p03-events.html
index cd6c5b4..c48c2a9 100644
--- a/docs/p03-events.html
+++ b/docs/p03-events.html
@@ -67,7 +67,7 @@ document.body.append(
function log({ type, detail }){
console.log({ _this: this, type, detail });
}
-For Custom elements, we will later introduce a way to replace *Callback
syntax with dde:*
events. The on.*
functions then listen to the appropriate Custom Elements events (see Custom element lifecycle callbacks | MDN).
But, in case of regular elemnets the MutationObserver
| MDN is internaly used to track these events. Therefore, there are some drawbacks:
- To proper listener registration, you need to use
on.*
not `on("dde:*", …)`! - Use sparingly! Internally, library must loop of all registered events and fires event properly. It is good practice to use the fact that if an element is removed, its children are also removed! In this spirit, we will introduce later the host syntax to register clean up procedures when the component is removed from the app.
# Final notes
The library also provides a method to dispatch the events.
import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
+
For Custom elements, we will later introduce a way to replace *Callback
syntax with dde:*
events. The on.*
functions then listen to the appropriate Custom Elements events (see Custom element lifecycle callbacks | MDN).
But, in case of regular elemnets the MutationObserver
| MDN is internaly used to track these events. Therefore, there are some drawbacks:
- To proper listener registration, you need to use
on.*
not `on("dde:*", …)`! - Use sparingly! Internally, library must loop of all registered events and fires event properly. It is good practice to use the fact that if an element is removed, its children are also removed! In this spirit, we will introduce later the host syntax to register clean up procedures when the component is removed from the app.
To provide intuitive behaviour, similar also to how the life-cycle events works in other frameworks/libraries, deka-dom-el library ensures that on.connected
and on.disconnected
are called only once and only when the element is (dis)connected to live DOM. The solution is inspired by Vue. For using native behaviour re-(dis)connecting element, use:
- custom
MutationObserver
or logic in (dis)
or… - re-add
on.connected
or on.disconnected
listeners.
# Final notes
The library also provides a method to dispatch the events.
import { el, on, dispatchEvent } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
document.body.append(
el("p", "Listenning to `test` event.", on("test", console.log)).append(
el("br"),
diff --git a/docs_src/p03-events.html.js b/docs_src/p03-events.html.js
index b0def3a..451617f 100644
--- a/docs_src/p03-events.html.js
+++ b/docs_src/p03-events.html.js
@@ -108,6 +108,17 @@ export function page({ pkg, info }){
" clean up procedures when the component is removed from the app."
),
),
+ el("p").append(
+ "To provide intuitive behaviour, similar also to how the life-cycle events works in other",
+ " frameworks/libraries, deka-dom-el library ensures that ", el("code", "on.connected"),
+ " and ", el("code", "on.disconnected"), " are called only once and only when the element",
+ " is (dis)connected to live DOM. The solution is inspired by ", el("a", { textContent: "Vue", href: "https://vuejs.org/guide/extras/web-components.html#lifecycle", title: "Vue and Web Components | Lifecycle" }), ".",
+ " For using native behaviour re-(dis)connecting element, use:"
+ ),
+ el("ul").append(
+ el("li").append("custom ", el("code", "MutationObserver"), " or logic in (dis)", el(code, "connectedCallback"), " or…"),
+ el("li").append("re-add ", el("code", "on.connected"), " or ", el("code", "on.disconnected"), " listeners.")
+ ),
el(h3, "Final notes"),
el("p", "The library also provides a method to dispatch the events."),
diff --git a/examples/components/todosComponent.js b/examples/components/todosComponent.js
index 333b138..da6d768 100644
--- a/examples/components/todosComponent.js
+++ b/examples/components/todosComponent.js
@@ -84,7 +84,7 @@ function todoComponent({ textContent, value }){
return el("li").append(
O.el(is_editable, is=> is
? el("input", { value: textContent(), type: "text" }, onedited)
- : el("span", { textContent, onclick: is_editable.bind(null, true) }),
+ : el("span", { textContent, onclick: is_editable.bind(null, true) })
),
el("button", { type: "button", value, textContent: "-" }, onclick)
);
diff --git a/index.d.ts b/index.d.ts
index e99ce12..51677c1 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,16 +1,11 @@
import { Observable } from "./observables";
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
-declare global {
- interface ddePublicElementTagNameMap{
- }
-}
type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
- | ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global {
type ddeComponentAttributes= Record | undefined;
type ddeElementAddon= (element: El)=> El | void;
@@ -48,7 +43,7 @@ export function classListDeclarative(element: El, c
export function assign(element: El, ...attrs_array: ElementAttributes[]): El
export function assignAttribute>(element: El, attr: ATT, value: ElementAttributes[ATT]): ElementAttributes[ATT]
-type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap & ddePublicElementTagNameMap
+type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable` leads to `attrs?: any`
export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap & string,
@@ -187,9 +182,10 @@ export function customElementWithDDE(custom_element: EL)
export function lifecycleToEvents(custom_element: EL): EL
export function observedAttributes(custom_element: HTMLElement): Record
-/* TypeScript MEH // TODO for SVG */
-type ddeAppend= (...nodes: (Node | string)[])=> el;
+/* TypeScript MEH */
declare global{
+ type ddeAppend= (...nodes: (Node | string)[])=> el;
+
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend; }
diff --git a/package.json b/package.json
index 0ae939c..0cb953f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "deka-dom-el",
- "version": "0.7.7",
+ "version": "0.7.8",
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
"author": "Jan Andrle ",
"license": "MIT",
@@ -59,18 +59,24 @@
"size-limit": [
{
"path": "./index.js",
- "limit": "10 kB",
+ "limit": "10.5 kB",
"gzip": false,
"brotli": false
},
{
"path": "./observables.js",
- "limit": "11.5 kB",
+ "limit": "12 kB",
"gzip": false,
"brotli": false
},
+ {
+ "path": "./index-with-observables.js",
+ "limit": "15 kB",
+ "gzip": false,
+ "brotli": false
+ },
{
"path": "./index-with-observables.js",
"limit": "5 kB"
diff --git a/src/customElement.js b/src/customElement.js
index 1267c73..c3f6c40 100644
--- a/src/customElement.js
+++ b/src/customElement.js
@@ -1,3 +1,4 @@
+import { keyLTE } from "./dom-common.js";
import { scope } from "./dom.js";
export function customElementRender(custom_element, render, props= observedAttributes){
scope.push({
@@ -11,20 +12,24 @@ export function customElementRender(custom_element, render, props= observedAttri
return out;
}
export function lifecycleToEvents(class_declaration){
- for (const name of [ "connected", "disconnected" ])
- wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
- target.apply(thisArg, detail);
- thisArg.dispatchEvent(new Event("dde:"+name));
- });
- const name= "attributeChanged";
- wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
+ wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail){
+ target.apply(thisArg, detail);
+ thisArg.dispatchEvent(new Event("dde:connected"));
+ });
+ wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail){
+ target.apply(thisArg, detail);
+ (queueMicrotask || setTimeout)(
+ ()=> !thisArg.isConnected && thisArg.dispatchEvent(new Event("dde:disconnected"))
+ );
+ });
+ wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail){
const [ attribute, , value ]= detail;
- thisArg.dispatchEvent(new CustomEvent("dde:"+name, {
+ thisArg.dispatchEvent(new CustomEvent("dde:attributeChanged", {
detail: [ attribute, value ]
}));
target.apply(thisArg, detail);
});
- class_declaration.prototype.__dde_lifecycleToEvents= true;
+ class_declaration.prototype[keyLTE]= true;
return class_declaration;
}
export { lifecycleToEvents as customElementWithDDE };
diff --git a/src/dom-common.js b/src/dom-common.js
index fa7d2a0..73dbe6c 100644
--- a/src/dom-common.js
+++ b/src/dom-common.js
@@ -26,3 +26,4 @@ function setDeleteAttr(obj, prop, val){
if(Reflect.get(obj, prop)==="undefined")
return Reflect.set(obj, prop, "");
}
+export const keyLTE= "__dde_lifecycleToEvents"; //boolean
diff --git a/src/events.js b/src/events.js
index 0d6cbd3..03d7465 100644
--- a/src/events.js
+++ b/src/events.js
@@ -1,5 +1,5 @@
export { registerReactivity } from './observables-common.js';
-import { enviroment as env } from './dom-common.js';
+import { enviroment as env, keyLTE } from './dom-common.js';
export function dispatchEvent(name, options, host){
if(!options) options= {};
@@ -26,6 +26,7 @@ const els_attribute_store= new WeakSet();
import { scope } from "./dom.js";
import { onAbort } from './helpers.js';
//TODO: cleanUp when event before abort?
+//TODO: docs (e.g.) https://nolanlawson.com/2024/01/13/web-component-gotcha-constructor-vs-connectedcallback/
on.connected= function(listener, options){
const { custom_element }= scope.current;
const name= "connected";
@@ -36,7 +37,7 @@ on.connected= function(listener, options){
if(custom_element) element= custom_element;
const event= "dde:"+name;
element.addEventListener(event, listener, options);
- if(element.__dde_lifecycleToEvents) return element;
+ if(element[keyLTE]) return element;
if(element.isConnected) return ( element.dispatchEvent(new Event(event)), element );
const c= onAbort(options.signal, ()=> c_ch_o.offConnected(element, listener));
@@ -54,7 +55,7 @@ on.disconnected= function(listener, options){
if(custom_element) element= custom_element;
const event= "dde:"+name;
element.addEventListener(event, listener, options);
- if(element.__dde_lifecycleToEvents) return element;
+ if(element[keyLTE]) return element;
const c= onAbort(options.signal, ()=> c_ch_o.offDisconnected(element, listener));
if(c) c_ch_o.onDisconnected(element, listener);
@@ -77,7 +78,7 @@ on.attributeChanged= function(listener, options){
return function registerElement(element){
const event= "dde:"+name;
element.addEventListener(event, listener, options);
- if(element.__dde_lifecycleToEvents || els_attribute_store.has(element))
+ if(element[keyLTE] || els_attribute_store.has(element))
return element;
if(!env.M) return element;
@@ -171,13 +172,13 @@ function connectionsChangesObserverConstructor(){
function requestIdle(){ return new Promise(function(resolve){
(requestIdleCallback || requestAnimationFrame)(resolve);
}); }
- async function collectChildren(element, filter){
+ async function collectChildren(element){
if(store.size > 30)//TODO limit?
await requestIdle();
const out= [];
if(!(element instanceof Node)) return out;
for(const el of store.keys()){
- if(el===element || !(el instanceof Node) || filter(el)) continue;
+ if(el===element || !(el instanceof Node)) continue;
if(element.contains(el))
out.push(el);
}
@@ -186,7 +187,7 @@ function connectionsChangesObserverConstructor(){
function observerAdded(addedNodes, is_root){
let out= false;
for(const element of addedNodes){
- if(is_root) collectChildren(element, el=> !el.isConnectedd).then(observerAdded);
+ if(is_root) collectChildren(element).then(observerAdded);
if(!store.has(element)) continue;
const ls= store.get(element);
@@ -203,17 +204,21 @@ function connectionsChangesObserverConstructor(){
function observerRemoved(removedNodes, is_root){
let out= false;
for(const element of removedNodes){
- if(is_root) collectChildren(element, el=> el.isConnectedd).then(observerRemoved);
+ if(is_root) collectChildren(element).then(observerRemoved);
if(!store.has(element)) continue;
const ls= store.get(element);
if(!ls.length_d) continue;
-
- element.dispatchEvent(new Event("dde:disconnected"));
-
- store.delete(element);
+ (queueMicrotask || setTimeout)(dispatchRemove(element));
out= true;
}
return out;
}
+ function dispatchRemove(element){
+ return ()=> {
+ if(element.isConnected) return;
+ element.dispatchEvent(new Event("dde:disconnected"));
+ store.delete(element);
+ };
+ }
}
diff --git a/src/observables-lib.js b/src/observables-lib.js
index 21538f9..28b3618 100644
--- a/src/observables-lib.js
+++ b/src/observables-lib.js
@@ -96,7 +96,7 @@ observable.el= function(o, map){
out.append(mark_start, mark_end);
const { current }= scope;
const reRenderReactiveElement= v=> {
- if(!mark_start.parentNode || !mark_end.parentNode)
+ if(!mark_start.parentNode || !mark_end.parentNode) // isConnected or wasn’t yet rendered
return removeObservableListener(o, reRenderReactiveElement);
scope.push(current);
let els= map(v);
@@ -107,12 +107,21 @@ observable.el= function(o, map){
while(( el_r= mark_start.nextSibling ) !== mark_end)
el_r.remove();
mark_start.after(...els);
+ if(mark_start.isConnected)
+ requestCleanUpReactives(current.host());
};
addObservableListener(o, reRenderReactiveElement);
removeObservablesFromElements(o, reRenderReactiveElement, mark_start, map);
reRenderReactiveElement(o());
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));
+ });
+}
import { on } from "./events.js";
import { observedAttributes } from "./helpers.js";
const observedAttributeActions= {
@@ -152,7 +161,11 @@ export const observables_config= {
isObservable,
processReactiveAttribute(element, key, attrs, set){
if(!isObservable(attrs)) return attrs;
- const l= attr=> set(key, attr);
+ const l= attr=> {
+ if(!element.isConnected)
+ return removeObservableListener(attrs, l);
+ set(key, attr);
+ };
addObservableListener(attrs, l);
removeObservablesFromElements(attrs, l, element, key);
return attrs();