From 994c378bda95b4061e67f93cadc07f2efc799dd7 Mon Sep 17 00:00:00 2001 From: Mendel Greenberg Date: Fri, 24 Mar 2023 14:44:47 -0400 Subject: [PATCH] implement live-throttle --- README.md | 5 +++- web/browser/auto.js | 2 +- web/browser/auto.js.map | 4 +-- web/src/events.ts | 62 ++++++++++++++++++++++++++++------------- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 83a944d..fa5b0d5 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ See the [form example](https://github.com/jfyne/live-examples/tree/main/todo) fo ### Rate Limiting - [x] live-debounce -- [ ] live-throttle +- [x] live-throttle All events can be rate-limited on the client by using the `live-debounce` and `live-throttle` bindings, with the following behavior: @@ -311,6 +311,9 @@ used for input elements. throttle will immediately emit the event, then rate limit it at once per provided timeout. Throttling is typically used to rate limit clicks, mouse and keyboard actions. +> Note: If `live-debounce` and `live-throttle` are used on the same element, +> `live-debounce` will be used first, and then `live-throttle`. + ### Dom Patching - [x] live-update diff --git a/web/browser/auto.js b/web/browser/auto.js index 0f09071..91d6be9 100644 --- a/web/browser/auto.js +++ b/web/browser/auto.js @@ -1,2 +1,2 @@ -(()=>{var z=Object.defineProperty;var x=Object.getOwnPropertySymbols;var _=Object.prototype.hasOwnProperty,j=Object.prototype.propertyIsEnumerable;var M=(i,t,e)=>t in i?z(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,h=(i,t)=>{for(var e in t||(t={}))_.call(t,e)&&M(i,e,t[e]);if(x)for(var e of x(t))j.call(t,e)&&M(i,e,t[e]);return i};var y=class{static hook(t){return t.getAttribute===void 0?null:t.getAttribute("live-hook")}};var J="live:mounted",X="live:beforeupdate",Q="live:updated",V="live:beforedestroy",Y="live:destroyed",Z="live:disconnected",tt="live:reconnected",A="live-connected",T="live-disconnected",et="live-error",k=class{constructor(t,e,s){this.typ=t,this.data=e,s!==void 0?this.id=s:this.id=0}static GetID(){return this.sequence++}serialize(){return JSON.stringify({t:this.typ,i:this.id,d:this.data})}static fromMessage(t){let e=JSON.parse(t);return new k(e.t,e.d,e.i)}},o=k;o.sequence=1;var a=class{constructor(){}static init(t,e){this.hooks=t,this.dom=e,this.eventHandlers={}}static handleEvent(t){t.typ in this.eventHandlers&&this.eventHandlers[t.typ].map(e=>{e(t.data)})}static mounted(t){let e=new CustomEvent(J,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.mounted)}static beforeUpdate(t,e){let s=new CustomEvent(X,{}),n=this.getElementHooks(t);n!==null&&this.callHook(s,t,n.beforeUpdate),this.dom!==void 0&&this.dom.onBeforeElUpdated!==void 0&&this.dom.onBeforeElUpdated(t,e)}static updated(t){let e=new CustomEvent(Q,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.updated)}static beforeDestroy(t){let e=new CustomEvent(V,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.beforeDestroy)}static destroyed(t){let e=new CustomEvent(Y,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.destroyed)}static disconnected(){let t=new CustomEvent(Z,{});document.querySelectorAll("[live-hook]").forEach(e=>{let s=this.getElementHooks(e);s!==null&&this.callHook(t,e,s.disconnected)}),document.body.classList.add(T),document.body.classList.remove(A)}static reconnected(){let t=new CustomEvent(tt,{});document.querySelectorAll("[live-hook]").forEach(e=>{let s=this.getElementHooks(e);s!==null&&this.callHook(t,e,s.reconnected)}),document.body.classList.remove(T),document.body.classList.add(A)}static error(){document.body.classList.add(et)}static getElementHooks(t){let e=y.hook(t);return e===null?e:this.hooks[e]}static callHook(t,e,s){if(s===void 0)return;let n=d=>{c.send(d)},r=(d,m)=>{d in this.eventHandlers||(this.eventHandlers[d]=[]),this.eventHandlers[d].push(m)};s.bind({el:e,pushEvent:n,handleEvent:r})(),e.dispatchEvent(t)}};var l=class{static dehydrate(){document.querySelectorAll("form").forEach(e=>{if(e.id===""){console.error("form does not have an ID. DOM updates may be affected",e);return}this.formState[e.id]=[],new FormData(e).forEach((s,n)=>{let r={name:n,value:s,focus:e.querySelector(`[name="${n}"]`)==document.activeElement};this.formState[e.id].push(r)})})}static hydrate(){Object.keys(this.formState).map(t=>{let e=document.querySelector(`#${t}`);if(e===null){delete this.formState[t];return}this.formState[t].map(n=>{let r=e.querySelector(`[name="${n.name}"]`);if(r!==null)switch(r.type){case"file":break;case"checkbox":n.value==="on"&&(r.checked=!0);break;default:r.value=n.value,n.focus===!0&&r.focus();break}})})}static serialize(t){let e={};return new FormData(t).forEach((n,r)=>{switch(!0){case n instanceof File:let d=n,m={name:d.name,type:d.type,size:d.size,lastModified:d.lastModified};Reflect.has(e,this.upKey)||(e[this.upKey]={}),Reflect.has(e[this.upKey],r)||(e[this.upKey][r]=[]),e[this.upKey][r].push(m);break;default:if(!Reflect.has(e,r)){e[r]=n;return}Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(n)}}),e}static hasFiles(t){let e=new FormData(t),s=!1;return e.forEach(n=>{n instanceof File&&(s=!0)}),s}};l.upKey="uploads",l.formState={};var p=class{static handle(t){l.dehydrate(),t.data.map(p.applyPatch),l.hydrate()}static applyPatch(t){let e=document.querySelector(`*[${t.Anchor}]`);if(e===null)return;let s=p.html2Node(t.HTML);switch(t.Action){case 0:return;case 1:t.HTML===""?a.beforeDestroy(e):a.beforeUpdate(e,s),e.outerHTML=t.HTML,t.HTML===""?a.destroyed(e):a.updated(e);break;case 2:a.beforeUpdate(e,s),e.append(s),a.updated(e);break;case 3:a.beforeUpdate(e,s),e.prepend(s),a.updated(e);break}}static html2Node(t){let e=document.createElement("template");return t=t.trim(),e.innerHTML=t,e.content.firstChild===null?document.createTextNode(t):e.content.firstChild}};function E(i){let t={};if(new URLSearchParams(window.location.search).forEach((n,r)=>{t[r]=n}),i===void 0||!i.hasAttributes())return t;let s=i.attributes;for(let n=0;n{s[r]=n}),s}function b(i,t){if(window.history.pushState({},"",i),t===void 0)c.send(new o("params",h({},w(i))));else{let e=E(t);c.sendAndTrack(new o("params",h(h({},e),w(i)),o.GetID()),t)}}var u=class{constructor(t,e){this.event=t;this.attribute=e;this.limiter=new L}isWired(t){return t.hasAttribute(`${this.attribute}-wired`)?!0:(t.setAttribute(`${this.attribute}-wired`,""),!1)}attach(){document.querySelectorAll(`*[${this.attribute}]`).forEach(t=>{if(this.isWired(t)==!0)return;let e=E(t);t.addEventListener(this.event,s=>{this.limiter.hasDebounce(t)?this.limiter.debounce(t,s,this.handler(t,e)):this.handler(t,e)(s)}),t.addEventListener("ack",s=>{t.classList.remove(`${this.attribute}-loading`)})})}windowAttach(){document.querySelectorAll(`*[${this.attribute}]`).forEach(t=>{if(this.isWired(t)===!0)return;let e=E(t);window.addEventListener(this.event,this.handler(t,e)),window.addEventListener("ack",s=>{t.classList.remove(`${this.attribute}-loading`)})})}handler(t,e){return s=>{let n=t==null?void 0:t.getAttribute(this.attribute);n!==null&&(t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(n,e,o.GetID()),t))}}},f=class extends u{handler(t,e){return s=>{let n=s,r=t==null?void 0:t.getAttribute(this.attribute);if(r===null)return;let d=t.getAttribute("live-key");if(d!==null&&n.key!==d)return;t.classList.add(`${this.attribute}-loading`);let m={key:n.key,altKey:n.altKey,ctrlKey:n.ctrlKey,shiftKey:n.shiftKey,metaKey:n.metaKey};c.sendAndTrack(new o(r,h(h({},e),m),o.GetID()),t)}}},L=class{constructor(){this.debounceAttr="live-debounce"}hasDebounce(t){return t.hasAttribute(this.debounceAttr)}debounce(t,e,s){if(clearTimeout(this.debounceEvent),!this.hasDebounce(t)){s(e);return}let n=t.getAttribute(this.debounceAttr);if(n===null){s(e);return}if(n==="blur"){this.debounceEvent=s,t.addEventListener("blur",()=>{this.debounceEvent()});return}this.debounceEvent=setTimeout(()=>{s(e)},parseInt(n))}},D=class extends u{constructor(){super("click","live-click")}},S=class extends u{constructor(){super("contextmenu","live-contextmenu")}},$=class extends u{constructor(){super("mousedown","live-mousedown")}},P=class extends u{constructor(){super("mouseup","live-mouseup")}},F=class extends u{constructor(){super("focus","live-focus")}},K=class extends u{constructor(){super("blur","live-blur")}},U=class extends u{constructor(){super("focus","live-window-focus")}attach(){this.windowAttach()}},q=class extends u{constructor(){super("blur","live-window-blur")}attach(){this.windowAttach()}},C=class extends f{constructor(){super("keydown","live-keydown")}},W=class extends f{constructor(){super("keyup","live-keyup")}},R=class extends f{constructor(){super("keydown","live-window-keydown")}attach(){this.windowAttach()}},N=class extends f{constructor(){super("keyup","live-window-keyup")}attach(){this.windowAttach()}},O=class{constructor(){this.attribute="live-change";this.limiter=new L}isWired(t){return t.hasAttribute(`${this.attribute}-wired`)?!0:(t.setAttribute(`${this.attribute}-wired`,""),!1)}attach(){let t=[];document.querySelectorAll(`form[${this.attribute}]`).forEach(e=>{e.addEventListener("ack",s=>{e.classList.remove(`${this.attribute}-loading`)}),t.push(e),e.querySelectorAll("input,select,textarea").forEach(s=>{this.addEvent(e,s)})}),t.forEach(e=>{document.querySelectorAll(`[form=${e.getAttribute("id")}]`).forEach(s=>{this.addEvent(e,s)})})}addEvent(t,e){this.isWired(e)||e.addEventListener("input",s=>{this.limiter.hasDebounce(e)?this.limiter.debounce(e,s,()=>{this.handler(t)}):this.handler(t)})}handler(t){let e=t==null?void 0:t.getAttribute(this.attribute);if(e===null)return;let s=l.serialize(t);t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(e,s,o.GetID()),t)}},B=class extends u{constructor(){super("submit","live-submit")}handler(t,e){return s=>{if(s.preventDefault&&s.preventDefault(),l.hasFiles(t)===!0){let r=new XMLHttpRequest;r.open("POST",""),r.addEventListener("load",()=>{this.sendEvent(t,e)}),r.send(new FormData(t))}else this.sendEvent(t,e);return!1}}sendEvent(t,e){let s=t==null?void 0:t.getAttribute(this.attribute);if(s===null)return;var n=h({},e);let r=l.serialize(t);Object.keys(r).map(d=>{n[d]=r[d]}),t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(s,n,o.GetID()),t)}},G=class extends u{constructor(){super("","live-hook")}attach(){document.querySelectorAll(`[${this.attribute}]`).forEach(t=>{this.isWired(t)!=!0&&a.mounted(t)})}},I=class extends u{constructor(){super("click","live-patch")}handler(t,e){return s=>{s.preventDefault&&s.preventDefault();let n=t.getAttribute("href");if(n!==null)return b(n,t),!1}}},v=class{static init(){this.clicks=new D,this.contextmenu=new S,this.mousedown=new $,this.mouseup=new P,this.focus=new F,this.blur=new K,this.windowFocus=new U,this.windowBlur=new q,this.keydown=new C,this.keyup=new W,this.windowKeydown=new R,this.windowKeyup=new N,this.change=new O,this.submit=new B,this.hook=new G,this.patch=new I,this.handleBrowserNav()}static rewire(){this.clicks.attach(),this.contextmenu.attach(),this.mousedown.attach(),this.mouseup.attach(),this.focus.attach(),this.blur.attach(),this.windowFocus.attach(),this.windowBlur.attach(),this.keydown.attach(),this.keyup.attach(),this.windowKeyup.attach(),this.windowKeydown.attach(),this.change.attach(),this.submit.attach(),this.hook.attach(),this.patch.attach()}static handleBrowserNav(){window.onpopstate=function(t){c.send(new o("params",w(document.location.search),o.GetID()))}}};var H=class{constructor(){}static dial(){this.trackedEvents={},console.debug("Socket.dial called"),this.conn=new WebSocket(`${location.protocol==="https:"?"wss":"ws"}://${location.host}${location.pathname}${location.search}${location.hash}`),this.conn.addEventListener("close",t=>{this.ready=!1,console.warn(`WebSocket Disconnected code: ${t.code}, reason: ${t.reason}`),t.code!==1001&&(this.disconnectNotified===!1&&(a.disconnected(),this.disconnectNotified=!0),setTimeout(()=>{H.dial()},1e3))}),this.conn.addEventListener("open",t=>{a.reconnected(),this.disconnectNotified=!1,this.ready=!0}),this.conn.addEventListener("message",t=>{if(typeof t.data!="string"){console.error("unexpected message type",typeof t.data);return}let e=o.fromMessage(t.data);switch(e.typ){case"patch":p.handle(e),v.rewire();break;case"params":b(`${window.location.pathname}?${e.data}`);break;case"redirect":window.location.replace(e.data);break;case"ack":this.ack(e);break;case"err":a.error();default:a.handleEvent(e)}})}static sendAndTrack(t,e){if(this.ready===!1){console.warn("connection not ready for send of event",t);return}this.trackedEvents[t.id]={ev:t,el:e},this.conn.send(t.serialize())}static send(t){if(this.ready===!1){console.warn("connection not ready for send of event",t);return}this.conn.send(t.serialize())}static ack(t){t.id in this.trackedEvents&&(this.trackedEvents[t.id].el.dispatchEvent(new Event("ack")),delete this.trackedEvents[t.id])}},c=H;c.ready=!1,c.disconnectNotified=!1;var g=class{constructor(t,e){this.hooks=t;this.dom=e}init(){document.querySelector("[live-rendered]")!==null&&(a.init(this.hooks,this.dom),c.dial(),v.init(),v.rewire())}send(t,e,s){let n=new o(t,e,s);c.send(n)}};document.addEventListener("DOMContentLoaded",i=>{window.Live!==void 0&&console.error("window.Live already defined");let t=window.Hooks||{};window.Live=new g(t),window.Live.init()});})(); +(()=>{var z=Object.defineProperty;var A=Object.getOwnPropertySymbols;var _=Object.prototype.hasOwnProperty,j=Object.prototype.propertyIsEnumerable;var x=(i,t,e)=>t in i?z(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,h=(i,t)=>{for(var e in t||(t={}))_.call(t,e)&&x(i,e,t[e]);if(A)for(var e of A(t))j.call(t,e)&&x(i,e,t[e]);return i};var y=class{static hook(t){return t.getAttribute===void 0?null:t.getAttribute("live-hook")}};var J="live:mounted",X="live:beforeupdate",Q="live:updated",V="live:beforedestroy",Y="live:destroyed",Z="live:disconnected",tt="live:reconnected",M="live-connected",T="live-disconnected",et="live-error",k=class{constructor(t,e,s){this.typ=t,this.data=e,s!==void 0?this.id=s:this.id=0}static GetID(){return this.sequence++}serialize(){return JSON.stringify({t:this.typ,i:this.id,d:this.data})}static fromMessage(t){let e=JSON.parse(t);return new k(e.t,e.d,e.i)}},o=k;o.sequence=1;var a=class{constructor(){}static init(t,e){this.hooks=t,this.dom=e,this.eventHandlers={}}static handleEvent(t){t.typ in this.eventHandlers&&this.eventHandlers[t.typ].map(e=>{e(t.data)})}static mounted(t){let e=new CustomEvent(J,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.mounted)}static beforeUpdate(t,e){let s=new CustomEvent(X,{}),n=this.getElementHooks(t);n!==null&&this.callHook(s,t,n.beforeUpdate),this.dom!==void 0&&this.dom.onBeforeElUpdated!==void 0&&this.dom.onBeforeElUpdated(t,e)}static updated(t){let e=new CustomEvent(Q,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.updated)}static beforeDestroy(t){let e=new CustomEvent(V,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.beforeDestroy)}static destroyed(t){let e=new CustomEvent(Y,{}),s=this.getElementHooks(t);s!==null&&this.callHook(e,t,s.destroyed)}static disconnected(){let t=new CustomEvent(Z,{});document.querySelectorAll("[live-hook]").forEach(e=>{let s=this.getElementHooks(e);s!==null&&this.callHook(t,e,s.disconnected)}),document.body.classList.add(T),document.body.classList.remove(M)}static reconnected(){let t=new CustomEvent(tt,{});document.querySelectorAll("[live-hook]").forEach(e=>{let s=this.getElementHooks(e);s!==null&&this.callHook(t,e,s.reconnected)}),document.body.classList.remove(T),document.body.classList.add(M)}static error(){document.body.classList.add(et)}static getElementHooks(t){let e=y.hook(t);return e===null?e:this.hooks[e]}static callHook(t,e,s){if(s===void 0)return;let n=u=>{c.send(u)},r=(u,m)=>{u in this.eventHandlers||(this.eventHandlers[u]=[]),this.eventHandlers[u].push(m)};s.bind({el:e,pushEvent:n,handleEvent:r})(),e.dispatchEvent(t)}};var l=class{static dehydrate(){document.querySelectorAll("form").forEach(e=>{if(e.id===""){console.error("form does not have an ID. DOM updates may be affected",e);return}this.formState[e.id]=[],new FormData(e).forEach((s,n)=>{let r={name:n,value:s,focus:e.querySelector(`[name="${n}"]`)==document.activeElement};this.formState[e.id].push(r)})})}static hydrate(){Object.keys(this.formState).map(t=>{let e=document.querySelector(`#${t}`);if(e===null){delete this.formState[t];return}this.formState[t].map(n=>{let r=e.querySelector(`[name="${n.name}"]`);if(r!==null)switch(r.type){case"file":break;case"checkbox":n.value==="on"&&(r.checked=!0);break;default:r.value=n.value,n.focus===!0&&r.focus();break}})})}static serialize(t){let e={};return new FormData(t).forEach((n,r)=>{switch(!0){case n instanceof File:let u=n,m={name:u.name,type:u.type,size:u.size,lastModified:u.lastModified};Reflect.has(e,this.upKey)||(e[this.upKey]={}),Reflect.has(e[this.upKey],r)||(e[this.upKey][r]=[]),e[this.upKey][r].push(m);break;default:if(!Reflect.has(e,r)){e[r]=n;return}Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(n)}}),e}static hasFiles(t){let e=new FormData(t),s=!1;return e.forEach(n=>{n instanceof File&&(s=!0)}),s}};l.upKey="uploads",l.formState={};var p=class{static handle(t){l.dehydrate(),t.data.map(p.applyPatch),l.hydrate()}static applyPatch(t){let e=document.querySelector(`*[${t.Anchor}]`);if(e===null)return;let s=p.html2Node(t.HTML);switch(t.Action){case 0:return;case 1:t.HTML===""?a.beforeDestroy(e):a.beforeUpdate(e,s),e.outerHTML=t.HTML,t.HTML===""?a.destroyed(e):a.updated(e);break;case 2:a.beforeUpdate(e,s),e.append(s),a.updated(e);break;case 3:a.beforeUpdate(e,s),e.prepend(s),a.updated(e);break}}static html2Node(t){let e=document.createElement("template");return t=t.trim(),e.innerHTML=t,e.content.firstChild===null?document.createTextNode(t):e.content.firstChild}};function E(i){let t={};if(new URLSearchParams(window.location.search).forEach((n,r)=>{t[r]=n}),i===void 0||!i.hasAttributes())return t;let s=i.attributes;for(let n=0;n{s[r]=n}),s}function b(i,t){if(window.history.pushState({},"",i),t===void 0)c.send(new o("params",h({},w(i))));else{let e=E(t);c.sendAndTrack(new o("params",h(h({},e),w(i)),o.GetID()),t)}}var d=class{constructor(t,e){this.event=t;this.attribute=e;this.limiter=new L}isWired(t){return t.hasAttribute(`${this.attribute}-wired`)?!0:(t.setAttribute(`${this.attribute}-wired`,""),!1)}attach(){document.querySelectorAll(`*[${this.attribute}]`).forEach(t=>{if(this.isWired(t)==!0)return;let e=E(t);t.addEventListener(this.event,s=>{this.limiter.limit(t,s,this.handler(t,e))}),t.addEventListener("ack",s=>{t.classList.remove(`${this.attribute}-loading`)})})}windowAttach(){document.querySelectorAll(`*[${this.attribute}]`).forEach(t=>{if(this.isWired(t)===!0)return;let e=E(t);window.addEventListener(this.event,this.handler(t,e)),window.addEventListener("ack",s=>{t.classList.remove(`${this.attribute}-loading`)})})}handler(t,e){return s=>{let n=t==null?void 0:t.getAttribute(this.attribute);n!==null&&(t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(n,e,o.GetID()),t))}}},f=class extends d{handler(t,e){return s=>{let n=s,r=t==null?void 0:t.getAttribute(this.attribute);if(r===null)return;let u=t.getAttribute("live-key");if(u!==null&&n.key!==u)return;t.classList.add(`${this.attribute}-loading`);let m={key:n.key,altKey:n.altKey,ctrlKey:n.ctrlKey,shiftKey:n.shiftKey,metaKey:n.metaKey};c.sendAndTrack(new o(r,h(h({},e),m),o.GetID()),t)}}},L=class{constructor(){this.debounceAttr="live-debounce";this.throttleAttr="live-throttle"}hasDebounce(t){return t.hasAttribute(this.debounceAttr)}hasThrottle(t){return t.hasAttribute(this.throttleAttr)}debounce(t,e,s){clearTimeout(this.debounceEvent);let n=t.getAttribute(this.debounceAttr);if(!this.hasDebounce(t)||n===null){s(e);return}if(n==="blur"){this.debounceEvent=s,t.addEventListener("blur",()=>{this.debounceEvent()});return}this.debounceEvent=setTimeout(()=>{s(e)},parseInt(n))}throttle(t,e,s){let n=t.getAttribute(this.throttleAttr);if(!this.hasThrottle(t)||n===null){s(e);return}this.throttleEvent?this.throttleFunc=()=>{s(e)}:(s(e),this.throttleEvent=setTimeout(()=>{this.throttleFunc&&(this.throttleFunc(),this.throttleFunc=null,this.throttleEvent=null)},parseInt(n)))}limit(t,e,s){this.debounce(t,e,n=>{this.throttle(t,n,s)})}},D=class extends d{constructor(){super("click","live-click")}},S=class extends d{constructor(){super("contextmenu","live-contextmenu")}},F=class extends d{constructor(){super("mousedown","live-mousedown")}},$=class extends d{constructor(){super("mouseup","live-mouseup")}},P=class extends d{constructor(){super("focus","live-focus")}},K=class extends d{constructor(){super("blur","live-blur")}},U=class extends d{constructor(){super("focus","live-window-focus")}attach(){this.windowAttach()}},q=class extends d{constructor(){super("blur","live-window-blur")}attach(){this.windowAttach()}},C=class extends f{constructor(){super("keydown","live-keydown")}},W=class extends f{constructor(){super("keyup","live-keyup")}},R=class extends f{constructor(){super("keydown","live-window-keydown")}attach(){this.windowAttach()}},N=class extends f{constructor(){super("keyup","live-window-keyup")}attach(){this.windowAttach()}},O=class{constructor(){this.attribute="live-change";this.limiter=new L}isWired(t){return t.hasAttribute(`${this.attribute}-wired`)?!0:(t.setAttribute(`${this.attribute}-wired`,""),!1)}attach(){let t=[];document.querySelectorAll(`form[${this.attribute}]`).forEach(e=>{e.addEventListener("ack",s=>{e.classList.remove(`${this.attribute}-loading`)}),t.push(e),e.querySelectorAll("input,select,textarea").forEach(s=>{this.addEvent(e,s)})}),t.forEach(e=>{document.querySelectorAll(`[form=${e.getAttribute("id")}]`).forEach(s=>{this.addEvent(e,s)})})}addEvent(t,e){this.isWired(e)||e.addEventListener("input",s=>{this.limiter.limit(e,s,()=>{this.handler(t)})})}handler(t){let e=t==null?void 0:t.getAttribute(this.attribute);if(e===null)return;let s=l.serialize(t);t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(e,s,o.GetID()),t)}},B=class extends d{constructor(){super("submit","live-submit")}handler(t,e){return s=>{if(s.preventDefault&&s.preventDefault(),l.hasFiles(t)===!0){let r=new XMLHttpRequest;r.open("POST",""),r.addEventListener("load",()=>{this.sendEvent(t,e)}),r.send(new FormData(t))}else this.sendEvent(t,e);return!1}}sendEvent(t,e){let s=t==null?void 0:t.getAttribute(this.attribute);if(s===null)return;var n=h({},e);let r=l.serialize(t);Object.keys(r).map(u=>{n[u]=r[u]}),t.classList.add(`${this.attribute}-loading`),c.sendAndTrack(new o(s,n,o.GetID()),t)}},G=class extends d{constructor(){super("","live-hook")}attach(){document.querySelectorAll(`[${this.attribute}]`).forEach(t=>{this.isWired(t)!=!0&&a.mounted(t)})}},I=class extends d{constructor(){super("click","live-patch")}handler(t,e){return s=>{s.preventDefault&&s.preventDefault();let n=t.getAttribute("href");if(n!==null)return b(n,t),!1}}},v=class{static init(){this.clicks=new D,this.contextmenu=new S,this.mousedown=new F,this.mouseup=new $,this.focus=new P,this.blur=new K,this.windowFocus=new U,this.windowBlur=new q,this.keydown=new C,this.keyup=new W,this.windowKeydown=new R,this.windowKeyup=new N,this.change=new O,this.submit=new B,this.hook=new G,this.patch=new I,this.handleBrowserNav()}static rewire(){this.clicks.attach(),this.contextmenu.attach(),this.mousedown.attach(),this.mouseup.attach(),this.focus.attach(),this.blur.attach(),this.windowFocus.attach(),this.windowBlur.attach(),this.keydown.attach(),this.keyup.attach(),this.windowKeyup.attach(),this.windowKeydown.attach(),this.change.attach(),this.submit.attach(),this.hook.attach(),this.patch.attach()}static handleBrowserNav(){window.onpopstate=function(t){c.send(new o("params",w(document.location.search),o.GetID()))}}};var g=class{constructor(){}static dial(){this.trackedEvents={},console.debug("Socket.dial called"),this.conn=new WebSocket(`${location.protocol==="https:"?"wss":"ws"}://${location.host}${location.pathname}${location.search}${location.hash}`),this.conn.addEventListener("close",t=>{this.ready=!1,console.warn(`WebSocket Disconnected code: ${t.code}, reason: ${t.reason}`),t.code!==1001&&(this.disconnectNotified===!1&&(a.disconnected(),this.disconnectNotified=!0),setTimeout(()=>{g.dial()},1e3))}),this.conn.addEventListener("open",t=>{a.reconnected(),this.disconnectNotified=!1,this.ready=!0}),this.conn.addEventListener("message",t=>{if(typeof t.data!="string"){console.error("unexpected message type",typeof t.data);return}let e=o.fromMessage(t.data);switch(e.typ){case"patch":p.handle(e),v.rewire();break;case"params":b(`${window.location.pathname}?${e.data}`);break;case"redirect":window.location.replace(e.data);break;case"ack":this.ack(e);break;case"err":a.error();default:a.handleEvent(e)}})}static sendAndTrack(t,e){if(this.ready===!1){console.warn("connection not ready for send of event",t);return}this.trackedEvents[t.id]={ev:t,el:e},this.conn.send(t.serialize())}static send(t){if(this.ready===!1){console.warn("connection not ready for send of event",t);return}this.conn.send(t.serialize())}static ack(t){t.id in this.trackedEvents&&(this.trackedEvents[t.id].el.dispatchEvent(new Event("ack")),delete this.trackedEvents[t.id])}},c=g;c.ready=!1,c.disconnectNotified=!1;var H=class{constructor(t,e){this.hooks=t;this.dom=e}init(){document.querySelector("[live-rendered]")!==null&&(a.init(this.hooks,this.dom),c.dial(),v.init(),v.rewire())}send(t,e,s){let n=new o(t,e,s);c.send(n)}};document.addEventListener("DOMContentLoaded",i=>{window.Live!==void 0&&console.error("window.Live already defined");let t=window.Hooks||{};window.Live=new H(t),window.Live.init()});})(); //# sourceMappingURL=auto.js.map diff --git a/web/browser/auto.js.map b/web/browser/auto.js.map index 076a9a9..602c355 100644 --- a/web/browser/auto.js.map +++ b/web/browser/auto.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/element.ts", "../src/event.ts", "../src/forms.ts", "../src/patch.ts", "../src/params.ts", "../src/events.ts", "../src/socket.ts", "../src/live.ts", "../src/auto.ts"], - "sourcesContent": ["/**\n * Element helper class.\n */\nexport class LiveElement {\n static hook(element: HTMLElement): string | null {\n if (element.getAttribute === undefined) {\n return null;\n }\n return element.getAttribute(\"live-hook\");\n }\n}\n", "import { Socket } from \"./socket\";\nimport { LiveElement } from \"./element\";\nimport { Hook, Hooks, DOM } from \"./interop\";\n\nexport const EventMounted = \"live:mounted\";\nexport const EventBeforeUpdate = \"live:beforeupdate\";\nexport const EventUpdated = \"live:updated\";\nexport const EventBeforeDestroy = \"live:beforedestroy\";\nexport const EventDestroyed = \"live:destroyed\";\nexport const EventDisconnected = \"live:disconnected\";\nexport const EventReconnected = \"live:reconnected\";\n\nexport const ClassConnected = \"live-connected\";\nexport const ClassDisconnected = \"live-disconnected\";\nexport const ClassError = \"live-error\";\n\n/**\n * LiveEvent an event that is being passed back and forth\n * between the frontend and server.\n */\nexport class LiveEvent {\n public typ: string;\n public id: number;\n public data: any;\n private static sequence: number = 1;\n\n constructor(typ: string, data: any, id?: number) {\n this.typ = typ;\n this.data = data;\n if (id !== undefined) {\n this.id = id;\n } else {\n this.id = 0;\n }\n }\n\n /**\n * Get an ID for an event.\n */\n public static GetID(): number {\n return this.sequence++;\n }\n\n /**\n * Convert the event onto our wire format\n */\n public serialize(): string {\n return JSON.stringify({\n t: this.typ,\n i: this.id,\n d: this.data,\n });\n }\n\n /**\n * From an incoming message create a live event.\n */\n public static fromMessage(data: any): LiveEvent {\n const e = JSON.parse(data);\n return new LiveEvent(e.t, e.d, e.i);\n }\n}\n\n/**\n * EventDispatch allows the code base to send events\n * to hooked elements. Also handles events coming from\n * the server.\n */\nexport class EventDispatch {\n private static hooks: Hooks;\n private static dom?: DOM;\n private static eventHandlers: { [e: string]: ((d: any) => void)[] };\n\n constructor() {}\n\n /**\n * Must be called before usage.\n */\n static init(hooks: Hooks, dom?: DOM) {\n this.hooks = hooks;\n this.dom = dom;\n this.eventHandlers = {};\n }\n\n /**\n * Handle an event pushed from the server.\n */\n static handleEvent(ev: LiveEvent) {\n if (!(ev.typ in this.eventHandlers)) {\n return;\n }\n this.eventHandlers[ev.typ].map((h) => {\n h(ev.data);\n });\n }\n\n /**\n * Handle an element being mounted.\n */\n static mounted(element: Element) {\n const event = new CustomEvent(EventMounted, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.mounted);\n }\n\n /**\n * Before an element is updated.\n */\n static beforeUpdate(fromEl: Element, toEl: Element) {\n const event = new CustomEvent(EventBeforeUpdate, {});\n\n const h = this.getElementHooks(fromEl);\n if (h !== null) {\n this.callHook(event, fromEl, h.beforeUpdate);\n }\n\n if (\n this.dom !== undefined &&\n this.dom.onBeforeElUpdated !== undefined\n ) {\n this.dom.onBeforeElUpdated(fromEl, toEl);\n }\n }\n\n /**\n * After and element has been updated.\n */\n static updated(element: Element) {\n const event = new CustomEvent(EventUpdated, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.updated);\n }\n\n /**\n * Before an element is destroyed.\n */\n static beforeDestroy(element: Element) {\n const event = new CustomEvent(EventBeforeDestroy, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.beforeDestroy);\n }\n\n /**\n * After an element has been destroyed.\n */\n static destroyed(element: Element) {\n const event = new CustomEvent(EventDestroyed, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.destroyed);\n }\n\n /**\n * Handle a disconnection event.\n */\n static disconnected() {\n const event = new CustomEvent(EventDisconnected, {});\n document.querySelectorAll(`[live-hook]`).forEach((element: Element) => {\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.disconnected);\n });\n document.body.classList.add(ClassDisconnected);\n document.body.classList.remove(ClassConnected);\n }\n\n /**\n * Handle a reconnection event.\n */\n static reconnected() {\n const event = new CustomEvent(EventReconnected, {});\n document.querySelectorAll(`[live-hook]`).forEach((element: Element) => {\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.reconnected);\n });\n document.body.classList.remove(ClassDisconnected);\n document.body.classList.add(ClassConnected);\n }\n\n /**\n * Handle an error event.\n */\n static error() {\n document.body.classList.add(ClassError);\n }\n\n private static getElementHooks(element: Element): Hook | null {\n const val = LiveElement.hook(element as HTMLElement);\n if (val === null) {\n return val;\n }\n return this.hooks[val];\n }\n\n private static callHook(\n event: CustomEvent,\n el: Element,\n f: (() => void) | undefined\n ) {\n if (f === undefined) {\n return;\n }\n const pushEvent = (e: LiveEvent) => {\n Socket.send(e);\n };\n const handleEvent = (e: string, cb: (d: any) => void) => {\n if (!(e in this.eventHandlers)) {\n this.eventHandlers[e] = [];\n }\n this.eventHandlers[e].push(cb);\n };\n f.bind({ el, pushEvent, handleEvent })();\n el.dispatchEvent(event);\n }\n}\n", "/**\n * A value of an existing input in a form.\n */\ninterface inputState {\n name: string;\n focus: boolean;\n value: any;\n}\n\n/**\n * A value of a file input for validation.\n */\ninterface fileInput {\n name: string;\n lastModified: number;\n size: number;\n type: string;\n}\n\n/**\n * Form helper class.\n */\nexport class Forms {\n private static upKey = \"uploads\";\n\n private static formState: { [id: string]: inputState[] } = {};\n\n /**\n * When we are patching the DOM we need to save the state\n * of any forms so that we don't lose input values or\n * focus\n */\n static dehydrate() {\n const forms = document.querySelectorAll(\"form\");\n forms.forEach((f) => {\n if (f.id === \"\") {\n console.error(\n \"form does not have an ID. DOM updates may be affected\",\n f\n );\n return;\n }\n\n this.formState[f.id] = [];\n new FormData(f).forEach((value: any, name: string) => {\n const i = {\n name: name,\n value: value,\n focus:\n f.querySelector(`[name=\"${name}\"]`) ==\n document.activeElement,\n };\n this.formState[f.id].push(i);\n });\n });\n }\n\n /**\n * This sets the form backup to its original state.\n */\n static hydrate() {\n Object.keys(this.formState).map((formID) => {\n const form = document.querySelector(`#${formID}`);\n if (form === null) {\n delete this.formState[formID];\n return;\n }\n\n const state = this.formState[formID];\n state.map((i) => {\n const input = form.querySelector(\n `[name=\"${i.name}\"]`\n ) as HTMLInputElement;\n if (input === null) {\n return;\n }\n switch (input.type) {\n case \"file\":\n break;\n case \"checkbox\":\n if (i.value === \"on\") {\n input.checked = true;\n }\n break;\n default:\n input.value = i.value;\n if (i.focus === true) {\n input.focus();\n }\n break;\n }\n });\n });\n }\n\n /**\n * serialize form to values.\n */\n static serialize(form: HTMLFormElement): { [key: string]: string | number | fileInput } {\n const values: { [key: string]: any } = {};\n const formData = new FormData(form);\n formData.forEach((value, key) => {\n switch (true) {\n case value instanceof File:\n const file = value as File;\n const fi = {\n name: file.name,\n type: file.type,\n size: file.size,\n lastModified: file.lastModified,\n }\n if (!Reflect.has(values, this.upKey)) {\n values[this.upKey] = {};\n }\n if (!Reflect.has(values[this.upKey], key)) {\n values[this.upKey][key] = [];\n }\n values[this.upKey][key].push(fi);\n break;\n default:\n // If the key doesn't exist set it.\n if (!Reflect.has(values, key)) {\n values[key] = value;\n return;\n }\n // If it already exists that means this needs to become\n // an array.\n if (!Array.isArray(values[key])) {\n values[key] = [values[key]];\n }\n // Push the new value onto the array.\n values[key].push(value);\n }\n });\n return values;\n }\n\n /**\n * does a form have files.\n */\n static hasFiles(form: HTMLFormElement): boolean {\n const formData = new FormData(form);\n let hasFiles = false;\n formData.forEach((value) => {\n if(value instanceof File) {\n hasFiles = true;\n }\n });\n return hasFiles;\n }\n}\n", "import { LiveEvent, EventDispatch } from \"./event\";\nimport { Forms } from \"./forms\";\n\ninterface PatchEvent {\n Anchor: string;\n Action: number;\n HTML: string;\n}\n\n/**\n * Handle patches from the backend.\n */\nexport class Patch {\n static handle(event: LiveEvent) {\n Forms.dehydrate();\n\n const patches = event.data;\n patches.map(Patch.applyPatch);\n\n Forms.hydrate();\n }\n\n private static applyPatch(e: PatchEvent) {\n const target = document.querySelector(`*[${e.Anchor}]`);\n if (target === null) {\n return;\n }\n\n const newElement = Patch.html2Node(e.HTML);\n switch (e.Action) {\n case 0: // NOOP\n return;\n case 1: // REPLACE\n if (e.HTML === \"\") {\n EventDispatch.beforeDestroy(target);\n } else {\n EventDispatch.beforeUpdate(target, newElement as Element);\n }\n target.outerHTML = e.HTML;\n if (e.HTML === \"\") {\n EventDispatch.destroyed(target);\n } else {\n EventDispatch.updated(target);\n }\n break;\n case 2: // APPEND\n EventDispatch.beforeUpdate(target, newElement as Element);\n target.append(newElement);\n EventDispatch.updated(target);\n break;\n case 3: // PREPEND\n EventDispatch.beforeUpdate(target, newElement as Element);\n target.prepend(newElement);\n EventDispatch.updated(target);\n break;\n }\n }\n\n private static html2Node(html: string): Node {\n const template = document.createElement(\"template\");\n html = html.trim();\n template.innerHTML = html;\n if (template.content.firstChild === null) {\n return document.createTextNode(html);\n }\n return template.content.firstChild;\n }\n}\n", "import { Socket } from \"./socket\";\nimport { LiveEvent } from \"./event\";\n\n/**\n * A values from the \"live-value-\" attributes. As\n * well as values from the query string in the URL.\n */\nexport interface Params {\n [key: string]: any;\n}\n\n/**\n * GetParams gets the current parameters for an event. This includes\n * any from an element passed in and the URL search string.\n */\nexport function GetParams(element?: HTMLElement): Params {\n const output: Params = {};\n\n const urlParams = new URLSearchParams(window.location.search);\n urlParams.forEach((value, key) => {\n output[key] = value;\n });\n\n if (element === undefined) {\n return output;\n }\n\n if (!element.hasAttributes()) {\n return output;\n }\n const attrs = element.attributes;\n for (let i = 0; i < attrs.length; i++) {\n if (!attrs[i].name.startsWith(\"live-value-\")) {\n continue;\n }\n output[attrs[i].name.split(\"live-value-\")[1]] = attrs[i].value;\n }\n return output;\n}\n\n/**\n * GetURLParams get the params from a url path.\n */\nexport function GetURLParams(path: string): Params {\n const url = new URL(path, location.origin);\n const urlParams = new URLSearchParams(url.search);\n\n const output: Params = {};\n urlParams.forEach((value, key) => {\n output[key] = value;\n });\n\n return output;\n}\n\n/**\n * UpdateURLParams update the URL using the push state api, then\n * notify the backend.\n */\nexport function UpdateURLParams(path: string, element?: HTMLElement) {\n window.history.pushState({}, \"\", path);\n if (element === undefined) {\n Socket.send(new LiveEvent(\"params\", { ...GetURLParams(path) }));\n } else {\n const params = GetParams(element);\n Socket.sendAndTrack(\n new LiveEvent(\n \"params\",\n { ...params, ...GetURLParams(path) },\n LiveEvent.GetID()\n ),\n element\n );\n }\n}\n", "import { Socket } from \"./socket\";\nimport { Forms } from \"./forms\";\nimport { UpdateURLParams, GetParams, GetURLParams, Params } from \"./params\";\nimport { EventDispatch, LiveEvent } from \"./event\";\n\n/**\n * Standard event handler class. Clicks, focus and blur.\n */\nclass LiveHandler {\n protected limiter = new Limiter();\n\n constructor(protected event: string, protected attribute: string) {}\n\n public isWired(element: Element): boolean {\n if (element.hasAttribute(`${this.attribute}-wired`)) {\n return true;\n }\n element.setAttribute(`${this.attribute}-wired`, \"\");\n return false;\n }\n\n public attach() {\n document\n .querySelectorAll(`*[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) == true) {\n return;\n }\n const params = GetParams(element as HTMLElement);\n element.addEventListener(this.event, (e) => {\n if (this.limiter.hasDebounce(element)) {\n this.limiter.debounce(\n element,\n e,\n this.handler(element as HTMLFormElement, params)\n );\n } else {\n this.handler(element as HTMLFormElement, params)(e);\n }\n });\n element.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n });\n }\n\n protected windowAttach() {\n document\n .querySelectorAll(`*[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) === true) {\n return;\n }\n const params = GetParams(element as HTMLElement);\n window.addEventListener(\n this.event,\n this.handler(element as HTMLElement, params)\n );\n window.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n });\n }\n\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (_: Event) => {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, params, LiveEvent.GetID()),\n element\n );\n };\n }\n}\n\n/**\n * KeyHandler handle key events.\n */\nexport class KeyHandler extends LiveHandler {\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (ev: Event) => {\n const ke = ev as KeyboardEvent;\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n const filter = element.getAttribute(\"live-key\");\n if (filter !== null) {\n if (ke.key !== filter) {\n return;\n }\n }\n element.classList.add(`${this.attribute}-loading`);\n const keyData = {\n key: ke.key,\n altKey: ke.altKey,\n ctrlKey: ke.ctrlKey,\n shiftKey: ke.shiftKey,\n metaKey: ke.metaKey,\n };\n Socket.sendAndTrack(\n new LiveEvent(t, { ...params, ...keyData }, LiveEvent.GetID()),\n element\n );\n };\n }\n}\n\nclass Limiter {\n private debounceAttr = \"live-debounce\";\n private debounceEvent: any;\n\n public hasDebounce(element: Element): boolean {\n return element.hasAttribute(this.debounceAttr);\n }\n\n public debounce(element: Element, e: Event, fn: EventListener) {\n clearTimeout(this.debounceEvent);\n if (!this.hasDebounce(element)) {\n fn(e);\n return;\n }\n const debounce = element.getAttribute(this.debounceAttr);\n if (debounce === null) {\n fn(e);\n return;\n }\n if (debounce === \"blur\") {\n this.debounceEvent = fn;\n element.addEventListener(\"blur\", () => {\n this.debounceEvent();\n });\n return;\n }\n this.debounceEvent = setTimeout(() => {\n fn(e);\n }, parseInt(debounce));\n }\n}\n\n/**\n * live-click attribute handling.\n */\nclass Click extends LiveHandler {\n constructor() {\n super(\"click\", \"live-click\");\n }\n}\n\n/**\n * live-contextmenu attribute handling.\n */\nclass Contextmenu extends LiveHandler {\n constructor() {\n super(\"contextmenu\", \"live-contextmenu\");\n }\n}\n\n/**\n * live-mousedown attribute handling.\n */\nclass Mousedown extends LiveHandler {\n constructor() {\n super(\"mousedown\", \"live-mousedown\");\n }\n}\n\n/**\n * live-mouseup attribute handling.\n */\nclass Mouseup extends LiveHandler {\n constructor() {\n super(\"mouseup\", \"live-mouseup\");\n }\n}\n\n/**\n * live-focus event handling.\n */\nclass Focus extends LiveHandler {\n constructor() {\n super(\"focus\", \"live-focus\");\n }\n}\n\n/**\n * live-blur event handling.\n */\nclass Blur extends LiveHandler {\n constructor() {\n super(\"blur\", \"live-blur\");\n }\n}\n\n/**\n * live-window-focus event handler.\n */\nclass WindowFocus extends LiveHandler {\n constructor() {\n super(\"focus\", \"live-window-focus\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-window-blur event handler.\n */\nclass WindowBlur extends LiveHandler {\n constructor() {\n super(\"blur\", \"live-window-blur\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-keydown event handler.\n */\nclass Keydown extends KeyHandler {\n constructor() {\n super(\"keydown\", \"live-keydown\");\n }\n}\n\n/**\n * live-keyup event handler.\n */\nclass Keyup extends KeyHandler {\n constructor() {\n super(\"keyup\", \"live-keyup\");\n }\n}\n\n/**\n * live-window-keydown event handler.\n */\nclass WindowKeydown extends KeyHandler {\n constructor() {\n super(\"keydown\", \"live-window-keydown\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-window-keyup event handler.\n */\nclass WindowKeyup extends KeyHandler {\n constructor() {\n super(\"keyup\", \"live-window-keyup\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-change form handler.\n */\nclass Change {\n protected attribute = \"live-change\";\n protected limiter = new Limiter();\n\n constructor() {}\n\n public isWired(element: Element): boolean {\n if (element.hasAttribute(`${this.attribute}-wired`)) {\n return true;\n }\n element.setAttribute(`${this.attribute}-wired`, \"\");\n return false;\n }\n \n public attach() {\n let forms: Element[] = [];\n document\n .querySelectorAll(`form[${this.attribute}]`)\n .forEach((element: Element) => {\n element.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n forms.push(element);\n element\n .querySelectorAll(`input,select,textarea`)\n .forEach((childElement: Element) => {\n this.addEvent(element, childElement);\n });\n });\n forms.forEach((element: Element) => {\n document\n .querySelectorAll(`[form=${element.getAttribute(\"id\")}]`)\n .forEach((childElement) => {\n this.addEvent(element, childElement);\n });\n });\n };\n\n private addEvent(element: Element, childElement: Element) {\n if (this.isWired(childElement)) {\n return;\n }\n childElement.addEventListener(\"input\", (e) => {\n if (this.limiter.hasDebounce(childElement)) {\n this.limiter.debounce(childElement, e, () => {\n this.handler(element as HTMLFormElement);\n });\n } else {\n this.handler(element as HTMLFormElement);\n }\n });\n }\n\n private handler(element: HTMLFormElement) {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n const values: { [key: string]: any } = Forms.serialize(element);\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, values, LiveEvent.GetID()),\n element\n );\n }\n}\n\n/**\n * live-submit form handler.\n */\nclass Submit extends LiveHandler {\n constructor() {\n super(\"submit\", \"live-submit\");\n }\n\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (e: Event) => {\n if (e.preventDefault) e.preventDefault();\n\n const hasFiles = Forms.hasFiles(element as HTMLFormElement);\n if (hasFiles === true) {\n const request = new XMLHttpRequest();\n request.open(\"POST\", \"\");\n request.addEventListener('load', () => {\n this.sendEvent(element, params);\n });\n\n request.send(new FormData(element as HTMLFormElement));\n } else {\n this.sendEvent(element, params);\n }\n return false;\n };\n }\n\n protected sendEvent(element: HTMLElement, params: Params) {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n\n var vals = { ...params };\n\n const data: { [key: string]: any } = Forms.serialize(\n element as HTMLFormElement\n );\n Object.keys(data).map((k) => {\n vals[k] = data[k];\n });\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, vals, LiveEvent.GetID()),\n element\n );\n }\n}\n\n/**\n * live-hook event handler.\n */\nclass Hook extends LiveHandler {\n constructor() {\n super(\"\", \"live-hook\");\n }\n\n public attach() {\n document\n .querySelectorAll(`[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) == true) {\n return;\n }\n EventDispatch.mounted(element);\n });\n }\n}\n\n/**\n * live-patch event handler.\n */\nclass Patch extends LiveHandler {\n constructor() {\n super(\"click\", \"live-patch\");\n }\n\n protected handler(element: HTMLElement, _: Params): EventListener {\n return (e: Event) => {\n if (e.preventDefault) e.preventDefault();\n const path = element.getAttribute(\"href\");\n if (path === null) {\n return;\n }\n UpdateURLParams(path, element);\n return false;\n };\n }\n}\n\n/**\n * Handle all events.\n */\nexport class Events {\n private static clicks: Click;\n private static contextmenu: Contextmenu;\n private static mousedown: Mousedown;\n private static mouseup: Mouseup;\n private static focus: Focus;\n private static blur: Blur;\n private static windowFocus: WindowFocus;\n private static windowBlur: WindowBlur;\n private static keydown: Keydown;\n private static keyup: Keyup;\n private static windowKeydown: WindowKeydown;\n private static windowKeyup: WindowKeyup;\n private static change: Change;\n private static submit: Submit;\n private static hook: Hook;\n private static patch: Patch;\n\n /**\n * Initialise all the event wiring.\n */\n public static init() {\n this.clicks = new Click();\n this.contextmenu = new Contextmenu();\n this.mousedown = new Mousedown();\n this.mouseup = new Mouseup();\n this.focus = new Focus();\n this.blur = new Blur();\n this.windowFocus = new WindowFocus();\n this.windowBlur = new WindowBlur();\n this.keydown = new Keydown();\n this.keyup = new Keyup();\n this.windowKeydown = new WindowKeydown();\n this.windowKeyup = new WindowKeyup();\n this.change = new Change();\n this.submit = new Submit();\n this.hook = new Hook();\n this.patch = new Patch();\n\n this.handleBrowserNav();\n }\n\n /**\n * Re-attach all events when we have re-rendered.\n */\n public static rewire() {\n this.clicks.attach();\n this.contextmenu.attach();\n this.mousedown.attach();\n this.mouseup.attach();\n this.focus.attach();\n this.blur.attach();\n this.windowFocus.attach();\n this.windowBlur.attach();\n this.keydown.attach();\n this.keyup.attach();\n this.windowKeyup.attach();\n this.windowKeydown.attach();\n this.change.attach();\n this.submit.attach();\n this.hook.attach();\n this.patch.attach();\n }\n\n /**\n * Watch the browser popstate so that we can send a params\n * change event to the server.\n */\n private static handleBrowserNav() {\n window.onpopstate = function (_: any) {\n Socket.send(\n new LiveEvent(\n \"params\",\n GetURLParams(document.location.search),\n LiveEvent.GetID()\n )\n );\n };\n }\n}\n", "import { EventDispatch, LiveEvent } from \"./event\";\nimport { Patch } from \"./patch\";\nimport { Events } from \"./events\";\nimport { UpdateURLParams } from \"./params\";\n\n/**\n * Represents the websocket connection to\n * the backend server.\n */\nexport class Socket {\n private static conn: WebSocket;\n private static ready: boolean = false;\n private static disconnectNotified: boolean = false;\n\n private static trackedEvents: {\n [id: number]: { ev: LiveEvent; el: HTMLElement };\n };\n\n constructor() {}\n\n static dial() {\n this.trackedEvents = {};\n\n console.debug(\"Socket.dial called\");\n this.conn = new WebSocket(\n `${location.protocol === \"https:\" ? \"wss\" : \"ws\"}://${\n location.host\n }${location.pathname}${location.search}${location.hash}`\n );\n this.conn.addEventListener(\"close\", (ev) => {\n this.ready = false;\n console.warn(\n `WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`\n );\n if (ev.code !== 1001) {\n if (this.disconnectNotified === false) {\n EventDispatch.disconnected();\n this.disconnectNotified = true;\n }\n setTimeout(() => {\n Socket.dial();\n }, 1000);\n }\n });\n // Ping on open.\n this.conn.addEventListener(\"open\", (_) => {\n EventDispatch.reconnected();\n this.disconnectNotified = false;\n this.ready = true;\n });\n this.conn.addEventListener(\"message\", (ev) => {\n if (typeof ev.data !== \"string\") {\n console.error(\"unexpected message type\", typeof ev.data);\n return;\n }\n const e = LiveEvent.fromMessage(ev.data);\n switch (e.typ) {\n case \"patch\":\n Patch.handle(e);\n Events.rewire();\n break;\n case \"params\":\n UpdateURLParams(`${window.location.pathname}?${e.data}`);\n break;\n case \"redirect\":\n window.location.replace(e.data);\n break;\n case \"ack\":\n this.ack(e);\n break;\n case \"err\":\n EventDispatch.error();\n // Fallthrough here.\n default:\n EventDispatch.handleEvent(e);\n }\n });\n }\n\n /**\n * Send an event and keep track of it until\n * the ack event comes back.\n */\n static sendAndTrack(e: LiveEvent, element: HTMLElement) {\n if (this.ready === false) {\n console.warn(\"connection not ready for send of event\", e);\n return;\n }\n this.trackedEvents[e.id] = {\n ev: e,\n el: element,\n };\n this.conn.send(e.serialize());\n }\n\n static send(e: LiveEvent) {\n if (this.ready === false) {\n console.warn(\"connection not ready for send of event\", e);\n return;\n }\n this.conn.send(e.serialize());\n }\n\n /**\n * Called when a ack event comes in. Complete the loop\n * with any outstanding tracked events.\n */\n static ack(e: LiveEvent) {\n if (!(e.id in this.trackedEvents)) {\n return;\n }\n this.trackedEvents[e.id].el.dispatchEvent(new Event(\"ack\"));\n delete this.trackedEvents[e.id];\n }\n}\n", "import { Socket } from \"./socket\";\nimport { Events } from \"./events\";\nimport { EventDispatch, LiveEvent } from \"./event\";\nimport { Hooks, DOM } from \"./interop\";\n\nexport class Live {\n constructor(private hooks: Hooks, private dom?: DOM) {}\n\n public init() {\n // Check that this document has been rendered by live.\n if (document.querySelector(`[live-rendered]`) === null) {\n return;\n }\n // Initialise the event dispatch.\n EventDispatch.init(this.hooks, this.dom);\n\n // Dial the server.\n Socket.dial();\n\n // Initialise our live bindings.\n Events.init();\n\n // Rewire all the events.\n Events.rewire();\n }\n\n public send(typ: string, data: any, id?: number) {\n const e = new LiveEvent(typ, data, id);\n Socket.send(e);\n }\n}\n", "import { Live } from \"./live\";\nimport { Hooks } from \"./interop\";\n\ndeclare global {\n interface Window {\n Hooks: Hooks;\n Live: Live;\n }\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", (_) => {\n if (window.Live !== undefined) {\n console.error(\"window.Live already defined\");\n }\n const hooks = window.Hooks || {};\n window.Live = new Live(hooks);\n window.Live.init();\n});\n"], - "mappings": "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", + "sourcesContent": ["/**\n * Element helper class.\n */\nexport class LiveElement {\n static hook(element: HTMLElement): string | null {\n if (element.getAttribute === undefined) {\n return null;\n }\n return element.getAttribute(\"live-hook\");\n }\n}\n", "import { Socket } from \"./socket\";\nimport { LiveElement } from \"./element\";\nimport { Hook, Hooks, DOM } from \"./interop\";\n\nexport const EventMounted = \"live:mounted\";\nexport const EventBeforeUpdate = \"live:beforeupdate\";\nexport const EventUpdated = \"live:updated\";\nexport const EventBeforeDestroy = \"live:beforedestroy\";\nexport const EventDestroyed = \"live:destroyed\";\nexport const EventDisconnected = \"live:disconnected\";\nexport const EventReconnected = \"live:reconnected\";\n\nexport const ClassConnected = \"live-connected\";\nexport const ClassDisconnected = \"live-disconnected\";\nexport const ClassError = \"live-error\";\n\n/**\n * LiveEvent an event that is being passed back and forth\n * between the frontend and server.\n */\nexport class LiveEvent {\n public typ: string;\n public id: number;\n public data: any;\n private static sequence: number = 1;\n\n constructor(typ: string, data: any, id?: number) {\n this.typ = typ;\n this.data = data;\n if (id !== undefined) {\n this.id = id;\n } else {\n this.id = 0;\n }\n }\n\n /**\n * Get an ID for an event.\n */\n public static GetID(): number {\n return this.sequence++;\n }\n\n /**\n * Convert the event onto our wire format\n */\n public serialize(): string {\n return JSON.stringify({\n t: this.typ,\n i: this.id,\n d: this.data,\n });\n }\n\n /**\n * From an incoming message create a live event.\n */\n public static fromMessage(data: any): LiveEvent {\n const e = JSON.parse(data);\n return new LiveEvent(e.t, e.d, e.i);\n }\n}\n\n/**\n * EventDispatch allows the code base to send events\n * to hooked elements. Also handles events coming from\n * the server.\n */\nexport class EventDispatch {\n private static hooks: Hooks;\n private static dom?: DOM;\n private static eventHandlers: { [e: string]: ((d: any) => void)[] };\n\n constructor() {}\n\n /**\n * Must be called before usage.\n */\n static init(hooks: Hooks, dom?: DOM) {\n this.hooks = hooks;\n this.dom = dom;\n this.eventHandlers = {};\n }\n\n /**\n * Handle an event pushed from the server.\n */\n static handleEvent(ev: LiveEvent) {\n if (!(ev.typ in this.eventHandlers)) {\n return;\n }\n this.eventHandlers[ev.typ].map((h) => {\n h(ev.data);\n });\n }\n\n /**\n * Handle an element being mounted.\n */\n static mounted(element: Element) {\n const event = new CustomEvent(EventMounted, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.mounted);\n }\n\n /**\n * Before an element is updated.\n */\n static beforeUpdate(fromEl: Element, toEl: Element) {\n const event = new CustomEvent(EventBeforeUpdate, {});\n\n const h = this.getElementHooks(fromEl);\n if (h !== null) {\n this.callHook(event, fromEl, h.beforeUpdate);\n }\n\n if (\n this.dom !== undefined &&\n this.dom.onBeforeElUpdated !== undefined\n ) {\n this.dom.onBeforeElUpdated(fromEl, toEl);\n }\n }\n\n /**\n * After and element has been updated.\n */\n static updated(element: Element) {\n const event = new CustomEvent(EventUpdated, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.updated);\n }\n\n /**\n * Before an element is destroyed.\n */\n static beforeDestroy(element: Element) {\n const event = new CustomEvent(EventBeforeDestroy, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.beforeDestroy);\n }\n\n /**\n * After an element has been destroyed.\n */\n static destroyed(element: Element) {\n const event = new CustomEvent(EventDestroyed, {});\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.destroyed);\n }\n\n /**\n * Handle a disconnection event.\n */\n static disconnected() {\n const event = new CustomEvent(EventDisconnected, {});\n document.querySelectorAll(`[live-hook]`).forEach((element: Element) => {\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.disconnected);\n });\n document.body.classList.add(ClassDisconnected);\n document.body.classList.remove(ClassConnected);\n }\n\n /**\n * Handle a reconnection event.\n */\n static reconnected() {\n const event = new CustomEvent(EventReconnected, {});\n document.querySelectorAll(`[live-hook]`).forEach((element: Element) => {\n const h = this.getElementHooks(element);\n if (h === null) {\n return;\n }\n this.callHook(event, element, h.reconnected);\n });\n document.body.classList.remove(ClassDisconnected);\n document.body.classList.add(ClassConnected);\n }\n\n /**\n * Handle an error event.\n */\n static error() {\n document.body.classList.add(ClassError);\n }\n\n private static getElementHooks(element: Element): Hook | null {\n const val = LiveElement.hook(element as HTMLElement);\n if (val === null) {\n return val;\n }\n return this.hooks[val];\n }\n\n private static callHook(\n event: CustomEvent,\n el: Element,\n f: (() => void) | undefined\n ) {\n if (f === undefined) {\n return;\n }\n const pushEvent = (e: LiveEvent) => {\n Socket.send(e);\n };\n const handleEvent = (e: string, cb: (d: any) => void) => {\n if (!(e in this.eventHandlers)) {\n this.eventHandlers[e] = [];\n }\n this.eventHandlers[e].push(cb);\n };\n f.bind({ el, pushEvent, handleEvent })();\n el.dispatchEvent(event);\n }\n}\n", "/**\n * A value of an existing input in a form.\n */\ninterface inputState {\n name: string;\n focus: boolean;\n value: any;\n}\n\n/**\n * A value of a file input for validation.\n */\ninterface fileInput {\n name: string;\n lastModified: number;\n size: number;\n type: string;\n}\n\n/**\n * Form helper class.\n */\nexport class Forms {\n private static upKey = \"uploads\";\n\n private static formState: { [id: string]: inputState[] } = {};\n\n /**\n * When we are patching the DOM we need to save the state\n * of any forms so that we don't lose input values or\n * focus\n */\n static dehydrate() {\n const forms = document.querySelectorAll(\"form\");\n forms.forEach((f) => {\n if (f.id === \"\") {\n console.error(\n \"form does not have an ID. DOM updates may be affected\",\n f\n );\n return;\n }\n\n this.formState[f.id] = [];\n new FormData(f).forEach((value: any, name: string) => {\n const i = {\n name: name,\n value: value,\n focus:\n f.querySelector(`[name=\"${name}\"]`) ==\n document.activeElement,\n };\n this.formState[f.id].push(i);\n });\n });\n }\n\n /**\n * This sets the form backup to its original state.\n */\n static hydrate() {\n Object.keys(this.formState).map((formID) => {\n const form = document.querySelector(`#${formID}`);\n if (form === null) {\n delete this.formState[formID];\n return;\n }\n\n const state = this.formState[formID];\n state.map((i) => {\n const input = form.querySelector(\n `[name=\"${i.name}\"]`\n ) as HTMLInputElement;\n if (input === null) {\n return;\n }\n switch (input.type) {\n case \"file\":\n break;\n case \"checkbox\":\n if (i.value === \"on\") {\n input.checked = true;\n }\n break;\n default:\n input.value = i.value;\n if (i.focus === true) {\n input.focus();\n }\n break;\n }\n });\n });\n }\n\n /**\n * serialize form to values.\n */\n static serialize(form: HTMLFormElement): { [key: string]: string | number | fileInput } {\n const values: { [key: string]: any } = {};\n const formData = new FormData(form);\n formData.forEach((value, key) => {\n switch (true) {\n case value instanceof File:\n const file = value as File;\n const fi = {\n name: file.name,\n type: file.type,\n size: file.size,\n lastModified: file.lastModified,\n }\n if (!Reflect.has(values, this.upKey)) {\n values[this.upKey] = {};\n }\n if (!Reflect.has(values[this.upKey], key)) {\n values[this.upKey][key] = [];\n }\n values[this.upKey][key].push(fi);\n break;\n default:\n // If the key doesn't exist set it.\n if (!Reflect.has(values, key)) {\n values[key] = value;\n return;\n }\n // If it already exists that means this needs to become\n // an array.\n if (!Array.isArray(values[key])) {\n values[key] = [values[key]];\n }\n // Push the new value onto the array.\n values[key].push(value);\n }\n });\n return values;\n }\n\n /**\n * does a form have files.\n */\n static hasFiles(form: HTMLFormElement): boolean {\n const formData = new FormData(form);\n let hasFiles = false;\n formData.forEach((value) => {\n if(value instanceof File) {\n hasFiles = true;\n }\n });\n return hasFiles;\n }\n}\n", "import { LiveEvent, EventDispatch } from \"./event\";\nimport { Forms } from \"./forms\";\n\ninterface PatchEvent {\n Anchor: string;\n Action: number;\n HTML: string;\n}\n\n/**\n * Handle patches from the backend.\n */\nexport class Patch {\n static handle(event: LiveEvent) {\n Forms.dehydrate();\n\n const patches = event.data;\n patches.map(Patch.applyPatch);\n\n Forms.hydrate();\n }\n\n private static applyPatch(e: PatchEvent) {\n const target = document.querySelector(`*[${e.Anchor}]`);\n if (target === null) {\n return;\n }\n\n const newElement = Patch.html2Node(e.HTML);\n switch (e.Action) {\n case 0: // NOOP\n return;\n case 1: // REPLACE\n if (e.HTML === \"\") {\n EventDispatch.beforeDestroy(target);\n } else {\n EventDispatch.beforeUpdate(target, newElement as Element);\n }\n target.outerHTML = e.HTML;\n if (e.HTML === \"\") {\n EventDispatch.destroyed(target);\n } else {\n EventDispatch.updated(target);\n }\n break;\n case 2: // APPEND\n EventDispatch.beforeUpdate(target, newElement as Element);\n target.append(newElement);\n EventDispatch.updated(target);\n break;\n case 3: // PREPEND\n EventDispatch.beforeUpdate(target, newElement as Element);\n target.prepend(newElement);\n EventDispatch.updated(target);\n break;\n }\n }\n\n private static html2Node(html: string): Node {\n const template = document.createElement(\"template\");\n html = html.trim();\n template.innerHTML = html;\n if (template.content.firstChild === null) {\n return document.createTextNode(html);\n }\n return template.content.firstChild;\n }\n}\n", "import { Socket } from \"./socket\";\nimport { LiveEvent } from \"./event\";\n\n/**\n * A values from the \"live-value-\" attributes. As\n * well as values from the query string in the URL.\n */\nexport interface Params {\n [key: string]: any;\n}\n\n/**\n * GetParams gets the current parameters for an event. This includes\n * any from an element passed in and the URL search string.\n */\nexport function GetParams(element?: HTMLElement): Params {\n const output: Params = {};\n\n const urlParams = new URLSearchParams(window.location.search);\n urlParams.forEach((value, key) => {\n output[key] = value;\n });\n\n if (element === undefined) {\n return output;\n }\n\n if (!element.hasAttributes()) {\n return output;\n }\n const attrs = element.attributes;\n for (let i = 0; i < attrs.length; i++) {\n if (!attrs[i].name.startsWith(\"live-value-\")) {\n continue;\n }\n output[attrs[i].name.split(\"live-value-\")[1]] = attrs[i].value;\n }\n return output;\n}\n\n/**\n * GetURLParams get the params from a url path.\n */\nexport function GetURLParams(path: string): Params {\n const url = new URL(path, location.origin);\n const urlParams = new URLSearchParams(url.search);\n\n const output: Params = {};\n urlParams.forEach((value, key) => {\n output[key] = value;\n });\n\n return output;\n}\n\n/**\n * UpdateURLParams update the URL using the push state api, then\n * notify the backend.\n */\nexport function UpdateURLParams(path: string, element?: HTMLElement) {\n window.history.pushState({}, \"\", path);\n if (element === undefined) {\n Socket.send(new LiveEvent(\"params\", { ...GetURLParams(path) }));\n } else {\n const params = GetParams(element);\n Socket.sendAndTrack(\n new LiveEvent(\n \"params\",\n { ...params, ...GetURLParams(path) },\n LiveEvent.GetID()\n ),\n element\n );\n }\n}\n", "import { Socket } from \"./socket\";\nimport { Forms } from \"./forms\";\nimport { UpdateURLParams, GetParams, GetURLParams, Params } from \"./params\";\nimport { EventDispatch, LiveEvent } from \"./event\";\n\n/**\n * Standard event handler class. Clicks, focus and blur.\n */\nclass LiveHandler {\n protected limiter = new Limiter();\n\n constructor(protected event: string, protected attribute: string) {}\n\n public isWired(element: Element): boolean {\n if (element.hasAttribute(`${this.attribute}-wired`)) {\n return true;\n }\n element.setAttribute(`${this.attribute}-wired`, \"\");\n return false;\n }\n\n public attach() {\n document\n .querySelectorAll(`*[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) == true) {\n return;\n }\n const params = GetParams(element as HTMLElement);\n element.addEventListener(this.event, (e) => {\n this.limiter.limit(\n element,\n e,\n this.handler(element as HTMLFormElement, params)\n );\n });\n element.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n });\n }\n\n protected windowAttach() {\n document\n .querySelectorAll(`*[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) === true) {\n return;\n }\n const params = GetParams(element as HTMLElement);\n window.addEventListener(\n this.event,\n this.handler(element as HTMLElement, params)\n );\n window.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n });\n }\n\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (_: Event) => {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, params, LiveEvent.GetID()),\n element\n );\n };\n }\n}\n\n/**\n * KeyHandler handle key events.\n */\nexport class KeyHandler extends LiveHandler {\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (ev: Event) => {\n const ke = ev as KeyboardEvent;\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n const filter = element.getAttribute(\"live-key\");\n if (filter !== null) {\n if (ke.key !== filter) {\n return;\n }\n }\n element.classList.add(`${this.attribute}-loading`);\n const keyData = {\n key: ke.key,\n altKey: ke.altKey,\n ctrlKey: ke.ctrlKey,\n shiftKey: ke.shiftKey,\n metaKey: ke.metaKey,\n };\n Socket.sendAndTrack(\n new LiveEvent(t, { ...params, ...keyData }, LiveEvent.GetID()),\n element\n );\n };\n }\n}\n\nclass Limiter {\n private debounceAttr = \"live-debounce\";\n private debounceEvent: any;\n\n private throttleAttr = \"live-throttle\";\n private throttleEvent: any;\n private throttleFunc: any;\n\n public hasDebounce(element: Element): boolean {\n return element.hasAttribute(this.debounceAttr);\n }\n\n public hasThrottle(element: Element): boolean {\n return element.hasAttribute(this.throttleAttr);\n }\n\n public debounce(element: Element, e: Event, fn: EventListener) {\n clearTimeout(this.debounceEvent);\n const debounce = element.getAttribute(this.debounceAttr);\n if (!this.hasDebounce(element) || debounce === null) {\n fn(e);\n return;\n }\n if (debounce === \"blur\") {\n this.debounceEvent = fn;\n element.addEventListener(\"blur\", () => {\n this.debounceEvent();\n });\n return;\n }\n this.debounceEvent = setTimeout(() => {\n fn(e);\n }, parseInt(debounce));\n }\n\n public throttle(element: Element, e: Event, fn: EventListener) {\n const throttle = element.getAttribute(this.throttleAttr);\n if (!this.hasThrottle(element) || throttle === null) {\n fn(e);\n return;\n }\n if (this.throttleEvent) {\n this.throttleFunc = () => { fn(e); }\n } else {\n fn(e);\n this.throttleEvent = setTimeout(() => {\n if (this.throttleFunc) {\n this.throttleFunc();\n this.throttleFunc = null;\n this.throttleEvent = null;\n }\n }, parseInt(throttle));\n }\n }\n\n public limit(element: Element, e: Event, fn: EventListener) {\n this.debounce(element, e, (ev: Event) => {\n this.throttle(element, ev, fn);\n });\n }\n}\n\n/**\n * live-click attribute handling.\n */\nclass Click extends LiveHandler {\n constructor() {\n super(\"click\", \"live-click\");\n }\n}\n\n/**\n * live-contextmenu attribute handling.\n */\nclass Contextmenu extends LiveHandler {\n constructor() {\n super(\"contextmenu\", \"live-contextmenu\");\n }\n}\n\n/**\n * live-mousedown attribute handling.\n */\nclass Mousedown extends LiveHandler {\n constructor() {\n super(\"mousedown\", \"live-mousedown\");\n }\n}\n\n/**\n * live-mouseup attribute handling.\n */\nclass Mouseup extends LiveHandler {\n constructor() {\n super(\"mouseup\", \"live-mouseup\");\n }\n}\n\n/**\n * live-focus event handling.\n */\nclass Focus extends LiveHandler {\n constructor() {\n super(\"focus\", \"live-focus\");\n }\n}\n\n/**\n * live-blur event handling.\n */\nclass Blur extends LiveHandler {\n constructor() {\n super(\"blur\", \"live-blur\");\n }\n}\n\n/**\n * live-window-focus event handler.\n */\nclass WindowFocus extends LiveHandler {\n constructor() {\n super(\"focus\", \"live-window-focus\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-window-blur event handler.\n */\nclass WindowBlur extends LiveHandler {\n constructor() {\n super(\"blur\", \"live-window-blur\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-keydown event handler.\n */\nclass Keydown extends KeyHandler {\n constructor() {\n super(\"keydown\", \"live-keydown\");\n }\n}\n\n/**\n * live-keyup event handler.\n */\nclass Keyup extends KeyHandler {\n constructor() {\n super(\"keyup\", \"live-keyup\");\n }\n}\n\n/**\n * live-window-keydown event handler.\n */\nclass WindowKeydown extends KeyHandler {\n constructor() {\n super(\"keydown\", \"live-window-keydown\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-window-keyup event handler.\n */\nclass WindowKeyup extends KeyHandler {\n constructor() {\n super(\"keyup\", \"live-window-keyup\");\n }\n\n public attach() {\n this.windowAttach();\n }\n}\n\n/**\n * live-change form handler.\n */\nclass Change {\n protected attribute = \"live-change\";\n protected limiter = new Limiter();\n\n constructor() {}\n\n public isWired(element: Element): boolean {\n if (element.hasAttribute(`${this.attribute}-wired`)) {\n return true;\n }\n element.setAttribute(`${this.attribute}-wired`, \"\");\n return false;\n }\n \n public attach() {\n let forms: Element[] = [];\n document\n .querySelectorAll(`form[${this.attribute}]`)\n .forEach((element: Element) => {\n element.addEventListener(\"ack\", (_) => {\n element.classList.remove(`${this.attribute}-loading`);\n });\n forms.push(element);\n element\n .querySelectorAll(`input,select,textarea`)\n .forEach((childElement: Element) => {\n this.addEvent(element, childElement);\n });\n });\n forms.forEach((element: Element) => {\n document\n .querySelectorAll(`[form=${element.getAttribute(\"id\")}]`)\n .forEach((childElement) => {\n this.addEvent(element, childElement);\n });\n });\n };\n\n private addEvent(element: Element, childElement: Element) {\n if (this.isWired(childElement)) {\n return;\n }\n childElement.addEventListener(\"input\", (e) => {\n this.limiter.limit(childElement, e, () => {\n this.handler(element as HTMLFormElement);\n });\n });\n }\n\n private handler(element: HTMLFormElement) {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n const values: { [key: string]: any } = Forms.serialize(element);\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, values, LiveEvent.GetID()),\n element\n );\n }\n}\n\n/**\n * live-submit form handler.\n */\nclass Submit extends LiveHandler {\n constructor() {\n super(\"submit\", \"live-submit\");\n }\n\n protected handler(element: HTMLElement, params: Params): EventListener {\n return (e: Event) => {\n if (e.preventDefault) e.preventDefault();\n\n const hasFiles = Forms.hasFiles(element as HTMLFormElement);\n if (hasFiles === true) {\n const request = new XMLHttpRequest();\n request.open(\"POST\", \"\");\n request.addEventListener('load', () => {\n this.sendEvent(element, params);\n });\n\n request.send(new FormData(element as HTMLFormElement));\n } else {\n this.sendEvent(element, params);\n }\n return false;\n };\n }\n\n protected sendEvent(element: HTMLElement, params: Params) {\n const t = element?.getAttribute(this.attribute);\n if (t === null) {\n return;\n }\n\n var vals = { ...params };\n\n const data: { [key: string]: any } = Forms.serialize(\n element as HTMLFormElement\n );\n Object.keys(data).map((k) => {\n vals[k] = data[k];\n });\n element.classList.add(`${this.attribute}-loading`);\n Socket.sendAndTrack(\n new LiveEvent(t, vals, LiveEvent.GetID()),\n element\n );\n }\n}\n\n/**\n * live-hook event handler.\n */\nclass Hook extends LiveHandler {\n constructor() {\n super(\"\", \"live-hook\");\n }\n\n public attach() {\n document\n .querySelectorAll(`[${this.attribute}]`)\n .forEach((element: Element) => {\n if (this.isWired(element) == true) {\n return;\n }\n EventDispatch.mounted(element);\n });\n }\n}\n\n/**\n * live-patch event handler.\n */\nclass Patch extends LiveHandler {\n constructor() {\n super(\"click\", \"live-patch\");\n }\n\n protected handler(element: HTMLElement, _: Params): EventListener {\n return (e: Event) => {\n if (e.preventDefault) e.preventDefault();\n const path = element.getAttribute(\"href\");\n if (path === null) {\n return;\n }\n UpdateURLParams(path, element);\n return false;\n };\n }\n}\n\n/**\n * Handle all events.\n */\nexport class Events {\n private static clicks: Click;\n private static contextmenu: Contextmenu;\n private static mousedown: Mousedown;\n private static mouseup: Mouseup;\n private static focus: Focus;\n private static blur: Blur;\n private static windowFocus: WindowFocus;\n private static windowBlur: WindowBlur;\n private static keydown: Keydown;\n private static keyup: Keyup;\n private static windowKeydown: WindowKeydown;\n private static windowKeyup: WindowKeyup;\n private static change: Change;\n private static submit: Submit;\n private static hook: Hook;\n private static patch: Patch;\n\n /**\n * Initialise all the event wiring.\n */\n public static init() {\n this.clicks = new Click();\n this.contextmenu = new Contextmenu();\n this.mousedown = new Mousedown();\n this.mouseup = new Mouseup();\n this.focus = new Focus();\n this.blur = new Blur();\n this.windowFocus = new WindowFocus();\n this.windowBlur = new WindowBlur();\n this.keydown = new Keydown();\n this.keyup = new Keyup();\n this.windowKeydown = new WindowKeydown();\n this.windowKeyup = new WindowKeyup();\n this.change = new Change();\n this.submit = new Submit();\n this.hook = new Hook();\n this.patch = new Patch();\n\n this.handleBrowserNav();\n }\n\n /**\n * Re-attach all events when we have re-rendered.\n */\n public static rewire() {\n this.clicks.attach();\n this.contextmenu.attach();\n this.mousedown.attach();\n this.mouseup.attach();\n this.focus.attach();\n this.blur.attach();\n this.windowFocus.attach();\n this.windowBlur.attach();\n this.keydown.attach();\n this.keyup.attach();\n this.windowKeyup.attach();\n this.windowKeydown.attach();\n this.change.attach();\n this.submit.attach();\n this.hook.attach();\n this.patch.attach();\n }\n\n /**\n * Watch the browser popstate so that we can send a params\n * change event to the server.\n */\n private static handleBrowserNav() {\n window.onpopstate = function (_: any) {\n Socket.send(\n new LiveEvent(\n \"params\",\n GetURLParams(document.location.search),\n LiveEvent.GetID()\n )\n );\n };\n }\n}\n", "import { EventDispatch, LiveEvent } from \"./event\";\nimport { Patch } from \"./patch\";\nimport { Events } from \"./events\";\nimport { UpdateURLParams } from \"./params\";\n\n/**\n * Represents the websocket connection to\n * the backend server.\n */\nexport class Socket {\n private static conn: WebSocket;\n private static ready: boolean = false;\n private static disconnectNotified: boolean = false;\n\n private static trackedEvents: {\n [id: number]: { ev: LiveEvent; el: HTMLElement };\n };\n\n constructor() {}\n\n static dial() {\n this.trackedEvents = {};\n\n console.debug(\"Socket.dial called\");\n this.conn = new WebSocket(\n `${location.protocol === \"https:\" ? \"wss\" : \"ws\"}://${\n location.host\n }${location.pathname}${location.search}${location.hash}`\n );\n this.conn.addEventListener(\"close\", (ev) => {\n this.ready = false;\n console.warn(\n `WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`\n );\n if (ev.code !== 1001) {\n if (this.disconnectNotified === false) {\n EventDispatch.disconnected();\n this.disconnectNotified = true;\n }\n setTimeout(() => {\n Socket.dial();\n }, 1000);\n }\n });\n // Ping on open.\n this.conn.addEventListener(\"open\", (_) => {\n EventDispatch.reconnected();\n this.disconnectNotified = false;\n this.ready = true;\n });\n this.conn.addEventListener(\"message\", (ev) => {\n if (typeof ev.data !== \"string\") {\n console.error(\"unexpected message type\", typeof ev.data);\n return;\n }\n const e = LiveEvent.fromMessage(ev.data);\n switch (e.typ) {\n case \"patch\":\n Patch.handle(e);\n Events.rewire();\n break;\n case \"params\":\n UpdateURLParams(`${window.location.pathname}?${e.data}`);\n break;\n case \"redirect\":\n window.location.replace(e.data);\n break;\n case \"ack\":\n this.ack(e);\n break;\n case \"err\":\n EventDispatch.error();\n // Fallthrough here.\n default:\n EventDispatch.handleEvent(e);\n }\n });\n }\n\n /**\n * Send an event and keep track of it until\n * the ack event comes back.\n */\n static sendAndTrack(e: LiveEvent, element: HTMLElement) {\n if (this.ready === false) {\n console.warn(\"connection not ready for send of event\", e);\n return;\n }\n this.trackedEvents[e.id] = {\n ev: e,\n el: element,\n };\n this.conn.send(e.serialize());\n }\n\n static send(e: LiveEvent) {\n if (this.ready === false) {\n console.warn(\"connection not ready for send of event\", e);\n return;\n }\n this.conn.send(e.serialize());\n }\n\n /**\n * Called when a ack event comes in. Complete the loop\n * with any outstanding tracked events.\n */\n static ack(e: LiveEvent) {\n if (!(e.id in this.trackedEvents)) {\n return;\n }\n this.trackedEvents[e.id].el.dispatchEvent(new Event(\"ack\"));\n delete this.trackedEvents[e.id];\n }\n}\n", "import { Socket } from \"./socket\";\nimport { Events } from \"./events\";\nimport { EventDispatch, LiveEvent } from \"./event\";\nimport { Hooks, DOM } from \"./interop\";\n\nexport class Live {\n constructor(private hooks: Hooks, private dom?: DOM) {}\n\n public init() {\n // Check that this document has been rendered by live.\n if (document.querySelector(`[live-rendered]`) === null) {\n return;\n }\n // Initialise the event dispatch.\n EventDispatch.init(this.hooks, this.dom);\n\n // Dial the server.\n Socket.dial();\n\n // Initialise our live bindings.\n Events.init();\n\n // Rewire all the events.\n Events.rewire();\n }\n\n public send(typ: string, data: any, id?: number) {\n const e = new LiveEvent(typ, data, id);\n Socket.send(e);\n }\n}\n", "import { Live } from \"./live\";\nimport { Hooks } from \"./interop\";\n\ndeclare global {\n interface Window {\n Hooks: Hooks;\n Live: Live;\n }\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", (_) => {\n if (window.Live !== undefined) {\n console.error(\"window.Live already defined\");\n }\n const hooks = window.Hooks || {};\n window.Live = new Live(hooks);\n window.Live.init();\n});\n"], + "mappings": "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", "names": [] } diff --git a/web/src/events.ts b/web/src/events.ts index 81d54de..4665f6a 100644 --- a/web/src/events.ts +++ b/web/src/events.ts @@ -28,15 +28,11 @@ class LiveHandler { } const params = GetParams(element as HTMLElement); element.addEventListener(this.event, (e) => { - if (this.limiter.hasDebounce(element)) { - this.limiter.debounce( - element, - e, - this.handler(element as HTMLFormElement, params) - ); - } else { - this.handler(element as HTMLFormElement, params)(e); - } + this.limiter.limit( + element, + e, + this.handler(element as HTMLFormElement, params) + ); }); element.addEventListener("ack", (_) => { element.classList.remove(`${this.attribute}-loading`); @@ -114,18 +110,22 @@ class Limiter { private debounceAttr = "live-debounce"; private debounceEvent: any; + private throttleAttr = "live-throttle"; + private throttleEvent: any; + private throttleFunc: any; + public hasDebounce(element: Element): boolean { return element.hasAttribute(this.debounceAttr); } + public hasThrottle(element: Element): boolean { + return element.hasAttribute(this.throttleAttr); + } + public debounce(element: Element, e: Event, fn: EventListener) { clearTimeout(this.debounceEvent); - if (!this.hasDebounce(element)) { - fn(e); - return; - } const debounce = element.getAttribute(this.debounceAttr); - if (debounce === null) { + if (!this.hasDebounce(element) || debounce === null) { fn(e); return; } @@ -140,6 +140,32 @@ class Limiter { fn(e); }, parseInt(debounce)); } + + public throttle(element: Element, e: Event, fn: EventListener) { + const throttle = element.getAttribute(this.throttleAttr); + if (!this.hasThrottle(element) || throttle === null) { + fn(e); + return; + } + if (this.throttleEvent) { + this.throttleFunc = () => { fn(e); } + } else { + fn(e); + this.throttleEvent = setTimeout(() => { + if (this.throttleFunc) { + this.throttleFunc(); + this.throttleFunc = null; + this.throttleEvent = null; + } + }, parseInt(throttle)); + } + } + + public limit(element: Element, e: Event, fn: EventListener) { + this.debounce(element, e, (ev: Event) => { + this.throttle(element, ev, fn); + }); + } } /** @@ -312,13 +338,9 @@ class Change { return; } childElement.addEventListener("input", (e) => { - if (this.limiter.hasDebounce(childElement)) { - this.limiter.debounce(childElement, e, () => { - this.handler(element as HTMLFormElement); - }); - } else { + this.limiter.limit(childElement, e, () => { this.handler(element as HTMLFormElement); - } + }); }); }