From 48394b9a69fcbe7864f235f0803fb99305905487 Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Thu, 15 Feb 2024 17:01:19 +0000 Subject: :wrench: Build the server component runtime. --- priv/lustre-server-component.min.mjs | 1 - priv/lustre-server-component.mjs | 558 ---------------------------- priv/static/lustre-server-component.min.mjs | 1 + priv/static/lustre-server-component.mjs | 542 +++++++++++++++++++++++++++ 4 files changed, 543 insertions(+), 559 deletions(-) delete mode 100644 priv/lustre-server-component.min.mjs delete mode 100644 priv/lustre-server-component.mjs create mode 100644 priv/static/lustre-server-component.min.mjs create mode 100644 priv/static/lustre-server-component.mjs (limited to 'priv') diff --git a/priv/lustre-server-component.min.mjs b/priv/lustre-server-component.min.mjs deleted file mode 100644 index bc05808..0000000 --- a/priv/lustre-server-component.min.mjs +++ /dev/null @@ -1 +0,0 @@ -var f=class{withFields(e){let n=Object.keys(this).map(l=>l in e?e[l]:this[l]);return new this.constructor(...n)}},w=class{static fromArray(e,n){let l=n||new p;return e.reduceRight((s,i)=>new $(i,s),l)}[Symbol.iterator](){return new k(this)}toArray(){return[...this]}atLeastLength(e){for(let n of this){if(e<=0)return!0;e--}return e<=0}hasLength(e){for(let n of this){if(e<=0)return!1;e--}return e===0}countLength(){let e=0;for(let n of this)e++;return e}};var k=class{#t;constructor(e){this.#t=e}next(){if(this.#t instanceof p)return{done:!0};{let{head:e,tail:n}=this.#t;return this.#t=n,{value:e,done:!1}}}},p=class extends w{},$=class extends w{constructor(e,n){super(),this.head=e,this.tail=n}};var x=class t extends f{static isResult(e){return e instanceof t}},h=class extends x{constructor(e){super(),this[0]=e}isOk(){return!0}},c=class extends x{constructor(e){super(),this[0]=e}isOk(){return!1}};function d(t,e,n,l,s,i){let r=new globalThis.Error(s);r.gleam_error=t,r.module=e,r.line=n,r.fn=l;for(let a in i)r[a]=i[a];return r}var Bt=new DataView(new ArrayBuffer(8));var Q=5,A=Math.pow(2,Q),Dt=A-1,Rt=A/2,Ut=A/4;function O(t,e){if(t.isOk()){let n=t[0];return new h(e(n))}else{if(t.isOk())throw d("case_no_match","gleam/result",67,"map","No case clause matched",{values:[t]});{let n=t[0];return new c(n)}}}function o(t,e,n,l){if(e?.tag&&t?.nodeType===1){let s=e.tag.toUpperCase(),i=e.namespace||"http://www.w3.org/1999/xhtml";return t.nodeName===s&&t.namespaceURI==i?te(t,e,n,l):D(t,e,n,l)}return e?.tag?D(t,e,n,l):typeof e?.content=="string"?t?.nodeType===3?ne(t,e):re(t,e):document.createComment(["[internal lustre error] I couldn't work out how to render this element. This","function should only be called internally by lustre's runtime: if you think","this is an error, please open an issue at","https://github.com/hayleigh-dot-dev/gleam-lustre/issues/new"].join(" "))}function R(t,e,n){for(let l of e[0]){let s=l[0];if(s==="0")o(t,l[1],n,t.parentNode);else{let i=Array.from(s),r=i.slice(0,-1).join(""),a=i.slice(-1)[0],u=t.querySelector(`[data-lustre-key="${s}"]`)??t.querySelector(`[data-lustre-key="${r}"]`).childNodes[a];o(u,l[1],n,u.parentNode)}}for(let l of e[1]){let s=l[0],i=Array.from(s),r=i.slice(0,-1).join(""),a=i.slice(-1)[0];(t.querySelector(`[data-lustre-key="${s}"]`)??t.querySelector(`[data-lustre-key="${r}"]`).childNodes[a]).remove()}for(let l of e[2]){let s=l[0],i=s==="0"?t:t.querySelector(`[data-lustre-key="${s}"]`);i.$lustre??={__registered_events:new Set};for(let r of l[0])g(i,r.name,r.value,n);for(let r of l[1])if(i.$lustre.__registered_events.has(r)){let a=r.slice(2).toLowerCase();i.removeEventListener(a,i.$lustre[`${r}Handler`]),i.$lustre.__registered_events.delete(r),delete i.$lustre[r],delete i.$lustre[`${r}Handler`]}else i.removeAttribute(r)}return t}function D(t,e,n,l=null){let s=e.namespace?document.createElementNS(e.namespace,e.tag):document.createElement(e.tag);s.$lustre={__registered_events:new Set};let i="";for(let r of e.attrs)r[0]==="class"?g(s,r[0],`${s.className} ${r[1]}`):r[0]==="style"?g(s,r[0],`${s.style.cssText} ${r[1]}`):r[0]==="dangerous-unescaped-html"?i+=r[1]:r[0]!==""&&g(s,r[0],r[1],n);if(customElements.get(e.tag))s._slot=e.children;else if(e.tag==="slot"){let r=new p,a=l;for(;a;)if(a._slot){r=a._slot;break}else a=a.parentNode;for(let u of r)s.appendChild(o(null,u,n,s))}else if(i)s.innerHTML=i;else for(let r of e.children)s.appendChild(o(null,r,n,s));return t&&t.replaceWith(s),s}function te(t,e,n,l){let s=t.attributes,i=new Map;t.$lustre??={__registered_events:new Set};for(let r of e.attrs)r[0]==="class"&&i.has("class")?i.set(r[0],`${i.get("class")} ${r[1]}`):r[0]==="style"&&i.has("style")?i.set(r[0],`${i.get("style")} ${r[1]}`):r[0]==="dangerous-unescaped-html"&&i.has("dangerous-unescaped-html")?i.set(r[0],`${i.get("dangerous-unescaped-html")} ${r[1]}`):r[0]!==""&&i.set(r[0],r[1]);for(let{name:r,value:a}of s)if(!i.has(r))t.removeAttribute(r);else{let u=i.get(r);u!==a&&(g(t,r,u,n),i.delete(r))}for(let r of t.$lustre.__registered_events)if(!i.has(r)){let a=r.slice(2).toLowerCase();t.removeEventListener(a,t.$lustre[`${r}Handler`]),t.$lustre.__registered_events.delete(r),delete t.$lustre[r],delete t.$lustre[`${r}Handler`]}for(let[r,a]of i)g(t,r,a,n);if(customElements.get(e.tag))t._slot=e.children;else if(e.tag==="slot"){let r=t.firstChild,a=new p,u=l;for(;u;)if(u._slot){a=u._slot;break}else u=u.parentNode;for(;r;)Array.isArray(a)&&a.length?o(r,a.shift(),n,t):a.head&&(o(r,a.head,n,t),a=a.tail),r=r.nextSibling;for(let _ of a)t.appendChild(o(null,_,n,t))}else if(i.has("dangerous-unescaped-html"))t.innerHTML=i.get("dangerous-unescaped-html");else{let r=t.firstChild,a=e.children;for(;r;)if(Array.isArray(a)&&a.length){let u=r.nextSibling;o(r,a.shift(),n,t),r=u}else if(a.head){let u=r.nextSibling;o(r,a.head,n,t),a=a.tail,r=u}else{let u=r.nextSibling;r.remove(),r=u}for(let u of a)t.appendChild(o(null,u,n,t))}return t}function g(t,e,n,l){switch(typeof n){case(e.startsWith("data-lustre-on-")&&"string"):{if(!n){t.removeAttribute(e),t.removeEventListener(s,t.$lustre[`${e}Handler`]);break}if(t.hasAttribute(e))break;let s=e.slice(15).toLowerCase(),i=r=>l(se(r));t.$lustre[`${e}Handler`]&&t.removeEventListener(s,t.$lustre[`${e}Handler`]),t.addEventListener(s,i),t.$lustre[e]=n,t.$lustre[`${e}Handler`]=i,t.$lustre.__registered_events.add(e),t.setAttribute(e,n);break}case"string":t.getAttribute(e)!==n&&t.setAttribute(e,n),n===""&&t.removeAttribute(e),e==="value"&&t.value!==n&&(t.value=n);break;case(e.startsWith("on")&&"function"):{if(t.$lustre[e]===n)break;let s=e.slice(2).toLowerCase(),i=r=>O(n(r),l);t.$lustre[`${e}Handler`]&&t.removeEventListener(s,t.$lustre[`${e}Handler`]),t.addEventListener(s,i),t.$lustre[e]=n,t.$lustre[`${e}Handler`]=i,t.$lustre.__registered_events.add(e);break}default:t[e]=n}}function re(t,e){let n=document.createTextNode(e.content);return t&&t.replaceWith(n),n}function ne(t,e){let n=t.nodeValue,l=e.content;return l?(n!==l&&(t.nodeValue=l),t):(t?.remove(),null)}function se(t){let e=t.target,n=e.getAttribute(`data-lustre-on-${t.type}`),l=JSON.parse(e.getAttribute("data-lustre-data")||"{}"),s=JSON.parse(e.getAttribute("data-lustre-include")||"[]");switch(t.type){case"input":case"change":s.push("target.value");break}return{tag:n,data:s.reduce((i,r)=>{let a=r.split(".");for(let u=0,_=i,b=t;u{let n=[];for(let l of e)if(l.type==="attributes"){let{attributeName:s,oldValue:i}=l,r=this.getAttribute(s);if(i!==r)try{n.push([s,JSON.parse(r)])}catch{n.push([s,r])}}n.length&&this.#e?.send(JSON.stringify([5,n]))})}connectedCallback(){this.#r=document.createElement("div"),this.appendChild(this.#r)}attributeChangedCallback(e,n,l){switch(e){case"route":if(!l)this.#e?.close(),this.#e=null;else if(n!==l){let s=this.getAttribute("id"),i=l+(s?`?id=${s}`:"");this.#e?.close(),this.#e=new WebSocket(`ws://${window.location.host}${i}`),this.#e.addEventListener("message",({data:r})=>{let[a,...u]=JSON.parse(r);switch(a){case 0:return this.diff(u);case 1:return this.emit(u);case 2:return this.init(u)}})}}}init([e,n]){let l=[];for(let s of e)s in this?l.push([s,this[s]]):this.hasAttribute(s)&&l.push([s,this.getAttribute(s)]),Object.defineProperty(this,s,{get(){return this[`_${s}`]??this.getAttribute(s)},set(i){let r=this[s];typeof i=="string"?this.setAttribute(s,i):this[`_${s}`]=i,r!==i&&this.#e?.send(JSON.stringify([5,[[s,i]]]))}});this.#t.observe(this,{attributeFilter:e,attributeOldValue:!0,attributes:!0,characterData:!1,characterDataOldValue:!1,childList:!1,subtree:!1}),this.morph(n),l.length&&this.#e?.send(JSON.stringify([5,l]))}morph(e){this.#r=o(this.#r,e,n=>{this.#e?.send(JSON.stringify([4,n.tag,n.data]))})}diff([e]){this.#r=R(this.#r,e,n=>{this.#e?.send(JSON.stringify([4,n.tag,n.data]))})}emit([e,n]){this.dispatchEvent(new CustomEvent(e,{detail:n}))}disconnectedCallback(){this.#e?.close()}};window.customElements.define("lustre-server-component",S);export{S as LustreServerComponent}; diff --git a/priv/lustre-server-component.mjs b/priv/lustre-server-component.mjs deleted file mode 100644 index 8230074..0000000 --- a/priv/lustre-server-component.mjs +++ /dev/null @@ -1,558 +0,0 @@ -// build/dev/javascript/lustre/lustre/internals/constants.mjs -var diff = 0; -var emit = 1; -var init = 2; -var event = 4; -var attrs = 5; - -// build/dev/javascript/prelude.mjs -var CustomType = class { - withFields(fields) { - let properties = Object.keys(this).map( - (label) => label in fields ? fields[label] : this[label] - ); - return new this.constructor(...properties); - } -}; -var List = class { - static fromArray(array, tail) { - let t = tail || new Empty(); - return array.reduceRight((xs, x) => new NonEmpty(x, xs), t); - } - [Symbol.iterator]() { - return new ListIterator(this); - } - toArray() { - return [...this]; - } - atLeastLength(desired) { - for (let _ of this) { - if (desired <= 0) - return true; - desired--; - } - return desired <= 0; - } - hasLength(desired) { - for (let _ of this) { - if (desired <= 0) - return false; - desired--; - } - return desired === 0; - } - countLength() { - let length2 = 0; - for (let _ of this) - length2++; - return length2; - } -}; -var ListIterator = class { - #current; - constructor(current) { - this.#current = current; - } - next() { - if (this.#current instanceof Empty) { - return { done: true }; - } else { - let { head, tail } = this.#current; - this.#current = tail; - return { value: head, done: false }; - } - } -}; -var Empty = class extends List { -}; -var NonEmpty = class extends List { - constructor(head, tail) { - super(); - this.head = head; - this.tail = tail; - } -}; -var Result = class _Result extends CustomType { - static isResult(data) { - return data instanceof _Result; - } -}; -var Ok = class extends Result { - constructor(value) { - super(); - this[0] = value; - } - isOk() { - return true; - } -}; -var Error = class extends Result { - constructor(detail) { - super(); - this[0] = detail; - } - isOk() { - return false; - } -}; -function makeError(variant, module, line, fn, message, extra) { - let error = new globalThis.Error(message); - error.gleam_error = variant; - error.module = module; - error.line = line; - error.fn = fn; - for (let k in extra) - error[k] = extra[k]; - return error; -} - -// build/dev/javascript/gleam_stdlib/dict.mjs -var tempDataView = new DataView(new ArrayBuffer(8)); -var SHIFT = 5; -var BUCKET_SIZE = Math.pow(2, SHIFT); -var MASK = BUCKET_SIZE - 1; -var MAX_INDEX_NODE = BUCKET_SIZE / 2; -var MIN_ARRAY_NODE = BUCKET_SIZE / 4; - -// build/dev/javascript/gleam_stdlib/gleam/result.mjs -function map2(result, fun) { - if (result.isOk()) { - let x = result[0]; - return new Ok(fun(x)); - } else if (!result.isOk()) { - let e = result[0]; - return new Error(e); - } else { - throw makeError( - "case_no_match", - "gleam/result", - 67, - "map", - "No case clause matched", - { values: [result] } - ); - } -} - -// build/dev/javascript/lustre/vdom.ffi.mjs -function morph(prev, curr, dispatch, parent) { - if (curr?.tag && prev?.nodeType === 1) { - const nodeName = curr.tag.toUpperCase(); - const ns = curr.namespace || "http://www.w3.org/1999/xhtml"; - if (prev.nodeName === nodeName && prev.namespaceURI == ns) { - return morphElement(prev, curr, dispatch, parent); - } else { - return createElement(prev, curr, dispatch, parent); - } - } - if (curr?.tag) { - return createElement(prev, curr, dispatch, parent); - } - if (typeof curr?.content === "string") { - return prev?.nodeType === 3 ? morphText(prev, curr) : createText(prev, curr); - } - return document.createComment( - [ - "[internal lustre error] I couldn't work out how to render this element. This", - "function should only be called internally by lustre's runtime: if you think", - "this is an error, please open an issue at", - "https://github.com/hayleigh-dot-dev/gleam-lustre/issues/new" - ].join(" ") - ); -} -function patch(root, diff2, dispatch) { - for (const created of diff2[0]) { - const key = created[0]; - if (key === "0") { - morph(root, created[1], dispatch, root.parentNode); - } else { - const segments = Array.from(key); - const parentKey = segments.slice(0, -1).join(""); - const indexKey = segments.slice(-1)[0]; - const prev = root.querySelector(`[data-lustre-key="${key}"]`) ?? root.querySelector(`[data-lustre-key="${parentKey}"]`).childNodes[indexKey]; - morph(prev, created[1], dispatch, prev.parentNode); - } - } - for (const removed of diff2[1]) { - const key = removed[0]; - const segments = Array.from(key); - const parentKey = segments.slice(0, -1).join(""); - const indexKey = segments.slice(-1)[0]; - const prev = root.querySelector(`[data-lustre-key="${key}"]`) ?? root.querySelector(`[data-lustre-key="${parentKey}"]`).childNodes[indexKey]; - prev.remove(); - } - for (const updated of diff2[2]) { - const key = updated[0]; - const prev = key === "0" ? root : root.querySelector(`[data-lustre-key="${key}"]`); - prev.$lustre ??= { __registered_events: /* @__PURE__ */ new Set() }; - for (const created of updated[0]) { - morphAttr(prev, created.name, created.value, dispatch); - } - for (const removed of updated[1]) { - if (prev.$lustre.__registered_events.has(removed)) { - const event2 = removed.slice(2).toLowerCase(); - prev.removeEventListener(event2, prev.$lustre[`${removed}Handler`]); - prev.$lustre.__registered_events.delete(removed); - delete prev.$lustre[removed]; - delete prev.$lustre[`${removed}Handler`]; - } else { - prev.removeAttribute(removed); - } - } - } - return root; -} -function createElement(prev, curr, dispatch, parent = null) { - const el = curr.namespace ? document.createElementNS(curr.namespace, curr.tag) : document.createElement(curr.tag); - el.$lustre = { - __registered_events: /* @__PURE__ */ new Set() - }; - let dangerousUnescapedHtml = ""; - for (const attr of curr.attrs) { - if (attr[0] === "class") { - morphAttr(el, attr[0], `${el.className} ${attr[1]}`); - } else if (attr[0] === "style") { - morphAttr(el, attr[0], `${el.style.cssText} ${attr[1]}`); - } else if (attr[0] === "dangerous-unescaped-html") { - dangerousUnescapedHtml += attr[1]; - } else if (attr[0] !== "") { - morphAttr(el, attr[0], attr[1], dispatch); - } - } - if (customElements.get(curr.tag)) { - el._slot = curr.children; - } else if (curr.tag === "slot") { - let children = new Empty(); - let parentWithSlot = parent; - while (parentWithSlot) { - if (parentWithSlot._slot) { - children = parentWithSlot._slot; - break; - } else { - parentWithSlot = parentWithSlot.parentNode; - } - } - for (const child of children) { - el.appendChild(morph(null, child, dispatch, el)); - } - } else if (dangerousUnescapedHtml) { - el.innerHTML = dangerousUnescapedHtml; - } else { - for (const child of curr.children) { - el.appendChild(morph(null, child, dispatch, el)); - } - } - if (prev) - prev.replaceWith(el); - return el; -} -function morphElement(prev, curr, dispatch, parent) { - const prevAttrs = prev.attributes; - const currAttrs = /* @__PURE__ */ new Map(); - prev.$lustre ??= { __registered_events: /* @__PURE__ */ new Set() }; - for (const currAttr of curr.attrs) { - if (currAttr[0] === "class" && currAttrs.has("class")) { - currAttrs.set(currAttr[0], `${currAttrs.get("class")} ${currAttr[1]}`); - } else if (currAttr[0] === "style" && currAttrs.has("style")) { - currAttrs.set(currAttr[0], `${currAttrs.get("style")} ${currAttr[1]}`); - } else if (currAttr[0] === "dangerous-unescaped-html" && currAttrs.has("dangerous-unescaped-html")) { - currAttrs.set( - currAttr[0], - `${currAttrs.get("dangerous-unescaped-html")} ${currAttr[1]}` - ); - } else if (currAttr[0] !== "") { - currAttrs.set(currAttr[0], currAttr[1]); - } - } - for (const { name, value: prevValue } of prevAttrs) { - if (!currAttrs.has(name)) { - prev.removeAttribute(name); - } else { - const value = currAttrs.get(name); - if (value !== prevValue) { - morphAttr(prev, name, value, dispatch); - currAttrs.delete(name); - } - } - } - for (const name of prev.$lustre.__registered_events) { - if (!currAttrs.has(name)) { - const event2 = name.slice(2).toLowerCase(); - prev.removeEventListener(event2, prev.$lustre[`${name}Handler`]); - prev.$lustre.__registered_events.delete(name); - delete prev.$lustre[name]; - delete prev.$lustre[`${name}Handler`]; - } - } - for (const [name, value] of currAttrs) { - morphAttr(prev, name, value, dispatch); - } - if (customElements.get(curr.tag)) { - prev._slot = curr.children; - } else if (curr.tag === "slot") { - let prevChild = prev.firstChild; - let currChild = new Empty(); - let parentWithSlot = parent; - while (parentWithSlot) { - if (parentWithSlot._slot) { - currChild = parentWithSlot._slot; - break; - } else { - parentWithSlot = parentWithSlot.parentNode; - } - } - while (prevChild) { - if (Array.isArray(currChild) && currChild.length) { - morph(prevChild, currChild.shift(), dispatch, prev); - } else if (currChild.head) { - morph(prevChild, currChild.head, dispatch, prev); - currChild = currChild.tail; - } - prevChild = prevChild.nextSibling; - } - for (const child of currChild) { - prev.appendChild(morph(null, child, dispatch, prev)); - } - } else if (currAttrs.has("dangerous-unescaped-html")) { - prev.innerHTML = currAttrs.get("dangerous-unescaped-html"); - } else { - let prevChild = prev.firstChild; - let currChild = curr.children; - while (prevChild) { - if (Array.isArray(currChild) && currChild.length) { - const next = prevChild.nextSibling; - morph(prevChild, currChild.shift(), dispatch, prev); - prevChild = next; - } else if (currChild.head) { - const next = prevChild.nextSibling; - morph(prevChild, currChild.head, dispatch, prev); - currChild = currChild.tail; - prevChild = next; - } else { - const next = prevChild.nextSibling; - prevChild.remove(); - prevChild = next; - } - } - for (const child of currChild) { - prev.appendChild(morph(null, child, dispatch, prev)); - } - } - return prev; -} -function morphAttr(el, name, value, dispatch) { - switch (typeof value) { - case (name.startsWith("data-lustre-on-") && "string"): { - if (!value) { - el.removeAttribute(name); - el.removeEventListener(event2, el.$lustre[`${name}Handler`]); - break; - } - if (el.hasAttribute(name)) - break; - const event2 = name.slice(15).toLowerCase(); - const handler = (e) => dispatch(serverEventHandler(e)); - if (el.$lustre[`${name}Handler`]) { - el.removeEventListener(event2, el.$lustre[`${name}Handler`]); - } - el.addEventListener(event2, handler); - el.$lustre[name] = value; - el.$lustre[`${name}Handler`] = handler; - el.$lustre.__registered_events.add(name); - el.setAttribute(name, value); - break; - } - case "string": - if (el.getAttribute(name) !== value) - el.setAttribute(name, value); - if (value === "") - el.removeAttribute(name); - if (name === "value" && el.value !== value) - el.value = value; - break; - case (name.startsWith("on") && "function"): { - if (el.$lustre[name] === value) - break; - const event2 = name.slice(2).toLowerCase(); - const handler = (e) => map2(value(e), dispatch); - if (el.$lustre[`${name}Handler`]) { - el.removeEventListener(event2, el.$lustre[`${name}Handler`]); - } - el.addEventListener(event2, handler); - el.$lustre[name] = value; - el.$lustre[`${name}Handler`] = handler; - el.$lustre.__registered_events.add(name); - break; - } - default: - el[name] = value; - } -} -function createText(prev, curr) { - const el = document.createTextNode(curr.content); - if (prev) - prev.replaceWith(el); - return el; -} -function morphText(prev, curr) { - const prevValue = prev.nodeValue; - const currValue = curr.content; - if (!currValue) { - prev?.remove(); - return null; - } - if (prevValue !== currValue) - prev.nodeValue = currValue; - return prev; -} -function serverEventHandler(event2) { - const el = event2.target; - const tag = el.getAttribute(`data-lustre-on-${event2.type}`); - const data = JSON.parse(el.getAttribute("data-lustre-data") || "{}"); - const include = JSON.parse(el.getAttribute("data-lustre-include") || "[]"); - switch (event2.type) { - case "input": - case "change": - include.push("target.value"); - break; - } - return { - tag, - data: include.reduce((data2, property) => { - const path = property.split("."); - for (let i = 0, o = data2, e = event2; i < path.length; i++) { - if (i === path.length - 1) { - o[path[i]] = e[path[i]]; - } else { - o[path[i]] ??= {}; - e = e[path[i]]; - o = o[path[i]]; - } - } - return data2; - }, data) - }; -} - -// src/server-component.mjs -var LustreServerComponent = class extends HTMLElement { - static get observedAttributes() { - return ["route"]; - } - #observer = null; - #root = null; - #socket = null; - constructor() { - super(); - this.#observer = new MutationObserver((mutations) => { - const changed = []; - for (const mutation of mutations) { - if (mutation.type === "attributes") { - const { attributeName: name, oldValue: prev } = mutation; - const next = this.getAttribute(name); - if (prev !== next) { - try { - changed.push([name, JSON.parse(next)]); - } catch { - changed.push([name, next]); - } - } - } - } - if (changed.length) { - this.#socket?.send(JSON.stringify([attrs, changed])); - } - }); - } - connectedCallback() { - this.#root = document.createElement("div"); - this.appendChild(this.#root); - } - attributeChangedCallback(name, prev, next) { - switch (name) { - case "route": { - if (!next) { - this.#socket?.close(); - this.#socket = null; - } else if (prev !== next) { - const id = this.getAttribute("id"); - const route = next + (id ? `?id=${id}` : ""); - this.#socket?.close(); - this.#socket = new WebSocket(`ws://${window.location.host}${route}`); - this.#socket.addEventListener("message", ({ data }) => { - const [kind, ...payload] = JSON.parse(data); - switch (kind) { - case diff: - return this.diff(payload); - case emit: - return this.emit(payload); - case init: - return this.init(payload); - } - }); - } - } - } - } - init([attrs2, vdom]) { - const initial = []; - for (const attr of attrs2) { - if (attr in this) { - initial.push([attr, this[attr]]); - } else if (this.hasAttribute(attr)) { - initial.push([attr, this.getAttribute(attr)]); - } - Object.defineProperty(this, attr, { - get() { - return this[`_${attr}`] ?? this.getAttribute(attr); - }, - set(value) { - const prev = this[attr]; - if (typeof value === "string") { - this.setAttribute(attr, value); - } else { - this[`_${attr}`] = value; - } - if (prev !== value) { - this.#socket?.send( - JSON.stringify([attrs, [[attr, value]]]) - ); - } - } - }); - } - this.#observer.observe(this, { - attributeFilter: attrs2, - attributeOldValue: true, - attributes: true, - characterData: false, - characterDataOldValue: false, - childList: false, - subtree: false - }); - this.morph(vdom); - if (initial.length) { - this.#socket?.send(JSON.stringify([attrs, initial])); - } - } - morph(vdom) { - this.#root = morph(this.#root, vdom, (msg) => { - this.#socket?.send(JSON.stringify([event, msg.tag, msg.data])); - }); - } - diff([diff2]) { - this.#root = patch(this.#root, diff2, (msg) => { - this.#socket?.send(JSON.stringify([event, msg.tag, msg.data])); - }); - } - emit([event2, data]) { - this.dispatchEvent(new CustomEvent(event2, { detail: data })); - } - disconnectedCallback() { - this.#socket?.close(); - } -}; -window.customElements.define("lustre-server-component", LustreServerComponent); -export { - LustreServerComponent -}; diff --git a/priv/static/lustre-server-component.min.mjs b/priv/static/lustre-server-component.min.mjs new file mode 100644 index 0000000..979b991 --- /dev/null +++ b/priv/static/lustre-server-component.min.mjs @@ -0,0 +1 @@ +var c=class{withFields(e){let n=Object.keys(this).map(u=>u in e?e[u]:this[u]);return new this.constructor(...n)}},w=class{static fromArray(e,n){let u=n||new d;return e.reduceRight((i,s)=>new y(s,i),u)}[Symbol.iterator](){return new k(this)}toArray(){return[...this]}atLeastLength(e){for(let n of this){if(e<=0)return!0;e--}return e<=0}hasLength(e){for(let n of this){if(e<=0)return!1;e--}return e===0}countLength(){let e=0;for(let n of this)e++;return e}};var k=class{#t;constructor(e){this.#t=e}next(){if(this.#t instanceof d)return{done:!0};{let{head:e,tail:n}=this.#t;return this.#t=n,{value:e,done:!1}}}},d=class extends w{},y=class extends w{constructor(e,n){super(),this.head=e,this.tail=n}};var m=class t extends c{static isResult(e){return e instanceof t}},p=class extends m{constructor(e){super(),this[0]=e}isOk(){return!0}},a=class extends m{constructor(e){super(),this[0]=e}isOk(){return!1}};var Mt=new DataView(new ArrayBuffer(8));var X=5,E=Math.pow(2,X),qt=E-1,Bt=E/2,Dt=E/4;function A(t,e){if(t.isOk()){let n=t[0];return new p(e(n))}else{let n=t[0];return new a(n)}}function f(t,e,n,u){if(e?.tag&&t?.nodeType===1){let i=e.tag.toUpperCase(),s=e.namespace||"http://www.w3.org/1999/xhtml";return t.nodeName===i&&t.namespaceURI==s?Q(t,e,n,u):B(t,e,n,u)}return e?.tag?B(t,e,n,u):typeof e?.content=="string"?t?.nodeType===3?te(t,e):ee(t,e):document.createComment(["[internal lustre error] I couldn't work out how to render this element. This","function should only be called internally by lustre's runtime: if you think","this is an error, please open an issue at","https://github.com/hayleigh-dot-dev/gleam-lustre/issues/new"].join(" "))}function D(t,e,n){for(let u of e[0]){let i=u[0];if(i==="0")f(t,u[1],n,t.parentNode);else{let s=Array.from(i),r=s.slice(0,-1).join(""),l=s.slice(-1)[0],o=t.querySelector(`[data-lustre-key="${i}"]`)??t.querySelector(`[data-lustre-key="${r}"]`).childNodes[l];f(o,u[1],n,o.parentNode)}}for(let u of e[1]){let i=u[0],s=Array.from(i),r=s.slice(0,-1).join(""),l=s.slice(-1)[0];(t.querySelector(`[data-lustre-key="${i}"]`)??t.querySelector(`[data-lustre-key="${r}"]`).childNodes[l]).remove()}for(let u of e[2]){let i=u[0],s=i==="0"?t:t.querySelector(`[data-lustre-key="${i}"]`);s.$lustre??={__registered_events:new Set};for(let r of u[0])x(s,r.name,r.value,n);for(let r of u[1])if(s.$lustre.__registered_events.has(r)){let l=r.slice(2).toLowerCase();s.removeEventListener(l,s.$lustre[`${r}Handler`]),s.$lustre.__registered_events.delete(r),delete s.$lustre[r],delete s.$lustre[`${r}Handler`]}else s.removeAttribute(r)}return t}function B(t,e,n,u=null){let i=e.namespace?document.createElementNS(e.namespace,e.tag):document.createElement(e.tag);i.$lustre={__registered_events:new Set};let s="";for(let r of e.attrs)r[0]==="class"?x(i,r[0],`${i.className} ${r[1]}`):r[0]==="style"?x(i,r[0],`${i.style.cssText} ${r[1]}`):r[0]==="dangerous-unescaped-html"?s+=r[1]:r[0]!==""&&x(i,r[0],r[1],n);if(customElements.get(e.tag))i._slot=e.children;else if(e.tag==="slot"){let r=new d,l=u;for(;l;)if(l._slot){r=l._slot;break}else l=l.parentNode;for(let o of r)i.appendChild(f(null,o,n,i))}else if(s)i.innerHTML=s;else for(let r of e.children)i.appendChild(f(null,r,n,i));return t&&t.replaceWith(i),i}function Q(t,e,n,u){let i=t.attributes,s=new Map;t.$lustre??={__registered_events:new Set};for(let r of e.attrs)r[0]==="class"&&s.has("class")?s.set(r[0],`${s.get("class")} ${r[1]}`):r[0]==="style"&&s.has("style")?s.set(r[0],`${s.get("style")} ${r[1]}`):r[0]==="dangerous-unescaped-html"&&s.has("dangerous-unescaped-html")?s.set(r[0],`${s.get("dangerous-unescaped-html")} ${r[1]}`):r[0]!==""&&s.set(r[0],r[1]);for(let{name:r}of i)if(!s.has(r))t.removeAttribute(r);else{let l=s.get(r);x(t,r,l,n),s.delete(r)}for(let r of t.$lustre.__registered_events)if(!s.has(r)){let l=r.slice(2).toLowerCase();t.removeEventListener(l,t.$lustre[`${r}Handler`]),t.$lustre.__registered_events.delete(r),delete t.$lustre[r],delete t.$lustre[`${r}Handler`]}for(let[r,l]of s)x(t,r,l,n);if(customElements.get(e.tag))t._slot=e.children;else if(e.tag==="slot"){let r=t.firstChild,l=new d,o=u;for(;o;)if(o._slot){l=o._slot;break}else o=o.parentNode;for(;r;)Array.isArray(l)&&l.length?f(r,l.shift(),n,t):l.head&&(f(r,l.head,n,t),l=l.tail),r=r.nextSibling;for(let h of l)t.appendChild(f(null,h,n,t))}else if(s.has("dangerous-unescaped-html"))t.innerHTML=s.get("dangerous-unescaped-html");else{let r=t.firstChild,l=e.children;for(;r;)if(Array.isArray(l)&&l.length){let o=r.nextSibling;f(r,l.shift(),n,t),r=o}else if(l.head){let o=r.nextSibling;f(r,l.head,n,t),l=l.tail,r=o}else{let o=r.nextSibling;r.remove(),r=o}for(let o of l)t.appendChild(f(null,o,n,t))}return t}function x(t,e,n,u){switch(typeof n){case(e.startsWith("data-lustre-on-")&&"string"):{if(!n){t.removeAttribute(e),t.removeEventListener(i,t.$lustre[`${e}Handler`]);break}if(t.hasAttribute(e))break;let i=e.slice(15).toLowerCase(),s=r=>u(re(r));t.$lustre[`${e}Handler`]&&t.removeEventListener(i,t.$lustre[`${e}Handler`]),t.addEventListener(i,s),t.$lustre[e]=n,t.$lustre[`${e}Handler`]=s,t.$lustre.__registered_events.add(e),t.setAttribute(e,n);break}case"string":e==="value"&&(t.value=n),n===""?t.removeAttribute(e):t.setAttribute(e,n);break;case(e.startsWith("on")&&"function"):{if(t.$lustre[e]===n)break;let i=e.slice(2).toLowerCase(),s=r=>A(n(r),u);t.$lustre[`${e}Handler`]&&t.removeEventListener(i,t.$lustre[`${e}Handler`]),t.addEventListener(i,s),t.$lustre[e]=n,t.$lustre[`${e}Handler`]=s,t.$lustre.__registered_events.add(e);break}default:t[e]=n}}function ee(t,e){let n=document.createTextNode(e.content);return t&&t.replaceWith(n),n}function te(t,e){let n=t.nodeValue,u=e.content;return u?(n!==u&&(t.nodeValue=u),t):(t?.remove(),null)}function re(t){let e=t.target,n=e.getAttribute(`data-lustre-on-${t.type}`),u=JSON.parse(e.getAttribute("data-lustre-data")||"{}"),i=JSON.parse(e.getAttribute("data-lustre-include")||"[]");switch(t.type){case"input":case"change":i.push("target.value");break}return{tag:n,data:i.reduce((s,r)=>{let l=r.split(".");for(let o=0,h=s,b=t;o{let n=[];for(let u of e)if(u.type==="attributes"){let{attributeName:i,oldValue:s}=u,r=this.getAttribute(i);if(s!==r)try{n.push([i,JSON.parse(r)])}catch{n.push([i,r])}}n.length&&this.#e?.send(JSON.stringify([5,n]))})}connectedCallback(){this.#r=document.createElement("div"),this.appendChild(this.#r)}attributeChangedCallback(e,n,u){switch(e){case"route":if(!u)this.#e?.close(),this.#e=null;else if(n!==u){let i=this.getAttribute("id"),s=u+(i?`?id=${i}`:"");this.#e?.close(),this.#e=new WebSocket(`ws://${window.location.host}${s}`),this.#e.addEventListener("message",r=>this.messageReceivedCallback(r))}}}messageReceivedCallback({data:e}){let[n,...u]=JSON.parse(e);switch(n){case 0:return this.diff(u);case 1:return this.emit(u);case 2:return this.init(u)}}init([e,n]){let u=[];for(let i of e)i in this?u.push([i,this[i]]):this.hasAttribute(i)&&u.push([i,this.getAttribute(i)]),Object.defineProperty(this,i,{get(){return this[`_${i}`]??this.getAttribute(i)},set(s){let r=this[i];typeof s=="string"?this.setAttribute(i,s):this[`_${i}`]=s,r!==s&&this.#e?.send(JSON.stringify([5,[[i,s]]]))}});this.#t.observe(this,{attributeFilter:e,attributeOldValue:!0,attributes:!0,characterData:!1,characterDataOldValue:!1,childList:!1,subtree:!1}),this.morph(n),u.length&&this.#e?.send(JSON.stringify([5,u]))}morph(e){this.#r=f(this.#r,e,n=>{this.#e?.send(JSON.stringify([4,n.tag,n.data]))})}diff([e]){this.#r=D(this.#r,e,n=>{this.#e?.send(JSON.stringify([4,n.tag,n.data]))})}emit([e,n]){this.dispatchEvent(new CustomEvent(e,{detail:n}))}disconnectedCallback(){this.#e?.close()}};window.customElements.define("lustre-server-component",C);export{C as LustreServerComponent}; diff --git a/priv/static/lustre-server-component.mjs b/priv/static/lustre-server-component.mjs new file mode 100644 index 0000000..aeb7d63 --- /dev/null +++ b/priv/static/lustre-server-component.mjs @@ -0,0 +1,542 @@ +// build/dev/javascript/lustre/lustre/internals/constants.mjs +var diff = 0; +var emit = 1; +var init = 2; +var event = 4; +var attrs = 5; + +// build/dev/javascript/prelude.mjs +var CustomType = class { + withFields(fields) { + let properties = Object.keys(this).map( + (label) => label in fields ? fields[label] : this[label] + ); + return new this.constructor(...properties); + } +}; +var List = class { + static fromArray(array, tail) { + let t = tail || new Empty(); + return array.reduceRight((xs, x) => new NonEmpty(x, xs), t); + } + [Symbol.iterator]() { + return new ListIterator(this); + } + toArray() { + return [...this]; + } + atLeastLength(desired) { + for (let _ of this) { + if (desired <= 0) + return true; + desired--; + } + return desired <= 0; + } + hasLength(desired) { + for (let _ of this) { + if (desired <= 0) + return false; + desired--; + } + return desired === 0; + } + countLength() { + let length2 = 0; + for (let _ of this) + length2++; + return length2; + } +}; +var ListIterator = class { + #current; + constructor(current) { + this.#current = current; + } + next() { + if (this.#current instanceof Empty) { + return { done: true }; + } else { + let { head, tail } = this.#current; + this.#current = tail; + return { value: head, done: false }; + } + } +}; +var Empty = class extends List { +}; +var NonEmpty = class extends List { + constructor(head, tail) { + super(); + this.head = head; + this.tail = tail; + } +}; +var Result = class _Result extends CustomType { + static isResult(data) { + return data instanceof _Result; + } +}; +var Ok = class extends Result { + constructor(value) { + super(); + this[0] = value; + } + isOk() { + return true; + } +}; +var Error = class extends Result { + constructor(detail) { + super(); + this[0] = detail; + } + isOk() { + return false; + } +}; + +// build/dev/javascript/gleam_stdlib/dict.mjs +var tempDataView = new DataView(new ArrayBuffer(8)); +var SHIFT = 5; +var BUCKET_SIZE = Math.pow(2, SHIFT); +var MASK = BUCKET_SIZE - 1; +var MAX_INDEX_NODE = BUCKET_SIZE / 2; +var MIN_ARRAY_NODE = BUCKET_SIZE / 4; + +// build/dev/javascript/gleam_stdlib/gleam/result.mjs +function map2(result, fun) { + if (result.isOk()) { + let x = result[0]; + return new Ok(fun(x)); + } else { + let e = result[0]; + return new Error(e); + } +} + +// build/dev/javascript/lustre/vdom.ffi.mjs +function morph(prev, curr, dispatch, parent) { + if (curr?.tag && prev?.nodeType === 1) { + const nodeName = curr.tag.toUpperCase(); + const ns = curr.namespace || "http://www.w3.org/1999/xhtml"; + if (prev.nodeName === nodeName && prev.namespaceURI == ns) { + return morphElement(prev, curr, dispatch, parent); + } else { + return createElement(prev, curr, dispatch, parent); + } + } + if (curr?.tag) { + return createElement(prev, curr, dispatch, parent); + } + if (typeof curr?.content === "string") { + return prev?.nodeType === 3 ? morphText(prev, curr) : createText(prev, curr); + } + return document.createComment( + [ + "[internal lustre error] I couldn't work out how to render this element. This", + "function should only be called internally by lustre's runtime: if you think", + "this is an error, please open an issue at", + "https://github.com/hayleigh-dot-dev/gleam-lustre/issues/new" + ].join(" ") + ); +} +function patch(root, diff2, dispatch) { + for (const created of diff2[0]) { + const key = created[0]; + if (key === "0") { + morph(root, created[1], dispatch, root.parentNode); + } else { + const segments = Array.from(key); + const parentKey = segments.slice(0, -1).join(""); + const indexKey = segments.slice(-1)[0]; + const prev = root.querySelector(`[data-lustre-key="${key}"]`) ?? root.querySelector(`[data-lustre-key="${parentKey}"]`).childNodes[indexKey]; + morph(prev, created[1], dispatch, prev.parentNode); + } + } + for (const removed of diff2[1]) { + const key = removed[0]; + const segments = Array.from(key); + const parentKey = segments.slice(0, -1).join(""); + const indexKey = segments.slice(-1)[0]; + const prev = root.querySelector(`[data-lustre-key="${key}"]`) ?? root.querySelector(`[data-lustre-key="${parentKey}"]`).childNodes[indexKey]; + prev.remove(); + } + for (const updated of diff2[2]) { + const key = updated[0]; + const prev = key === "0" ? root : root.querySelector(`[data-lustre-key="${key}"]`); + prev.$lustre ??= { __registered_events: /* @__PURE__ */ new Set() }; + for (const created of updated[0]) { + morphAttr(prev, created.name, created.value, dispatch); + } + for (const removed of updated[1]) { + if (prev.$lustre.__registered_events.has(removed)) { + const event2 = removed.slice(2).toLowerCase(); + prev.removeEventListener(event2, prev.$lustre[`${removed}Handler`]); + prev.$lustre.__registered_events.delete(removed); + delete prev.$lustre[removed]; + delete prev.$lustre[`${removed}Handler`]; + } else { + prev.removeAttribute(removed); + } + } + } + return root; +} +function createElement(prev, curr, dispatch, parent = null) { + const el = curr.namespace ? document.createElementNS(curr.namespace, curr.tag) : document.createElement(curr.tag); + el.$lustre = { + __registered_events: /* @__PURE__ */ new Set() + }; + let dangerousUnescapedHtml = ""; + for (const attr of curr.attrs) { + if (attr[0] === "class") { + morphAttr(el, attr[0], `${el.className} ${attr[1]}`); + } else if (attr[0] === "style") { + morphAttr(el, attr[0], `${el.style.cssText} ${attr[1]}`); + } else if (attr[0] === "dangerous-unescaped-html") { + dangerousUnescapedHtml += attr[1]; + } else if (attr[0] !== "") { + morphAttr(el, attr[0], attr[1], dispatch); + } + } + if (customElements.get(curr.tag)) { + el._slot = curr.children; + } else if (curr.tag === "slot") { + let children = new Empty(); + let parentWithSlot = parent; + while (parentWithSlot) { + if (parentWithSlot._slot) { + children = parentWithSlot._slot; + break; + } else { + parentWithSlot = parentWithSlot.parentNode; + } + } + for (const child of children) { + el.appendChild(morph(null, child, dispatch, el)); + } + } else if (dangerousUnescapedHtml) { + el.innerHTML = dangerousUnescapedHtml; + } else { + for (const child of curr.children) { + el.appendChild(morph(null, child, dispatch, el)); + } + } + if (prev) + prev.replaceWith(el); + return el; +} +function morphElement(prev, curr, dispatch, parent) { + const prevAttrs = prev.attributes; + const currAttrs = /* @__PURE__ */ new Map(); + prev.$lustre ??= { __registered_events: /* @__PURE__ */ new Set() }; + for (const currAttr of curr.attrs) { + if (currAttr[0] === "class" && currAttrs.has("class")) { + currAttrs.set(currAttr[0], `${currAttrs.get("class")} ${currAttr[1]}`); + } else if (currAttr[0] === "style" && currAttrs.has("style")) { + currAttrs.set(currAttr[0], `${currAttrs.get("style")} ${currAttr[1]}`); + } else if (currAttr[0] === "dangerous-unescaped-html" && currAttrs.has("dangerous-unescaped-html")) { + currAttrs.set( + currAttr[0], + `${currAttrs.get("dangerous-unescaped-html")} ${currAttr[1]}` + ); + } else if (currAttr[0] !== "") { + currAttrs.set(currAttr[0], currAttr[1]); + } + } + for (const { name } of prevAttrs) { + if (!currAttrs.has(name)) { + prev.removeAttribute(name); + } else { + const value = currAttrs.get(name); + morphAttr(prev, name, value, dispatch); + currAttrs.delete(name); + } + } + for (const name of prev.$lustre.__registered_events) { + if (!currAttrs.has(name)) { + const event2 = name.slice(2).toLowerCase(); + prev.removeEventListener(event2, prev.$lustre[`${name}Handler`]); + prev.$lustre.__registered_events.delete(name); + delete prev.$lustre[name]; + delete prev.$lustre[`${name}Handler`]; + } + } + for (const [name, value] of currAttrs) { + morphAttr(prev, name, value, dispatch); + } + if (customElements.get(curr.tag)) { + prev._slot = curr.children; + } else if (curr.tag === "slot") { + let prevChild = prev.firstChild; + let currChild = new Empty(); + let parentWithSlot = parent; + while (parentWithSlot) { + if (parentWithSlot._slot) { + currChild = parentWithSlot._slot; + break; + } else { + parentWithSlot = parentWithSlot.parentNode; + } + } + while (prevChild) { + if (Array.isArray(currChild) && currChild.length) { + morph(prevChild, currChild.shift(), dispatch, prev); + } else if (currChild.head) { + morph(prevChild, currChild.head, dispatch, prev); + currChild = currChild.tail; + } + prevChild = prevChild.nextSibling; + } + for (const child of currChild) { + prev.appendChild(morph(null, child, dispatch, prev)); + } + } else if (currAttrs.has("dangerous-unescaped-html")) { + prev.innerHTML = currAttrs.get("dangerous-unescaped-html"); + } else { + let prevChild = prev.firstChild; + let currChild = curr.children; + while (prevChild) { + if (Array.isArray(currChild) && currChild.length) { + const next = prevChild.nextSibling; + morph(prevChild, currChild.shift(), dispatch, prev); + prevChild = next; + } else if (currChild.head) { + const next = prevChild.nextSibling; + morph(prevChild, currChild.head, dispatch, prev); + currChild = currChild.tail; + prevChild = next; + } else { + const next = prevChild.nextSibling; + prevChild.remove(); + prevChild = next; + } + } + for (const child of currChild) { + prev.appendChild(morph(null, child, dispatch, prev)); + } + } + return prev; +} +function morphAttr(el, name, value, dispatch) { + switch (typeof value) { + case (name.startsWith("data-lustre-on-") && "string"): { + if (!value) { + el.removeAttribute(name); + el.removeEventListener(event2, el.$lustre[`${name}Handler`]); + break; + } + if (el.hasAttribute(name)) + break; + const event2 = name.slice(15).toLowerCase(); + const handler = (e) => dispatch(serverEventHandler(e)); + if (el.$lustre[`${name}Handler`]) { + el.removeEventListener(event2, el.$lustre[`${name}Handler`]); + } + el.addEventListener(event2, handler); + el.$lustre[name] = value; + el.$lustre[`${name}Handler`] = handler; + el.$lustre.__registered_events.add(name); + el.setAttribute(name, value); + break; + } + case "string": + if (name === "value") + el.value = value; + if (value === "") { + el.removeAttribute(name); + } else { + el.setAttribute(name, value); + } + break; + case (name.startsWith("on") && "function"): { + if (el.$lustre[name] === value) + break; + const event2 = name.slice(2).toLowerCase(); + const handler = (e) => map2(value(e), dispatch); + if (el.$lustre[`${name}Handler`]) { + el.removeEventListener(event2, el.$lustre[`${name}Handler`]); + } + el.addEventListener(event2, handler); + el.$lustre[name] = value; + el.$lustre[`${name}Handler`] = handler; + el.$lustre.__registered_events.add(name); + break; + } + default: + el[name] = value; + } +} +function createText(prev, curr) { + const el = document.createTextNode(curr.content); + if (prev) + prev.replaceWith(el); + return el; +} +function morphText(prev, curr) { + const prevValue = prev.nodeValue; + const currValue = curr.content; + if (!currValue) { + prev?.remove(); + return null; + } + if (prevValue !== currValue) + prev.nodeValue = currValue; + return prev; +} +function serverEventHandler(event2) { + const el = event2.target; + const tag = el.getAttribute(`data-lustre-on-${event2.type}`); + const data = JSON.parse(el.getAttribute("data-lustre-data") || "{}"); + const include = JSON.parse(el.getAttribute("data-lustre-include") || "[]"); + switch (event2.type) { + case "input": + case "change": + include.push("target.value"); + break; + } + return { + tag, + data: include.reduce((data2, property) => { + const path = property.split("."); + for (let i = 0, o = data2, e = event2; i < path.length; i++) { + if (i === path.length - 1) { + o[path[i]] = e[path[i]]; + } else { + o[path[i]] ??= {}; + e = e[path[i]]; + o = o[path[i]]; + } + } + return data2; + }, data) + }; +} + +// src/server-component.mjs +var LustreServerComponent = class extends HTMLElement { + static get observedAttributes() { + return ["route"]; + } + #observer = null; + #root = null; + #socket = null; + constructor() { + super(); + this.#observer = new MutationObserver((mutations) => { + const changed = []; + for (const mutation of mutations) { + if (mutation.type === "attributes") { + const { attributeName: name, oldValue: prev } = mutation; + const next = this.getAttribute(name); + if (prev !== next) { + try { + changed.push([name, JSON.parse(next)]); + } catch { + changed.push([name, next]); + } + } + } + } + if (changed.length) { + this.#socket?.send(JSON.stringify([attrs, changed])); + } + }); + } + connectedCallback() { + this.#root = document.createElement("div"); + this.appendChild(this.#root); + } + attributeChangedCallback(name, prev, next) { + switch (name) { + case "route": { + if (!next) { + this.#socket?.close(); + this.#socket = null; + } else if (prev !== next) { + const id = this.getAttribute("id"); + const route = next + (id ? `?id=${id}` : ""); + this.#socket?.close(); + this.#socket = new WebSocket(`ws://${window.location.host}${route}`); + this.#socket.addEventListener( + "message", + (message) => this.messageReceivedCallback(message) + ); + } + } + } + } + messageReceivedCallback({ data }) { + const [kind, ...payload] = JSON.parse(data); + switch (kind) { + case diff: + return this.diff(payload); + case emit: + return this.emit(payload); + case init: + return this.init(payload); + } + } + init([attrs2, vdom]) { + const initial = []; + for (const attr of attrs2) { + if (attr in this) { + initial.push([attr, this[attr]]); + } else if (this.hasAttribute(attr)) { + initial.push([attr, this.getAttribute(attr)]); + } + Object.defineProperty(this, attr, { + get() { + return this[`_${attr}`] ?? this.getAttribute(attr); + }, + set(value) { + const prev = this[attr]; + if (typeof value === "string") { + this.setAttribute(attr, value); + } else { + this[`_${attr}`] = value; + } + if (prev !== value) { + this.#socket?.send( + JSON.stringify([attrs, [[attr, value]]]) + ); + } + } + }); + } + this.#observer.observe(this, { + attributeFilter: attrs2, + attributeOldValue: true, + attributes: true, + characterData: false, + characterDataOldValue: false, + childList: false, + subtree: false + }); + this.morph(vdom); + if (initial.length) { + this.#socket?.send(JSON.stringify([attrs, initial])); + } + } + morph(vdom) { + this.#root = morph(this.#root, vdom, (msg) => { + this.#socket?.send(JSON.stringify([event, msg.tag, msg.data])); + }); + } + diff([diff2]) { + this.#root = patch(this.#root, diff2, (msg) => { + this.#socket?.send(JSON.stringify([event, msg.tag, msg.data])); + }); + } + emit([event2, data]) { + this.dispatchEvent(new CustomEvent(event2, { detail: data })); + } + disconnectedCallback() { + this.#socket?.close(); + } +}; +window.customElements.define("lustre-server-component", LustreServerComponent); +export { + LustreServerComponent +}; -- cgit v1.2.3