Packages
lustre
4.2.6
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/lustre@server_component.erl
-module(lustre@server_component).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([component/1, script/0, route/1, data/1, include/1, subscribe/2, unsubscribe/1, emit/2, set_selector/1, decode_action/1, encode_patch/1]).
-spec component(list(lustre@internals@vdom:attribute(RVS))) -> lustre@internals@vdom:element(RVS).
component(Attrs) ->
lustre@element:element(<<"lustre-server-component"/utf8>>, Attrs, []).
-spec script() -> lustre@internals@vdom:element(any()).
script() ->
lustre@element:element(
<<"script"/utf8>>,
[lustre@attribute:attribute(<<"type"/utf8>>, <<"module"/utf8>>)],
[lustre@element:text(
<<"function k(t,e,r,o=!1){let s,a=[{prev:t,next:e,parent:t.parentNode}];for(;a.length;){let{prev:n,next:i,parent:l}=a.pop();if(i.subtree!==void 0&&(i=i.subtree()),i.content!==void 0)if(n)if(n.nodeType===Node.TEXT_NODE)n.textContent!==i.content&&(n.textContent=i.content),s??=n;else{let c=document.createTextNode(i.content);l.replaceChild(c,n),s??=c}else{let c=document.createTextNode(i.content);l.appendChild(c),s??=c}else if(i.tag!==void 0){let c=D({prev:n,next:i,dispatch:r,stack:a,isComponent:o});n?n!==c&&l.replaceChild(c,n):l.appendChild(c),s??=c}else i.elements!==void 0?x(i,c=>{a.unshift({prev:n,next:c,parent:l}),n=n?.nextSibling}):i.subtree!==void 0&&a.push({prev:n,next:i,parent:l})}return s}function J(t,e,r){let o=t.parentNode;for(let s of e[0]){let a=s[0].split(\"-\"),n=s[1],i=N(o,a),l;if(i!==null&&i!==o)l=k(i,n,r);else{let c=N(o,a.slice(0,-1)),u=document.createTextNode(\"\");c.appendChild(u),l=k(u,n,r)}a===\"0\"&&(t=l)}for(let s of e[1]){let a=s[0].split(\"-\");N(o,a).remove()}for(let s of e[2]){let a=s[0].split(\"-\"),n=s[1],i=N(o,a),l=w.get(i);for(let c of n[0]){let u=c[0],m=c[1];if(u.startsWith(\"data-lustre-on-\")){let b=u.slice(15),p=r(M);l.has(b)||el.addEventListener(b,y),l.set(b,p),el.setAttribute(u,m)}else i.setAttribute(u,m),i[u]=m}for(let c of n[1])if(c[0].startsWith(\"data-lustre-on-\")){let u=c[0].slice(15);i.removeEventListener(u,y),l.delete(u)}else i.removeAttribute(c[0])}return t}function D({prev:t,next:e,dispatch:r,stack:o}){let s=e.namespace||\"http://www.w3.org/1999/xhtml\",a=t&&t.nodeType===Node.ELEMENT_NODE&&t.localName===e.tag&&t.namespaceURI===(e.namespace||\"http://www.w3.org/1999/xhtml\"),n=a?t:s?document.createElementNS(s,e.tag):document.createElement(e.tag),i;if(w.has(n))i=w.get(n);else{let d=new Map;w.set(n,d),i=d}let l=a?new Set(i.keys()):null,c=a?new Set(Array.from(t.attributes,d=>d.name)):null,u=null,m=null,b=null;for(let d of e.attrs){let f=d[0],h=d[1];if(d.as_property)n[f]!==h&&(n[f]=h),a&&c.delete(f);else if(f.startsWith(\"on\")){let g=f.slice(2),v=r(h);i.has(g)||n.addEventListener(g,y),i.set(g,v),a&&l.delete(g)}else if(f.startsWith(\"data-lustre-on-\")){let g=f.slice(15),v=r(M);i.has(g)||n.addEventListener(g,y),i.set(g,v),n.setAttribute(f,h)}else f===\"class\"?u=u===null?h:u+\" \"+h:f===\"style\"?m=m===null?h:m+h:f===\"dangerous-unescaped-html\"?b=h:(n.getAttribute(f)!==h&&n.setAttribute(f,h),(f===\"value\"||f===\"selected\")&&(n[f]=h),a&&c.delete(f))}if(u!==null&&(n.setAttribute(\"class\",u),a&&c.delete(\"class\")),m!==null&&(n.setAttribute(\"style\",m),a&&c.delete(\"style\")),a){for(let d of c)n.removeAttribute(d);for(let d of l)i.delete(d),n.removeEventListener(d,y)}if(e.key!==void 0&&e.key!==\"\")n.setAttribute(\"data-lustre-key\",e.key);else if(b!==null)return n.innerHTML=b,n;let p=n.firstChild,A=null,T=null,C=null,E=e.children[Symbol.iterator]().next().value;a&&E!==void 0&&E.key!==void 0&&E.key!==\"\"&&(A=new Set,T=L(t),C=L(e));for(let d of e.children)x(d,f=>{f.key!==void 0&&A!==null?p=W(p,f,n,o,C,T,A):(o.unshift({prev:p,next:f,parent:n}),p=p?.nextSibling)});for(;p;){let d=p.nextSibling;n.removeChild(p),p=d}return n}var w=new WeakMap;function y(t){let e=t.currentTarget;if(!w.has(e)){e.removeEventListener(t.type,y);return}let r=w.get(e);if(!r.has(t.type)){e.removeEventListener(t.type,y);return}r.get(t.type)(t)}function M(t){let e=t.currentTarget,r=e.getAttribute(`data-lustre-on-${t.type}`),o=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:r,data:s.reduce((a,n)=>{let i=n.split(\".\");for(let l=0,c=a,u=t;l<i.length;l++)l===i.length-1?c[i[l]]=u[i[l]]:(c[i[l]]??={},u=u[i[l]],c=c[i[l]]);return a},{data:o})}}function L(t){let e=new Map;if(t)for(let r of t.children)x(r,o=>{let s=o?.key||o?.getAttribute?.(\"data-lustre-key\");s&&e.set(s,o)});return e}function N(t,e){let r,o,s=t;for(;[r,...o]=e,r!==void 0;)s=s.childNodes.item(r),e=o;return s}function W(t,e,r,o,s,a,n){for(;t&&!s.has(t.getAttribute(\"data-lustre-key\"));){let l=t.nextSibling;r.removeChild(t),t=l}if(a.size===0)return x(e,l=>{o.unshift({prev:t,next:l,parent:r}),t=t?.nextSibling}),t;if(n.has(e.key))return console.warn(`Duplicate key found in Lustre vnode: ${e.key}`),o.unshift({prev:null,next:e,parent:r}),t;n.add(e.key);let i=a.get(e.key);if(!i&&!t)return o.unshift({prev:null,next:e,parent:r}),t;if(!i&&t!==null){let l=document.createTextNode(\"\");return r.insertBefore(l,t),o.unshift({prev:l,next:e,parent:r}),t}return!i||i===t?(o.unshift({prev:t,next:e,parent:r}),t=t?.nextSibling,t):(r.insertBefore(i,t),o.unshift({prev:i,next:e,parent:r}),t)}function x(t,e){if(t.elements!==void 0)for(let r of t.elements)e(r);else e(t)}var O=class extends HTMLElement{static get observedAttributes(){return[\"route\"]}#n=null;#t=null;#e=null;constructor(){super(),this.#n=new MutationObserver(e=>{let r=[];for(let o of e)if(o.type===\"attributes\"){let{attributeName:s,oldValue:a}=o,n=this.getAttribute(s);if(a!==n)try{r.push([s,JSON.parse(n)])}catch{r.push([s,n])}}r.length&&this.#e?.send(JSON.stringify([5,r]))})}connectedCallback(){this.#t=document.createElement(\"div\"),this.appendChild(this.#t)}attributeChangedCallback(e,r,o){switch(e){case\"route\":if(!o)this.#e?.close(),this.#e=null;else if(r!==o){let s=this.getAttribute(\"id\"),a=o+(s?`?id=${s}`:\"\"),n=window.location.protocol===\"https:\"?\"wss\":\"ws\";this.#e?.close(),this.#e=new WebSocket(`${n}://${window.location.host}${a}`),this.#e.addEventListener(\"message\",i=>this.messageReceivedCallback(i))}}}messageReceivedCallback({data:e}){let[r,...o]=JSON.parse(e);switch(r){case 0:return this.diff(o);case 1:return this.emit(o);case 2:return this.init(o)}}init([e,r]){let o=[];for(let s of e)s in this?o.push([s,this[s]]):this.hasAttribute(s)&&o.push([s,this.getAttribute(s)]),Object.defineProperty(this,s,{get(){return this[`_${s}`]??this.getAttribute(s)},set(a){let n=this[s];typeof a==\"string\"?this.setAttribute(s,a):this[`_${s}`]=a,n!==a&&this.#e?.send(JSON.stringify([5,[[s,a]]]))}});this.#n.observe(this,{attributeFilter:e,attributeOldValue:!0,attributes:!0,characterData:!1,characterDataOldValue:!1,childList:!1,subtree:!1}),this.morph(r),o.length&&this.#e?.send(JSON.stringify([5,o]))}morph(e){this.#t=k(this.#t,e,r=>o=>{let s=r(o);this.#e?.send(JSON.stringify([4,s.tag,s.data]))})}diff([e]){this.#t=J(this.#t,e,r=>o=>{let s=r(o);this.#e?.send(JSON.stringify([4,s.tag,s.data]))})}emit([e,r]){this.dispatchEvent(new CustomEvent(e,{detail:r}))}disconnectedCallback(){this.#e?.close()}};window.customElements.define(\"lustre-server-component\",O);export{O as LustreServerComponent};"/utf8>>
)]
).
-spec route(binary()) -> lustre@internals@vdom:attribute(any()).
route(Path) ->
lustre@attribute:attribute(<<"route"/utf8>>, Path).
-spec data(gleam@json:json()) -> lustre@internals@vdom:attribute(any()).
data(Json) ->
_pipe = Json,
_pipe@1 = gleam@json:to_string(_pipe),
lustre@attribute:attribute(<<"data-lustre-data"/utf8>>, _pipe@1).
-spec include(list(binary())) -> lustre@internals@vdom:attribute(any()).
include(Properties) ->
_pipe = Properties,
_pipe@1 = gleam@json:array(_pipe, fun gleam@json:string/1),
_pipe@2 = gleam@json:to_string(_pipe@1),
lustre@attribute:attribute(<<"data-lustre-include"/utf8>>, _pipe@2).
-spec subscribe(binary(), fun((lustre@internals@patch:patch(RWF)) -> nil)) -> lustre@internals@runtime:action(RWF, lustre:server_component()).
subscribe(Id, Renderer) ->
{subscribe, Id, Renderer}.
-spec unsubscribe(binary()) -> lustre@internals@runtime:action(any(), lustre:server_component()).
unsubscribe(Id) ->
{unsubscribe, Id}.
-spec emit(binary(), gleam@json:json()) -> lustre@effect:effect(any()).
emit(Event, Data) ->
lustre@effect:event(Event, Data).
-spec do_set_selector(
gleam@erlang@process:selector(lustre@internals@runtime:action(any(), RWV))
) -> lustre@effect:effect(RWV).
do_set_selector(Sel) ->
lustre@effect:from(
fun(_) ->
Self = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Self, {set_selector, Sel})
end
).
-spec set_selector(
gleam@erlang@process:selector(lustre@internals@runtime:action(any(), RWP))
) -> lustre@effect:effect(RWP).
set_selector(Sel) ->
do_set_selector(Sel).
-spec decode_event(gleam@dynamic:dynamic_()) -> {ok,
lustre@internals@runtime:action(any(), any())} |
{error, list(gleam@dynamic:decode_error())}.
decode_event(Dyn) ->
gleam@result:'try'(
(gleam@dynamic:tuple3(
fun gleam@dynamic:int/1,
fun gleam@dynamic:dynamic/1,
fun gleam@dynamic:dynamic/1
))(Dyn),
fun(_use0) ->
{Kind, Name, Data} = _use0,
gleam@bool:guard(
Kind /= 4,
{error,
[{decode_error,
gleam@int:to_string(4),
gleam@int:to_string(Kind),
[<<"0"/utf8>>]}]},
fun() ->
gleam@result:'try'(
gleam@dynamic:string(Name),
fun(Name@1) -> {ok, {event, Name@1, Data}} end
)
end
)
end
).
-spec decode_attr(gleam@dynamic:dynamic_()) -> {ok,
{binary(), gleam@dynamic:dynamic_()}} |
{error, list(gleam@dynamic:decode_error())}.
decode_attr(Dyn) ->
(gleam@dynamic:tuple2(
fun gleam@dynamic:string/1,
fun gleam@dynamic:dynamic/1
))(Dyn).
-spec decode_attrs(gleam@dynamic:dynamic_()) -> {ok,
lustre@internals@runtime:action(any(), any())} |
{error, list(gleam@dynamic:decode_error())}.
decode_attrs(Dyn) ->
gleam@result:'try'(
(gleam@dynamic:tuple2(
fun gleam@dynamic:int/1,
fun gleam@dynamic:dynamic/1
))(Dyn),
fun(_use0) ->
{Kind, Attrs} = _use0,
gleam@bool:guard(
Kind /= 5,
{error,
[{decode_error,
gleam@int:to_string(5),
gleam@int:to_string(Kind),
[<<"0"/utf8>>]}]},
fun() ->
gleam@result:'try'(
(gleam@dynamic:list(fun decode_attr/1))(Attrs),
fun(Attrs@1) -> {ok, {attrs, Attrs@1}} end
)
end
)
end
).
-spec decode_action(gleam@dynamic:dynamic_()) -> {ok,
lustre@internals@runtime:action(any(), lustre:server_component())} |
{error, list(gleam@dynamic:decode_error())}.
decode_action(Dyn) ->
(gleam@dynamic:any([fun decode_event/1, fun decode_attrs/1]))(Dyn).
-spec encode_patch(lustre@internals@patch:patch(any())) -> gleam@json:json().
encode_patch(Patch) ->
lustre@internals@patch:patch_to_json(Patch).