diff --git a/.gitignore b/.gitignore index 949b9b1..fe82df2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.beam *.ez -/build +build erl_crash.dump .lustre -**/.lustre +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..47e5256 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# off_topic + +[![Package Version](https://img.shields.io/hexpm/v/off_topic)](https://hex.pm/packages/off_topic) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/off_topic/) + +Declarative subscriptions (like Vues `watch` or Reacts `useEffect`) for [Lustre](https://hexdocs.pm/lustre/). Subscribe to browser events — keyboard, pointer, window size, page visibility, WebSockets, and more — without writing any FFI. + +```sh +gleam add off_topic +``` + +## Usage + +`off_topic.application` is a drop-in for `lustre.application` that adds a +`subscriptions` callback. off_topic calls it after every update, starting new +subscriptions and stopping removed ones automatically. + +```gleam +import lustre +import off_topic + +pub fn main() { + let app = off_topic.application(init:, update:, subscriptions:, view:) + let assert Ok(_) = lustre.start(app, "#app", Nil) + Nil +} + +fn subscriptions(model: Model) -> off_topic.Subscription(Msg) { + off_topic.batch([ + // always listen for the page state / visibility + off_topic.page_state(PageStateChanged), + case model.page_state { + // while the page is focused, run a 1 second interval + off_topic.Active -> + off_topic.every(every: duration.seconds(1), immediate: False, on_elapsed: Ticked) + // when the page is no longer active, returning off_topic.none() + // causes the runtime to clean up the interval automatically. + _ -> off_topic.none() + }, + ]) +} +``` + +A subscription is an effect with cleanup — a setup function that returns a +teardown function. off_topic diffs the subscription tree returned by each +`update` call the same way Lustre diffs elements: subscriptions that +disappeared are torn down, new ones are started, and unchanged ones are left +alone. + +For Lustre server components, off_topic provides a small client-side bridge (3kb min+gzip) +that runs subscriptions in the browser and forwards events to the server. +Almost every built-in subscription works on both sides +without any changes to your code. + +## Documentation + +Full API reference and guides at . diff --git a/demos/dist/component.css b/demos/dist/component.css new file mode 100644 index 0000000..1394fb4 --- /dev/null +++ b/demos/dist/component.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-yellow-500:oklch(79.5% .184 86.047);--color-green-500:oklch(72.3% .219 149.579);--spacing:.25rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-light:300;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-card-bg:var(--background,#fff);--color-card-border:var(--code-border,#ffaff3);--color-card-text:var(--text,#2a2020);--color-link:var(--links,#d900b8)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.flex{display:flex}.hidden{display:none}.inline{display:inline}.h-8{height:calc(var(--spacing)*8)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-40{height:calc(var(--spacing)*40)}.max-h-32{max-height:calc(var(--spacing)*32)}.min-h-screen{min-height:100vh}.w-8{width:calc(var(--spacing)*8)}.w-12{width:calc(var(--spacing)*12)}.w-20{width:calc(var(--spacing)*20)}.w-24{width:calc(var(--spacing)*24)}.w-40{width:calc(var(--spacing)*40)}.w-96{width:calc(var(--spacing)*96)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.self-start{align-self:flex-start}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-card-border{border-color:var(--color-card-border)}.bg-card-bg{background-color:var(--color-card-bg)}.bg-card-border{background-color:var(--color-card-border)}.p-6{padding:calc(var(--spacing)*6)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-12{padding-block:calc(var(--spacing)*12)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.text-card-text{color:var(--color-card-text)}.text-green-500{color:var(--color-green-500)}.text-link{color:var(--color-link)}.text-red-500{color:var(--color-red-500)}.text-yellow-500{color:var(--color-yellow-500)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media (hover:hover){.hover\:underline:hover{text-decoration-line:underline}}.disabled\:opacity-40:disabled{opacity:.4}}.theme-dark{--bg:#292d3e;--taupe-mono-1:#fdf1d4;--background:var(--bg);--code-background:#202431;--text:var(--taupe-mono-1);--links:var(--pink,#ffaff3);--code-border:var(--pink,#ffaff3)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} \ No newline at end of file diff --git a/demos/dist/component.js b/demos/dist/component.js new file mode 100644 index 0000000..eda7314 --- /dev/null +++ b/demos/dist/component.js @@ -0,0 +1,5 @@ +function QZ(Z,Q,Y){if(Z)return Q;else return Y()}class F{withFields(Z){let Q=Object.keys(this).map((Y)=>(Y in Z)?Z[Y]:this[Y]);return new this.constructor(...Q)}}class YZ{static fromArray(Z,Q){let Y=Q||new A;for(let J=Z.length-1;J>=0;--J)Y=new K9(Z[J],Y);return Y}[Symbol.iterator](){return new QJ(this)}toArray(){return[...this]}atLeastLength(Z){let Q=this;while(Z-- >0&&Q)Q=Q.tail;return Q!==void 0}hasLength(Z){let Q=this;while(Z-- >0&&Q)Q=Q.tail;return Z===-1&&Q instanceof A}countLength(){let Z=this,Q=0;while(Z)Z=Z.tail,Q++;return Q-1}}function B(Z,Q){return new K9(Z,Q)}function G(Z,Q){return YZ.fromArray(Z,Q)}class QJ{#Z;constructor(Z){this.#Z=Z}next(){if(this.#Z instanceof A)return{done:!0};else{let{head:Z,tail:Q}=this.#Z;return this.#Z=Q,{value:Z,done:!1}}}}class A extends YZ{}var YJ=()=>new A,dQ=(Z)=>Z instanceof A;class K9 extends YZ{constructor(Z,Q){super();this.head=Z,this.tail=Q}}var C6=(Z,Q)=>new K9(Z,Q),f8=(Z)=>Z instanceof K9,i8=(Z)=>Z.head,l0=(Z)=>Z.tail;class V9{bitSize;byteSize;bitOffset;rawBuffer;constructor(Z,Q,Y){if(!(Z instanceof Uint8Array))throw globalThis.Error("BitArray can only be constructed from a Uint8Array");if(this.bitSize=Q??Z.length*8,this.byteSize=Math.trunc((this.bitSize+7)/8),this.bitOffset=Y??0,this.bitSize<0)throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`);if(this.bitOffset<0||this.bitOffset>7)throw globalThis.Error(`BitArray bit offset is invalid: ${this.bitOffset}`);if(Z.length!==Math.trunc((this.bitOffset+this.bitSize+7)/8))throw globalThis.Error("BitArray buffer length is invalid");this.rawBuffer=Z}byteAt(Z){if(Z<0||Z>=this.byteSize)return;return X9(this.rawBuffer,this.bitOffset,Z)}equals(Z){if(this.bitSize!==Z.bitSize)return!1;let Q=Math.trunc(this.bitSize/8);if(this.bitOffset===0&&Z.bitOffset===0){for(let J=0;J>J!==Z.rawBuffer[Q]>>J)return!1}}else{for(let J=0;J>K!==X>>K)return!1}}return!0}get buffer(){if(this.bitOffset!==0||this.bitSize%8!==0)throw new globalThis.Error("BitArray.buffer does not support unaligned bit arrays");return this.rawBuffer}get length(){if(this.bitOffset!==0||this.bitSize%8!==0)throw new globalThis.Error("BitArray.length does not support unaligned bit arrays");return this.rawBuffer.length}}function X9(Z,Q,Y){if(Q===0)return Z[Y]??0;else{let J=Z[Y]<>8-Q;return J|X}}class rQ{constructor(Z){this.value=Z}}class JZ extends F{static isResult(Z){return Z instanceof JZ}}class w extends JZ{constructor(Z){super();this[0]=Z}isOk(){return!0}}var E0=(Z)=>new w(Z),C0=(Z)=>Z instanceof w,y0=(Z)=>Z[0];class Y0 extends JZ{constructor(Z){super();this[0]=Z}isOk(){return!1}}var h0=(Z)=>new Y0(Z),JJ=(Z)=>Z instanceof Y0;function J0(Z,Q){let Y=[Z,Q];while(Y.length){let J=Y.pop(),X=Y.pop();if(J===X)continue;if(!ZJ(J)||!ZJ(X))return!1;if(!vG(J,X)||yG(J,X)||bG(J,X)||fG(J,X)||gG(J,X)||_G(J,X)||hG(J,X))return!1;let V=Object.getPrototypeOf(J);if(V!==null&&typeof V.equals==="function")try{if(J.equals(X))continue;else return!1}catch{}let[W,H]=kG(J),z=W(J),U=W(X);if(z.length!==U.length)return!1;for(let q of z)Y.push(H(J,q),H(X,q))}return!0}function kG(Z){if(Z instanceof Map)return[(Q)=>Q.keys(),(Q,Y)=>Q.get(Y)];else{let Q=Z instanceof globalThis.Error?["message"]:[];return[(Y)=>[...Q,...Object.keys(Y)],(Y,J)=>Y[J]]}}function yG(Z,Q){return Z instanceof Date&&(Z>Q||ZY===Q[J]))}function fG(Z,Q){return Array.isArray(Z)&&Z.length!==Q.length}function gG(Z,Q){return Z instanceof Map&&Z.size!==Q.size}function _G(Z,Q){return Z instanceof Set&&(Z.size!=Q.size||[...Z].some((Y)=>!Q.has(Y)))}function hG(Z,Q){return Z instanceof RegExp&&(Z.source!==Q.source||Z.flags!==Q.flags)}function ZJ(Z){return typeof Z==="object"&&Z!==null}function vG(Z,Q){if(typeof Z!=="object"&&typeof Q!=="object"&&(!Z||!Q))return!1;if([Promise,WeakSet,WeakMap,Function].some((J)=>Z instanceof J))return!1;return Z.constructor===Q.constructor}function w6(Z,Q){if(Q===0)return 0;else return Z%Q}function x6(Z,Q){return Math.trunc(XZ(Z,Q))}function XZ(Z,Q){if(Q===0)return 0;else return Z/Q}function G9(Z,Q,Y,J,X,K,V){let W=new globalThis.Error(K);W.gleam_error=Z,W.file=Q,W.module=Y,W.line=J,W.function=X,W.fn=X;for(let H in V)W[H]=V[H];return W}class M0 extends F{}var XJ=()=>new M0;class j0 extends F{}var KJ=()=>new j0;class n8 extends F{}var VJ=()=>new n8;function sQ(Z,Q){if(Z instanceof M0)return Z;else if(Z instanceof j0)return Q;else return Z}class k extends F{constructor(Z){super();this[0]=Z}}var j8=(Z)=>Z instanceof k,P8=(Z)=>Z[0];class m extends F{}var GJ=new WeakMap,lQ=new DataView(new ArrayBuffer(8)),iQ=0;function aQ(Z){let Q=GJ.get(Z);if(Q!==void 0)return Q;let Y=iQ++;if(iQ===2147483647)iQ=0;return GJ.set(Z,Y),Y}function WJ(Z,Q){return Z^Q+2654435769+(Z<<6)+(Z>>2)|0}function oQ(Z){let Q=0,Y=Z.length;for(let J=0;J>16^Q)^Y}function $G(Z){return oQ(Z.toString())}function mG(Z){let Q=Object.getPrototypeOf(Z);if(Q!==null&&typeof Q.hashCode==="function")try{let J=Z.hashCode(Z);if(typeof J==="number")return J}catch{}if(Z instanceof Promise||Z instanceof WeakSet||Z instanceof WeakMap)return aQ(Z);if(Z instanceof Date)return FJ(Z.getTime());let Y=0;if(Z instanceof ArrayBuffer)Z=new Uint8Array(Z);if(Array.isArray(Z)||Z instanceof Uint8Array)for(let J=0;J{Y=Y+a8(J)|0});else if(Z instanceof Map)Z.forEach((J,X)=>{Y=Y+WJ(a8(J),a8(X))|0});else{let J=Object.keys(Z);for(let X=0;X32){for(let L=0;L>>1&1431655765,Z=(Z&858993459)+(Z>>>2&858993459),Math.imul(Z+(Z>>>4)&252645135,16843009)>>>24}function H9(Z,Q){return tQ(Z&Q-1)}function NJ(Z,Q){return 1<<(Z>>>Q&uG)}function F9(Z){return z6(Z,G([]),(Q,Y,J)=>{return B(Y,Q)})}class b0 extends F{}class b6 extends F{}function z9(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return J;else{let X=Y.head;Z=Y.tail,Q=B(X,J)}}}function a(Z){return z9(Z,G([]))}function iG(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return a(K);else{let{head:V,tail:W}=J,H;if(X(V))H=B(V,K);else H=K;let U=H;Z=W,Q=X,Y=U}}}function BJ(Z,Q){return iG(Z,Q,G([]))}function nG(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return a(K);else{let V=J.head;Z=J.tail,Q=X,Y=B(X(V),K)}}}function T0(Z,Q){return nG(Z,Q,G([]))}function aG(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return J;else{let X=Y.head;Z=Y.tail,Q=B(X,J)}}}function v8(Z,Q){return aG(a(Z),Q)}function f6(Z,Q){return B(Q,Z)}function x0(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return X;else{let V=J.head;Z=J.tail,Q=K(X,V),Y=K}}}function oG(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(X instanceof A)return z9(K,W);else if(K instanceof A)return z9(X,W);else{let{head:H,tail:z}=X,U=K.head,q=K.tail,I=V(H,U);if(I instanceof M0)Z=X,Q=q,Y=V,J=B(U,W);else if(I instanceof j0)Z=z,Q=K,Y=V,J=B(H,W);else Z=z,Q=K,Y=V,J=B(H,W)}}}function tG(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return a(K);else{let V=J.tail;if(V instanceof A){let W=J.head;return a(B(a(W),K))}else{let W=J.head,H=V.head,z=V.tail,U=oG(W,H,X,G([]));Z=z,Q=X,Y=B(U,K)}}}}function eG(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(X instanceof A)return z9(K,W);else if(K instanceof A)return z9(X,W);else{let{head:H,tail:z}=X,U=K.head,q=K.tail,I=V(H,U);if(I instanceof M0)Z=z,Q=K,Y=V,J=B(H,W);else if(I instanceof j0)Z=X,Q=q,Y=V,J=B(U,W);else Z=X,Q=q,Y=V,J=B(U,W)}}}function ZW(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return a(K);else{let V=J.tail;if(V instanceof A){let W=J.head;return a(B(a(W),K))}else{let W=J.head,H=V.head,z=V.tail,U=eG(W,H,X,G([]));Z=z,Q=X,Y=B(U,K)}}}}function QW(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return J;else if(X instanceof b0)if(J.tail instanceof A)return J.head;else Z=ZW(J,K,G([])),Q=new b6,Y=K;else if(J.tail instanceof A){let W=J.head;return a(W)}else Z=tG(J,K,G([])),Q=new b0,Y=K}}function YW(Z,Q,Y,J,X,K){while(!0){let V=Z,W=Q,H=Y,z=J,U=X,q=K,I=B(U,H);if(V instanceof A)if(z instanceof b0)return B(a(I),q);else return B(I,q);else{let{head:N,tail:R}=V,O=W(U,N);if(z instanceof b0)if(O instanceof M0)Z=R,Q=W,Y=I,J=z,X=N,K=q;else if(O instanceof j0)Z=R,Q=W,Y=I,J=z,X=N,K=q;else{let D;if(z instanceof b0)D=B(a(I),q);else D=B(I,q);let T=D;if(R instanceof A)return B(G([N]),T);else{let{head:C,tail:P}=R,L,E=W(N,C);if(E instanceof M0)L=new b0;else if(E instanceof j0)L=new b0;else L=new b6;let v=L;Z=P,Q=W,Y=G([N]),J=v,X=C,K=T}}else if(O instanceof M0){let D;if(z instanceof b0)D=B(a(I),q);else D=B(I,q);let T=D;if(R instanceof A)return B(G([N]),T);else{let{head:C,tail:P}=R,L,E=W(N,C);if(E instanceof M0)L=new b0;else if(E instanceof j0)L=new b0;else L=new b6;let v=L;Z=P,Q=W,Y=G([N]),J=v,X=C,K=T}}else if(O instanceof j0){let D;if(z instanceof b0)D=B(a(I),q);else D=B(I,q);let T=D;if(R instanceof A)return B(G([N]),T);else{let{head:C,tail:P}=R,L,E=W(N,C);if(E instanceof M0)L=new b0;else if(E instanceof j0)L=new b0;else L=new b6;let v=L;Z=P,Q=W,Y=G([N]),J=v,X=C,K=T}}else Z=R,Q=W,Y=I,J=z,X=N,K=q}}}function FZ(Z,Q){if(Z instanceof A)return Z;else{let Y=Z.tail;if(Y instanceof A)return Z;else{let J=Z.head,X=Y.head,K=Y.tail,V,W=Q(J,X);if(W instanceof M0)V=new b0;else if(W instanceof j0)V=new b0;else V=new b6;let H=V,z=YW(K,Q,G([J]),H,X,G([]));return QW(z,new b0,Q)}}}function zZ(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return;else{let{head:X,tail:K}=Y;J(X),Z=K,Q=J}}}function OJ(Z,Q){if(Q==="")return q9(Z);else{let J=F0(Z),X=ZY(J,Q);return T0(X,F0)}}function GW(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return J;else{let X=Y.head;Z=Y.tail,Q=J+X}}}function QY(Z){return GW(Z,"")}function WW(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return K;else{let V=J.head;Z=J.tail,Q=X,Y=K+X+V}}}function U6(Z,Q){if(Z instanceof A)return"";else{let{head:Y,tail:J}=Z;return WW(J,Q,Y)}}class e8 extends F{constructor(Z,Q,Y){super();this.expected=Z,this.found=Q,this.path=Y}}var jJ=(Z,Q,Y)=>new e8(Z,Q,Y);class u0 extends F{constructor(Z){super();this.function=Z}}var KY=new u0(UW),UZ=new u0(qW),J8=new u0(IW);var X8=new u0(NW),S0=new u0(MW);function UW(Z){return[Z,G([])]}function K8(Z,Q){let Y=Q.function(Z),J,X;if(J=Y[0],X=Y[1],X instanceof A)return new w(J);else return new Y0(X)}function VY(Z,Q,Y){let J=Y(Z);if(J instanceof w)return[J[0],G([])];else return[J[0],G([new e8(Q,h8(Z),G([]))])]}function qW(Z){return VY(Z,"Float",TJ)}function z0(Z,Q){return new u0((Y)=>{let J=Z.function(Y),X,K;return X=J[0],K=J[1],[Q(X),K]})}function IW(Z){return VY(Z,"Int",LJ)}function NW(Z){return VY(Z,"String",EJ)}function RW(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(K instanceof A)return X;else{let{head:V,tail:W}=K,H=V.function(J),z,U;if(z=H,U=H[1],U instanceof A)return z;else Z=J,Q=X,Y=W}}}function R9(Z,Q){return new u0((Y)=>{let J=Z.function(Y),X,K;if(X=J,K=J[1],K instanceof A)return X;else return RW(Y,X,Q)})}function BW(Z){let Q=R9(X8,G([(()=>{return z0(J8,$)})(),(()=>{return z0(UZ,g6)})()])),Y=K8(Z,Q);if(Y instanceof w)return Y[0];else return"<"+h8(Z)+">"}function N9(Z,Q){let Y=T0(Q,(X)=>{let V=F0(X);return BW(V)}),J=T0(Z[1],(X)=>{return new e8(X.expected,X.found,v8(Y,X.path))});return[Z[0],J]}function qZ(Z){return new u0((Q)=>{return SJ(Q,Z.function,(Y,J)=>{return N9(Y,G([J]))},0,G([]))})}function DW(Z,Q,Y,J,X){while(!0){let K=Z,V=Q,W=Y,H=J,z=X;if(K instanceof A){let q=W(H);return N9(q,a(V))}else{let{head:U,tail:q}=K,I=WY(H,U);if(I instanceof w){let N=I[0];if(N instanceof k){let R=N[0];Z=q,Q=B(U,V),Y=W,J=R,X=z}else return z(H,B(U,V))}else{let N=I[0],R=W(H),O;O=R[0];let D=[O,G([new e8(N,h8(H),G([]))])];return N9(D,a(V))}}}}function q6(Z,Q,Y){return new u0((J)=>{let X=DW(Z,G([]),Q.function,J,(U,q)=>{let I=Q.function(U),N;N=I[0];let R=[N,G([new e8("Field","Nothing",G([]))])];return N9(R,a(q))}),K,V;K=X[0],V=X[1];let W=Y(K).function(J),H,z;return H=W[0],z=W[1],[H,v8(V,z)]})}function O0(Z){return new u0((Q)=>{return[Z,G([])]})}function PJ(Z,Q){return G([new e8(Z,h8(Q),G([]))])}function V0(Z,Q,Y){return q6(G([Z]),Q,Y)}function I6(Z,Q,Y,J){return new u0((X)=>{let K,V,W=WY(X,Z);if(W instanceof w){let O=W[0];if(O instanceof k){let D=O[0];V=Y.function(D)}else V=[Q,G([])]}else{let O=W[0];V=[Q,G([new e8(O,h8(X),G([]))])]}K=N9(V,G([Z]));let z=K,U,q;U=z[0],q=z[1];let I=J(U).function(X),N,R;return N=I[0],R=I[1],[N,v8(q,R)]})}function MW(Z){if(J0(F0(!0),Z))return[!0,G([])];else if(J0(F0(!1),Z))return[!1,G([])];else return[!1,PJ("Bool",Z)]}function IZ(Z,Q){return new u0((Y)=>{let J=Z.function(Y),X,K;X=J[0],K=J[1];let W=Q(X).function(Y),H,z;if(H=W,z=W[0],K instanceof A)return H;else return[z,K]})}function GY(Z,Q){return new u0((Y)=>{return[Z,PJ(Q,Y)]})}var AW=void 0;function F0(Z){return Z}function D9(Z){if(/^[-+]?(\d+)$/.test(Z))return E0(parseInt(Z));else return h0(AW)}function $(Z){return Z.toString()}function q9(Z){let Q=jW(Z);if(Q)return B9(Array.from(Q).map((Y)=>Y.segment));else return B9(Z.match(/./gsu))}var CJ=void 0;function jW(Z){if(globalThis.Intl&&Intl.Segmenter)return CJ||=new Intl.Segmenter,CJ.segment(Z)[Symbol.iterator]()}function I9(Z){return Z.toLowerCase()}function ZY(Z,Q){return B9(Z.split(Q))}function YY(Z,Q){return Z.startsWith(Q)}var xJ=[" ","\t",` +`,"\v","\f","\r","…","\u2028","\u2029"].join(""),$3=new RegExp(`^[${xJ}]*`),m3=new RegExp(`[${xJ}]*$`);function HY(Z){return Math.round(Z)}function h8(Z){if(typeof Z==="string")return"String";else if(typeof Z==="boolean")return"Bool";else if(SW(Z))return"Result";else if(NZ(Z))return"List";else if(Z instanceof V9)return"BitArray";else if(Z instanceof o8)return"Dict";else if(Number.isInteger(Z))return"Int";else if(Array.isArray(Z))return"Array";else if(typeof Z==="number")return"Float";else if(Z===null)return"Nil";else if(Z===void 0)return"Nil";else{let Q=typeof Z;return Q.charAt(0).toUpperCase()+Q.slice(1)}}var{MAX_SAFE_INTEGER:u3,MIN_SAFE_INTEGER:c3}=Number;function g6(Z){let Q=Z.toString().replace("+","");if(Q.indexOf(".")>=0)return Q;else{let Y=Q.indexOf("e");if(Y>=0)return Q.slice(0,Y)+".0"+Q.slice(Y);else return Q+".0"}}class PW{#Z=new Set;inspect(Z){let Q=typeof Z;if(Z===!0)return"True";if(Z===!1)return"False";if(Z===null)return"//js(null)";if(Z===void 0)return"Nil";if(Q==="string")return this.#X(Z);if(Q==="bigint"||Number.isInteger(Z))return Z.toString();if(Q==="number")return g6(Z);if(Z instanceof rQ)return this.#V(Z);if(Z instanceof V9)return this.#W(Z);if(Z instanceof RegExp)return`//js(${Z})`;if(Z instanceof Date)return`//js(Date("${Z.toISOString()}"))`;if(Z instanceof globalThis.Error)return`//js(${Z.toString()})`;if(Z instanceof Function){let J=[];for(let X of Array(Z.length).keys())J.push(String.fromCharCode(X+97));return`//fn(${J.join(", ")}) { ... }`}if(this.#Z.size===this.#Z.add(Z).size)return"//js(circular reference)";let Y;if(Array.isArray(Z))Y=`#(${Z.map((J)=>this.inspect(J)).join(", ")})`;else if(NZ(Z))Y=this.#Q(Z);else if(Z instanceof F)Y=this.#Y(Z);else if(Z instanceof o8)Y=this.#J(Z);else if(Z instanceof Set)return`//js(Set(${[...Z].map((J)=>this.inspect(J)).join(", ")}))`;else Y=this.#K(Z);return this.#Z.delete(Z),Y}#K(Z){let Q=Object.getPrototypeOf(Z)?.constructor?.name||"Object",Y=[];for(let K of Object.keys(Z))Y.push(`${this.inspect(K)}: ${this.inspect(Z[K])}`);let J=Y.length?" "+Y.join(", ")+" ":"";return`//js(${Q==="Object"?"":Q+" "}{${J}})`}#J(Z){let Q="dict.from_list([",Y=!0;return Q=z6(Z,Q,(J,X,K)=>{if(!Y)J=J+", ";return Y=!1,J+"#("+this.inspect(X)+", "+this.inspect(K)+")"}),Q+"])"}#Y(Z){let Q=Object.keys(Z).map((Y)=>{let J=this.inspect(Z[Y]);return isNaN(parseInt(Y))?`${Y}: ${J}`:J}).join(", ");return Q?`${Z.constructor.name}(${Q})`:Z.constructor.name}#Q(Z){if(dQ(Z))return"[]";let Q='charlist.from_string("',Y="[",J=Z;while(f8(J)){let X=J.head;if(J=J.tail,Y!=="[")Y+=", ";if(Y+=this.inspect(X),Q)if(Number.isInteger(X)&&X>=32&&X<=126)Q+=String.fromCharCode(X);else Q=null}if(Q)return Q+'")';else return Y+"]"}#X(Z){let Q='"';for(let Y=0;Y"~"&&J<" ")Q+="\\u{"+J.charCodeAt(0).toString(16).toUpperCase().padStart(4,"0")+"}";else Q+=J}}return Q+='"',Q}#V(Z){return`//utfcodepoint(${String.fromCodePoint(Z.value)})`}#W(Z){if(Z.bitSize===0)return"<<>>";let Q="<<";for(let Y=0;Y>8-Y,Q+=`:size(${Y})`}return Q+=">>",Q}}function WY(Z,Q){if(Z instanceof o8){let J=g8(Z,Q);return E0(J.isOk()?new k(J[0]):new m)}if(Z instanceof WeakMap||Z instanceof Map){let J={},X=Z.get(Q,J);if(X===J)return E0(new m);return E0(new k(X))}let Y=Number.isInteger(Q);if(Y&&Q>=0&&Q<8&&NZ(Z)){let J=0;for(let X of Z){if(J===Q)return E0(new k(X));J++}return h0("Indexable")}if(Y&&Array.isArray(Z)||Z&&typeof Z==="object"||Z&&Object.getPrototypeOf(Z)===Object.prototype){if(Q in Z)return E0(new k(Z[Q]));return E0(new m)}return h0(Y?"Indexable":"Dict")}function SJ(Z,Q,Y,J,X){if(!(NZ(Z)||Array.isArray(Z))){let V=jJ("List",h8(Z),X);return[X,B9([V])]}let K=[];for(let V of Z){let W=Q(V),[H,z]=W;if(f8(z)){let[U,q]=Y(W,J.toString());return[X,q]}K.push(H),J++}return[B9(K),X]}function TJ(Z){if(typeof Z==="number")return E0(Z);return h0(0)}function LJ(Z){if(Number.isInteger(Z))return E0(Z);return h0(0)}function EJ(Z){if(typeof Z==="string")return E0(Z);return h0("")}function B9(Z){let Q=YJ(),Y=Z.length;while(Y--)Q=C6(Z[Y],Q);return Q}function NZ(Z){return dQ(Z)||f8(Z)}function SW(Z){return C0(Z)||JJ(Z)}function TW(Z){return-1*Z}function $8(Z){if(Z>=0)return HY(Z);else return 0-HY(TW(Z))}function FY(Z){if(Z>=0)return Z;else return Z*-1}function N6(Z,Q){if(Z===Q)return new j0;else if(Z=0)return new _6(X,Y);else return new _6(X-1,Q+Y)}function yJ(Z,Q){let Y=(U)=>{if(U.seconds>=0)return[U.seconds,U.nanoseconds];else return[U.seconds*-1-1,1e9-U.nanoseconds]},J=Y(Z),X,K;X=J[0],K=J[1];let V=Y(Q),W,H;W=V[0],H=V[1];let z=N6(X,W);return sQ(z,N6(K,H))}function bJ(Z,Q){let Y=new _6(Z.seconds+Q.seconds,Z.nanoseconds+Q.nanoseconds);return kJ(Y)}function S8(Z){return new _6(Z,0)}function zY(Z){return S8(Z*60)}function fJ(Z){let Q=new _6(0,Z);return kJ(Q)}function R6(Z){let Q=Z.seconds*1000,Y=globalThis.Math.trunc(Z.nanoseconds/1e6);return Q+Y}function M9(Z,Q){if(Z instanceof w){let Y=Z[0];return new w(Q(Y))}else return Z}function Z6(Z,Q){if(Z instanceof w){let Y=Z[0];return Q(Y)}else return Z}function h6(Z,Q){if(Z instanceof w)return Z[0];else return Q}function gJ(Z,Q){if(Z instanceof w)return Z;else return new Y0(Q)}function _J(Z){return Object.fromEntries(Z)}function D6(Z){return Z}function hJ(Z){let Q=[];while(f8(Z))Q.push(i8(Z)),Z=l0(Z);return Q}function vJ(){return null}function X0(Z){return D6(Z)}function RZ(Z){return D6(Z)}function c0(Z){return D6(Z)}function UY(){return vJ()}function v6(Z){return _J(Z)}function O9(Z){return hJ(Z)}function M6(Z,Q){let J=T0(Z,Q);return O9(J)}function A9(Z){return Z.replaceAll(/[><&"']/g,(Q)=>{switch(Q){case">":return">";case"<":return"<";case"'":return"'";case"&":return"&";case'"':return""";default:return Q}})}var y=G([]),$6=new Y0(void 0);var EW=VJ(),CW=XJ(),wW=KJ();function BZ(Z,Q){if(Z.name===Q.name)return wW;else if(Z.name{return BZ(K,X)});return bW(J,y)}}function a0(Z,Q){return sJ(Z,Q)}function aJ(Z,Q){return lJ(Z,Q)}function gW(Z,Q){if(Q)return a0(Z,"");else return aJ(Z,RZ(!1))}function M(Z){return a0("class",Z)}function oJ(){return M("")}function T8(Z){return a0("id",Z)}function _W(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return J;else{let X=Y.head[0];if(X==="")Z=Y.tail,Q=J;else{let K=Y.head[1];if(K==="")Z=Y.tail,Q=J;else{let V=Y.tail,W=X,H=K;Z=V,Q=J+W+":"+H+";"}}}}}function tJ(Z){return a0("style",_W(Z,""))}function OY(Z){return a0("href",Z)}function eJ(Z){return a0("target",Z)}function ZX(Z){return a0("rel",Z)}function QX(Z){return gW("disabled",Z)}function P9(Z){return a0("placeholder",Z)}function YX(Z){return a0("value",Z)}class O6 extends F{constructor(Z,Q,Y){super();this.synchronous=Z,this.before_paint=Q,this.after_paint=Y}}class PY extends F{constructor(Z,Q,Y,J,X){super();this.dispatch=Z,this.emit=Q,this.select=Y,this.root=J,this.provide=X}}var J6=new O6(G([]),G([]),G([]));function j(){return J6}function X6(Z){return new O6(G([(Y)=>{let J=Y.dispatch;return Z(J)}]),J6.before_paint,J6.after_paint)}function A6(Z){let Q=(Y)=>{let J=Y.root(),X=Y.dispatch;return Z(X,J)};return new O6(J6.synchronous,G([Q]),J6.after_paint)}function SY(Z,Q){return new O6(G([(J)=>{return J.emit(Z,Q)}]),J6.before_paint,J6.after_paint)}function U8(Z){return x0(Z,J6,(Q,Y)=>{return new O6(x0(Y.synchronous,Q.synchronous,f6),x0(Y.before_paint,Q.before_paint,f6),x0(Y.after_paint,Q.after_paint,f6))})}function hW(Z,Q,Y){return}function vW(Z,Q){return new PY((Y)=>{return Z.dispatch(Q(Y))},Z.emit,(Y)=>{return hW(Z,Y,Q)},Z.root,Z.provide)}function jY(Z,Q){return T0(Z,(Y)=>{return(J)=>{return Y(vW(J,Q))}})}function j6(Z,Q){return new O6(jY(Z.synchronous,Q),jY(Z.before_paint,Q),jY(Z.after_paint,Q))}function JX(Z,Q,Y,J,X,K){let V=new PY(Q,Y,J,X,K);return zZ(Z.synchronous,(W)=>{return W(V)})}function D0(){return null}function P6(Z,Q){return Z?.get(Q)}function u6(Z,Q,Y){return Z?.get(Q)??Y()}function L8(Z,Q){return Z&&Z.has(Q)}function V8(Z,Q,Y){return Z??=new Map,Z.set(Q,Y),Z}function DZ(Z,Q){return Z?.delete(Q),Z}function s(Z){return Z}function KX(Z,Q){if(typeof Z==="number"&&typeof Q==="number")return Z===Q||Z!==Z&&Q!==Q;return Z===Q}function VX(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)if(J instanceof A)return!0;else return!1;else if(J instanceof A)return!1;else{let{head:X,tail:K}=Y,V=J.head,W=J.tail,H=KX(X,V);if(H)Z=K,Q=W;else return H}}}class E8 extends F{constructor(Z,Q,Y,J){super();this.kind=Z,this.key=Q,this.children=Y,this.keyed_children=J}}class q8 extends F{constructor(Z,Q,Y,J,X,K,V,W,H){super();this.kind=Z,this.key=Q,this.namespace=Y,this.tag=J,this.attributes=X,this.children=K,this.keyed_children=V,this.self_closing=W,this.void=H}}class C8 extends F{constructor(Z,Q,Y){super();this.kind=Z,this.key=Q,this.content=Y}}class c8 extends F{constructor(Z,Q,Y,J,X,K){super();this.kind=Z,this.key=Q,this.namespace=Y,this.tag=J,this.attributes=X,this.inner_html=K}}class G8 extends F{constructor(Z,Q,Y,J){super();this.kind=Z,this.key=Q,this.mapper=Y,this.child=J}}class c6 extends F{constructor(Z,Q,Y,J){super();this.kind=Z,this.key=Q,this.dependencies=Y,this.view=J}}var p6=0,S6=1,d6=2,GX=3,K6=4,LY=5;function OZ(Z,Q,Y){return new E8(p6,Z,Q,Y)}function T9(Z,Q,Y,J,X,K,V,W){return new q8(S6,Z,Q,Y,nJ(J),X,K,V,W)}function L9(Z,Q){if(Q==="")if(Z==="area")return!0;else if(Z==="base")return!0;else if(Z==="br")return!0;else if(Z==="col")return!0;else if(Z==="embed")return!0;else if(Z==="hr")return!0;else if(Z==="img")return!0;else if(Z==="input")return!0;else if(Z==="link")return!0;else if(Z==="meta")return!0;else if(Z==="param")return!0;else if(Z==="source")return!0;else if(Z==="track")return!0;else if(Z==="wbr")return!0;else return!1;else return!1}function EY(Z,Q){return new C8(d6,Z,Q)}function WX(Z,Q){if(Z instanceof G8){let Y=Z.mapper;return new G8(K6,Z.key,(J)=>{return s(Q)(Y(J))},s(Z.child))}else return new G8(K6,Z.key,s(Q),s(Z))}function HX(Z,Q,Y){return new c6(LY,Z,Q,Y)}function MZ(Z,Q){if(Q instanceof E8)return new E8(Q.kind,Z,Q.children,Q.keyed_children);else if(Q instanceof q8)return new q8(Q.kind,Z,Q.namespace,Q.tag,Q.attributes,Q.children,Q.keyed_children,Q.self_closing,Q.void);else if(Q instanceof C8)return new C8(Q.kind,Z,Q.content);else if(Q instanceof c8)return new c8(Q.kind,Z,Q.namespace,Q.tag,Q.attributes,Q.inner_html);else if(Q instanceof G8){let Y=Q.child;return new G8(Q.kind,Z,Q.mapper,MZ(Z,Y))}else{let Y=Q.view;return new c6(Q.kind,Z,Q.dependencies,()=>{return MZ(Z,Y())})}}function I8(Z,Q,Y){return T9("","",Z,Q,Y,D0(),!1,L9(Z,""))}function AZ(Z){return EY("",Z)}function r6(){return EY("","")}function FX(Z){return OZ("",Z,D0())}function E9(Z,Q){return HX("",Z,Q)}function C9(Z){return s(Z)}function jZ(Z,Q){return WX(Z,Q)}function S(Z){return AZ(Z)}function c(Z,Q){return I8("div",Z,Q)}function CY(Z,Q){return I8("a",Z,Q)}function x(Z,Q){return I8("span",Z,Q)}function N0(Z,Q){return I8("button",Z,Q)}function w9(Z){return I8("input",Z,y)}function PZ(Z,Q){return SY(Z,Q)}function x9(Z,Q){return iJ(Z,z0(Q,(Y)=>{return new j9(!1,!1,Y)}),y,DY,DY,0,0)}function R0(Z){return x9("click",O0(Z))}function zX(Z){return x9("input",q6(G(["target","value"]),X8,(Q)=>{return O0(Z(Q))}))}function UX(){let Z=Date.now(),Q=Z%1000,Y=Q*1e6;return[(Z-Q)/1000,Y]}class SZ extends F{constructor(Z,Q){super();this.seconds=Z,this.nanoseconds=Q}}function pW(Z){let Q=1e9,Y=w6(Z.nanoseconds,Q),J=Z.nanoseconds-Y,X=Z.seconds+x6(J,Q);if(Y>=0)return new SZ(X,Y);else return new SZ(X-1,Q+Y)}function k9(){let Z=UX(),Q,Y;return Q=Z[0],Y=Z[1],pW(new SZ(Q,Y))}function qX(Z,Q){let Y=S8(Q.seconds-Z.seconds),J=fJ(Q.nanoseconds-Z.nanoseconds);return bJ(Y,J)}function TZ(Z){let Q=F0(Z.seconds),Y=F0(Z.nanoseconds);return Q+Y/1e9}class y9 extends F{constructor(Z,Q,Y,J){super();this.index=Z,this.removed=Q,this.changes=Y,this.children=J}}class NX extends F{constructor(Z,Q){super();this.kind=Z,this.content=Q}}class RX extends F{constructor(Z,Q){super();this.kind=Z,this.inner_html=Q}}class BX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.added=Q,this.removed=Y}}class DX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.key=Q,this.before=Y}}class MX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.index=Q,this.with=Y}}class OX extends F{constructor(Z,Q){super();this.kind=Z,this.index=Q}}class AX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.children=Q,this.before=Y}}var xY=0,kY=1,yY=2,bY=3,fY=4,gY=5,_Y=6;function hY(Z,Q,Y,J){return new y9(Z,Q,Y,J)}function jX(Z){return new NX(xY,Z)}function PX(Z){return new RX(kY,Z)}function vY(Z,Q){return new BX(yY,Z,Q)}function SX(Z,Q){return new DX(bY,Z,Q)}function TX(Z){return new OX(fY,Z)}function p8(Z,Q){return new MX(gY,Z,Q)}function $Y(Z,Q){return new AX(_Y,Z,Q)}class EX extends F{constructor(Z,Q,Y,J,X,K,V,W,H){super();this.kind=Z,this.open_shadow_root=Q,this.will_adopt_styles=Y,this.observed_attributes=J,this.observed_properties=X,this.requested_contexts=K,this.provided_contexts=V,this.vdom=W,this.memos=H}}class CX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.patch=Q,this.memos=Y}}class wX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.name=Q,this.data=Y}}class xX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.key=Q,this.value=Y}}class kX extends F{constructor(Z,Q){super();this.kind=Z,this.messages=Q}}var yX=(Z)=>Z instanceof kX;class bX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.name=Q,this.value=Y}}var fX=(Z)=>Z instanceof bX;class gX extends F{constructor(Z,Q,Y){super();this.kind=Z,this.name=Q,this.value=Y}}var _X=(Z)=>Z instanceof gX;class hX extends F{constructor(Z,Q,Y,J){super();this.kind=Z,this.path=Q,this.name=Y,this.event=J}}var vX=(Z)=>Z instanceof hX;class $X extends F{constructor(Z,Q,Y){super();this.kind=Z,this.key=Q,this.value=Y}}var mX=(Z)=>Z instanceof $X;var aW=0,oW=1,tW=2,eW=3;function uX(Z,Q,Y,J,X,K,V,W){return new EX(aW,Z,Q,Y,J,X,K,V,W)}function mY(Z,Q){return new CX(oW,Z,Q)}function cX(Z,Q){return new wX(tW,Z,Q)}function pX(Z,Q){return new xX(eW,Z,Q)}class cY extends F{}class pY extends F{constructor(Z,Q){super();this.key=Z,this.parent=Q}}class dY extends F{constructor(Z,Q){super();this.index=Z,this.parent=Q}}class rX extends F{constructor(Z){super();this.parent=Z}}var CZ="\r",EZ="\t",rY=` +`,wZ=new cY;function dX(Z){if(Z instanceof A)return"";else{let Q=Z.tail;return QY(Q)}}function sY(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(X instanceof cY)return dX(K);else if(X instanceof pY){let{key:V,parent:W}=X;Z=J,Q=W,Y=B(EZ,B(V,K))}else if(X instanceof dY){let{index:V,parent:W}=X,H=B(EZ,B($(V),K));Z=J,Q=W,Y=H}else if(!J)return dX(K);else{let V=X.parent;if(K instanceof A)Z=J,Q=V,Y=K;else{let W=K.tail;Z=J,Q=V,Y=B(CZ,W)}}}}function ZH(Z){return sY(!0,Z,y)}function QH(Z,Q){while(!0){let Y=Z,J=Q;if(J instanceof A)return!1;else{let{head:X,tail:K}=J,V=YY(Y,X);if(V)return V;else Z=Y,Q=K}}}function sX(Z,Q){if(Q instanceof A)return!1;else return QH(ZH(Z),Q)}function lX(Z){return OJ(Z,CZ)}function p0(Z,Q,Y){if(Y==="")return new dY(Q,Z);else return new pY(Y,Z)}function xZ(Z){return new rX(Z)}function lY(Z,Q){return sY(!1,Z,B(rY,B(Q,y)))}function b9(Z){return sY(!1,Z,y)}class d8 extends F{constructor(Z,Q,Y,J,X){super();this.events=Z,this.vdoms=Q,this.old_vdoms=Y,this.dispatched_paths=J,this.next_dispatched_paths=X}}class G6 extends F{constructor(Z,Q){super();this.handlers=Z,this.children=Q}}class yZ extends F{constructor(Z,Q){super();this.mapper=Z,this.events=Q}}class oX extends F{constructor(Z,Q,Y){super();this.handlers=Z,this.children=Q,this.vdoms=Y}}class aY extends F{constructor(Z,Q){super();this.path=Z,this.handler=Q}}class iY extends F{constructor(Z){super();this.path=Z}}function YH(Z,Q){return(Y)=>{return Z(Q(Y))}}function tX(){return new G6(D0(),D0())}function oY(){return new d8(tX(),D0(),D0(),y,y)}function eX(Z,Q,Y,J){return V8(Z,lY(Q,Y),J)}function nX(Z,Q,Y){return x0(Y,Z,(J,X)=>{if(X instanceof k0){let{name:K,handler:V}=X;return eX(J,Q,K,V)}else return J})}function kZ(Z,Q,Y,J,X,K){while(!0){let V=Z,W=Q,H=Y,z=J,U=X,q=K,I=U+1;if(q instanceof A)return new oX(V,W,H);else{let N=q.head;if(N instanceof E8){let R=q.tail,O=N.key,D=N.children,T=p0(z,U,O),C=kZ(V,W,H,T,0,D),P,L,E;P=C.handlers,L=C.children,E=C.vdoms,Z=P,Q=L,Y=E,J=z,X=I,K=R}else if(N instanceof q8){let R=q.tail,O=N.key,D=N.attributes,T=N.children,C=p0(z,U,O),P=nX(V,C,D),L=kZ(P,W,H,C,0,T),E,v,Z0;E=L.handlers,v=L.children,Z0=L.vdoms,Z=E,Q=v,Y=Z0,J=z,X=I,K=R}else if(N instanceof C8){let R=q.tail;Z=V,Q=W,Y=H,J=z,X=I,K=R}else if(N instanceof c8){let R=q.tail,O=N.key,D=N.attributes,T=p0(z,U,O);Z=nX(V,T,D),Q=W,Y=H,J=z,X=I,K=R}else if(N instanceof G8){let R=q.tail,O=N.key,D=N.mapper,T=N.child,C=p0(z,U,O),P=kZ(D0(),D0(),H,xZ(C),0,B(T,y)),L=P.vdoms,E=new G6(P.handlers,P.children),v=new yZ(D,E),Z0=V8(W,b9(C),v);Z=V,Q=Z0,Y=L,J=z,X=I,K=R}else{let R=q.tail,O=N.view,D=O(),T=V8(H,O,D),C=U,P=B(D,R);Z=V,Q=W,Y=T,J=z,X=C,K=P}}}}function tY(Z,Q,Y,J,X){let K=Z.vdoms,V,W;V=Q.handlers,W=Q.children;let H=kZ(V,W,K,Y,J,X),z,U,q;return z=H.handlers,U=H.children,q=H.vdoms,[new d8(Z.events,q,Z.old_vdoms,Z.dispatched_paths,Z.next_dispatched_paths),new G6(z,U)]}function bZ(Z,Q,Y,J,X){let K=B(X,y);return tY(Z,Q,Y,J,K)}function ZK(Z){let Q=oY(),Y=bZ(Q,Q.events,wZ,0,Z),J,X;return J=Y[0],X=Y[1],new d8(X,J.vdoms,J.old_vdoms,J.dispatched_paths,J.next_dispatched_paths)}function QK(Z){return new d8(Z.events,D0(),Z.vdoms,Z.next_dispatched_paths,y)}function YK(Z){return Z.events}function JK(Z,Q){return new d8(Q,Z.vdoms,Z.old_vdoms,Z.dispatched_paths,Z.next_dispatched_paths)}function s6(Z){return Z.vdoms}function XK(Z,Q,Y){return u6(Z.old_vdoms,Q,Y)}function KK(Z,Q,Y){let J=u6(Z.old_vdoms,Q,Y),X=V8(Z.vdoms,Y,J);return new d8(Z.events,X,Z.old_vdoms,Z.dispatched_paths,Z.next_dispatched_paths)}function VK(Z,Q,Y){let J=V8(Z.vdoms,Q,Y);return new d8(Z.events,J,Z.old_vdoms,Z.dispatched_paths,Z.next_dispatched_paths)}function GK(Z,Q,Y){return u6(Z.children,Q,()=>{return new yZ(Y,tX())}).events}function WK(Z,Q,Y,J){let X=new yZ(Y,J),K=V8(Z.children,Q,X);return new G6(Z.handlers,K)}function l6(Z,Q,Y,J){let X=eX(Z.handlers,Q,Y,J);return new G6(X,Z.children)}function HK(Z,Q,Y){return DZ(Z,lY(Q,Y))}function fZ(Z,Q,Y){let J=HK(Z.handlers,Q,Y);return new G6(J,Z.children)}function aX(Z,Q,Y){return x0(Y,Z,(J,X)=>{if(X instanceof k0){let K=X.name;return HK(J,Q,K)}else return J})}function nY(Z,Q,Y,J,X,K){while(!0){let V=Z,W=Q,H=Y,z=J,U=X,q=K,I=U+1;if(q instanceof A)return new G6(V,W);else{let N=q.head;if(N instanceof E8){let R=q.tail,O=N.key,D=N.children,T=p0(z,U,O),C=nY(V,W,H,T,0,D),P,L;P=C.handlers,L=C.children,Z=P,Q=L,Y=H,J=z,X=I,K=R}else if(N instanceof q8){let R=q.tail,O=N.key,D=N.attributes,T=N.children,C=p0(z,U,O),P=aX(V,C,D),L=nY(P,W,H,C,0,T),E,v;E=L.handlers,v=L.children,Z=E,Q=v,Y=H,J=z,X=I,K=R}else if(N instanceof C8){let R=q.tail;Z=V,Q=W,Y=H,J=z,X=I,K=R}else if(N instanceof c8){let R=q.tail,O=N.key,D=N.attributes,T=p0(z,U,O);Z=aX(V,T,D),Q=W,Y=H,J=z,X=I,K=R}else if(N instanceof G8){let R=q.tail,O=N.key,D=p0(z,U,O),T=DZ(W,b9(D));Z=V,Q=T,Y=H,J=z,X=I,K=R}else{let R=q.tail,O=N.view;if(L8(H,O)){let T=P6(H,O),C=B(T,R);Z=V,Q=W,Y=H,J=z,X=U,K=C}else Z=V,Q=W,Y=H,J=z,X=I,K=R}}}}function gZ(Z,Q,Y,J,X){return nY(Q.handlers,Q.children,Z.old_vdoms,Y,J,B(X,y))}function r8(Z,Q,Y,J,X,K){let V=gZ(Z,Q,Y,J,X);return bZ(Z,V,Y,J,K)}function JH(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(X instanceof A)return $6;else{let V=X.tail;if(V instanceof A){let W=X.head;if(L8(J.handlers,W)){let z=P6(J.handlers,W);return new w(z0(z,(U)=>{return new j9(U.prevent_default,U.stop_propagation,s(K)(U.message))}))}else return $6}else{let W=X.head,H=V;if(L8(J.children,W)){let U=P6(J.children,W),q=YH(K,U.mapper);Z=U.events,Q=H,Y=q}else return $6}}}}function eY(Z,Q,Y,J){let X=lX(Q+rY+Y),K=JH(Z.events,X,s);if(K instanceof w){let V=K[0],W=K8(J,V);if(W instanceof w){let H=W[0];return new aY(Q,H)}else return new iY(Q)}else return new iY(Q)}function Z7(Z,Q){let Y=B(Q.path,Z.next_dispatched_paths),J=new d8(Z.events,Z.vdoms,Z.old_vdoms,Z.dispatched_paths,Y);if(Q instanceof aY){let X=Q.handler;return[J,new w(X)]}else return[J,$6]}function FK(Z,Q,Y,J){let X=eY(Z,Q,Y,J);return((K)=>{return Z7(Z,K)})(X)}function _Z(Z,Q){return sX(Q,Z.dispatched_paths)}class Q7 extends F{constructor(Z){super();this.message=Z}}var zK=(Z)=>Z instanceof Q7;class Y7 extends F{constructor(Z){super();this.callback=Z}}var UK=(Z)=>Z instanceof Y7;class J7 extends F{constructor(Z){super();this.callback=Z}}var qK=(Z)=>Z instanceof J7;class vZ extends F{constructor(Z){super();this.message=Z}}var IK=(Z)=>new vZ(Z),i6=(Z)=>Z instanceof vZ;class X7 extends F{constructor(Z,Q){super();this.name=Z,this.data=Q}}var NK=(Z,Q)=>new X7(Z,Q),n6=(Z)=>Z instanceof X7;class K7 extends F{constructor(Z,Q){super();this.key=Z,this.value=Q}}var RK=(Z,Q)=>new K7(Z,Q),BK=(Z)=>Z instanceof K7;class V7 extends F{}var a6=(Z)=>Z instanceof V7;class W7 extends F{constructor(Z,Q,Y,J,X){super();this.name=Z,this.init=Q,this.update=Y,this.view=J,this.config=X}}class _0 extends F{constructor(Z,Q,Y,J,X,K,V,W,H,z,U,q,I){super();this.open_shadow_root=Z,this.adopt_styles=Q,this.delegates_focus=Y,this.attributes=J,this.properties=X,this.contexts=K,this.is_form_associated=V,this.on_form_autofill=W,this.on_form_reset=H,this.on_form_restore=z,this.on_connect=U,this.on_adopt=q,this.on_disconnect=I}}class v0 extends F{constructor(Z){super();this.apply=Z}}var DK=new _0(!0,!0,!1,y,y,y,!1,new m,new m,new m,new m,new m,new m);function MK(Z){return x0(Z,DK,(Q,Y)=>{return Y.apply(Q)})}var N8=(Z,Q)=>{if(Z===Q)return!0;if(Z==null||Q==null)return!1;let Y=typeof Z;if(Y!==typeof Q)return!1;if(Y!=="object")return!1;if(Z.constructor!==Q.constructor)return!1;if(Array.isArray(Z))return XH(Z,Q);return KH(Z,Q)},XH=(Z,Q)=>{let Y=Z.length;if(Y!==Q.length)return!1;while(Y--)if(!N8(Z[Y],Q[Y]))return!1;return!0},KH=(Z,Q)=>{let Y=Object.keys(Z),J=Y.length;if(Object.keys(Q).length!==J)return!1;while(J--){let X=Y[J];if(!Object.hasOwn(Q,X))return!1;if(!N8(Z[X],Q[X]))return!1}return!0};class jK extends F{constructor(Z,Q){super();this.patch=Z,this.cache=Q}}class H7 extends F{constructor(Z,Q,Y){super();this.patch=Z,this.cache=Q,this.events=Y}}class PK extends F{constructor(Z,Q,Y){super();this.added=Z,this.removed=Q,this.events=Y}}function AK(Z,Q,Y,J,X,K,V){while(!0){let W=Z,H=Q,z=Y,U=J,q=X,I=K,N=V;if(U instanceof A)if(q instanceof A)return new PK(I,N,z);else{let R=q.head;if(R instanceof k0){let O=R,D=q.tail,T=R.name,C=R.handler,P=l6(z,H,T,C),L=B(O,I);Z=W,Q=H,Y=P,J=U,X=D,K=L,V=N}else{let O=R,D=q.tail,T=B(O,I);Z=W,Q=H,Y=z,J=U,X=D,K=T,V=N}}else if(q instanceof A){let R=U.head;if(R instanceof k0){let O=R,D=U.tail,T=R.name,C=fZ(z,H,T),P=B(O,N);Z=W,Q=H,Y=C,J=D,X=q,K=I,V=P}else{let O=R,D=U.tail,T=B(O,N);Z=W,Q=H,Y=z,J=D,X=q,K=I,V=T}}else{let{head:R,tail:O}=U,D=q.head,T=q.tail,C=BZ(R,D);if(C instanceof M0)if(R instanceof k0){let P=R.name;Z=W,Q=H,Y=fZ(z,H,P),J=O,X=q,K=I,V=B(R,N)}else Z=W,Q=H,Y=z,J=O,X=q,K=I,V=B(R,N);else if(C instanceof j0)if(R instanceof z8)if(D instanceof z8){let P,L=D.name;if(L==="value")P=W||R.value!==D.value;else if(L==="checked")P=W||R.value!==D.value;else if(L==="selected")P=W||R.value!==D.value;else P=R.value!==D.value;let E=P,v;if(E)v=B(D,I);else v=I;let Z0=v;Z=W,Q=H,Y=z,J=O,X=T,K=Z0,V=N}else if(D instanceof k0){let{name:P,handler:L}=D;Z=W,Q=H,Y=l6(z,H,P,L),J=O,X=T,K=B(D,I),V=B(R,N)}else Z=W,Q=H,Y=z,J=O,X=T,K=B(D,I),V=B(R,N);else if(R instanceof m6)if(D instanceof m6){let P,L=D.name;if(L==="scrollLeft")P=!0;else if(L==="scrollRight")P=!0;else if(L==="value")P=W||!N8(R.value,D.value);else if(L==="checked")P=W||!N8(R.value,D.value);else if(L==="selected")P=W||!N8(R.value,D.value);else P=!N8(R.value,D.value);let E=P,v;if(E)v=B(D,I);else v=I;let Z0=v;Z=W,Q=H,Y=z,J=O,X=T,K=Z0,V=N}else if(D instanceof k0){let{name:P,handler:L}=D;Z=W,Q=H,Y=l6(z,H,P,L),J=O,X=T,K=B(D,I),V=B(R,N)}else Z=W,Q=H,Y=z,J=O,X=T,K=B(D,I),V=B(R,N);else if(D instanceof k0){let{name:P,handler:L}=D,E=R.prevent_default.kind!==D.prevent_default.kind||R.stop_propagation.kind!==D.stop_propagation.kind||R.debounce!==D.debounce||R.throttle!==D.throttle,v;if(E)v=B(D,I);else v=I;let Z0=v;Z=W,Q=H,Y=l6(z,H,P,L),J=O,X=T,K=Z0,V=N}else{let P=R.name;Z=W,Q=H,Y=fZ(z,H,P),J=O,X=T,K=B(D,I),V=B(R,N)}else if(D instanceof k0){let{name:P,handler:L}=D;Z=W,Q=H,Y=l6(z,H,P,L),J=U,X=T,K=B(D,I),V=N}else Z=W,Q=H,Y=z,J=U,X=T,K=B(D,I),V=N}}}function VH(Z,Q,Y,J){if(Y==="input"&&Q==="")return _Z(Z,J);else if(Y==="select"&&Q==="")return _Z(Z,J);else if(Y==="textarea"&&Q==="")return _Z(Z,J);else return!1}function $Z(Z,Q,Y,J,X,K,V,W,H,z,U,q,I,N){while(!0){let R=Z,O=Q,D=Y,T=J,C=X,P=K,L=V,E=W,v=H,Z0=z,Q0=U,i=q,U0=I,B0=N;if(R instanceof A)if(D instanceof A){let $0=new y9(v,L,Z0,Q0);return new H7($0,U0,B0)}else{let $0=tY(U0,B0,i,E,D),m0,H0;m0=$0[0],H0=$0[1];let K0=$Y(D,E-P),d=B(K0,Z0),n=new y9(v,L,d,Q0);return new H7(n,m0,H0)}else if(D instanceof A){let{head:$0,tail:m0}=R,H0;if($0.key===""||!L8(C,$0.key))H0=L+1;else H0=L;let d=H0,n=gZ(U0,B0,i,E,$0);Z=m0,Q=O,Y=D,J=T,X=C,K=P,V=d,W=E,H=v,z=Z0,U=Q0,q=i,I=U0,N=n}else{let $0=R.head,m0=D.head;if($0.key!==m0.key){let H0=R.tail,K0=D.tail,d=L8(O,m0.key);if(L8(T,$0.key))if(d)if(L8(C,$0.key))Z=H0,Q=O,Y=D,J=T,X=C,K=P-1,V=L,W=E,H=v,z=Z0,U=Q0,q=i,I=U0,N=B0;else{let l=P6(O,m0.key),r=E-P,h=B(SX(m0.key,r),Z0),p=V8(C,m0.key,void 0);Z=B(l,R),Q=O,Y=D,J=T,X=p,K=P+1,V=L,W=E,H=v,z=h,U=Q0,q=i,I=U0,N=B0}else{let _=E-P,l=bZ(U0,B0,i,E,m0),r,h;r=l[0],h=l[1];let p=$Y(G([m0]),_),u=B(p,Z0);Z=R,Q=O,Y=K0,J=T,X=C,K=P+1,V=L,W=E+1,H=v,z=u,U=Q0,q=i,I=r,N=h}else if(d){let _=E-P,l=B(TX(_),Z0),r=gZ(U0,B0,i,E,$0);Z=H0,Q=O,Y=D,J=T,X=C,K=P-1,V=L,W=E,H=v,z=l,U=Q0,q=i,I=U0,N=r}else{let _=p8(E-P,m0),l=r8(U0,B0,i,E,$0,m0),r,h;r=l[0],h=l[1],Z=H0,Q=O,Y=K0,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(_,Z0),U=Q0,q=i,I=r,N=h}}else{let H0=R.head;if(H0 instanceof E8){let K0=D.head;if(K0 instanceof E8){let d=H0,n=R.tail,_=K0,l=D.tail,r=$Z(d.children,d.keyed_children,_.children,_.keyed_children,D0(),0,0,0,E,y,y,p0(i,E,_.key),U0,B0),h,p,u;h=r.patch,p=r.cache,u=r.events;let L0;if(h.changes instanceof A)if(h.children instanceof A)if(h.removed===0)L0=Q0;else L0=B(h,Q0);else L0=B(h,Q0);else L0=B(h,Q0);let A8=L0;Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=A8,q=i,I=p,N=u}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}else if(H0 instanceof q8){let K0=D.head;if(K0 instanceof q8){let d=H0,n=K0;if(d.namespace===n.namespace&&d.tag===n.tag){let _=R.tail,l=D.tail,r=p0(i,E,n.key),h=VH(U0,n.namespace,n.tag,r),p=AK(h,r,B0,d.attributes,n.attributes,y,y),u,L0,s0;u=p.added,L0=p.removed,s0=p.events;let A8;if(u instanceof A&&L0 instanceof A)A8=y;else A8=G([vY(u,L0)]);let Z8=A8,E6=$Z(d.children,d.keyed_children,n.children,n.keyed_children,D0(),0,0,0,E,Z8,y,r,U0,s0),Q8,F6,Y9;Q8=E6.patch,F6=E6.cache,Y9=E6.events;let J9;if(Q8.changes instanceof A)if(Q8.children instanceof A)if(Q8.removed===0)J9=Q0;else J9=B(Q8,Q0);else J9=B(Q8,Q0);else J9=B(Q8,Q0);let xG=J9;Z=_,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=xG,q=i,I=F6,N=Y9}else{let _=H0,l=R.tail,r=K0,h=D.tail,p=p8(E-P,r),u=r8(U0,B0,i,E,_,r),L0,s0;L0=u[0],s0=u[1],Z=l,Q=O,Y=h,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(p,Z0),U=Q0,q=i,I=L0,N=s0}}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}else if(H0 instanceof C8){let K0=D.head;if(K0 instanceof C8){let d=H0,n=K0;if(d.content===n.content){let _=R.tail,l=D.tail;Z=_,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=Q0,q=i,I=U0,N=B0}else{let _=R.tail,l=K0,r=D.tail,h=hY(E,0,G([jX(l.content)]),y);Z=_,Q=O,Y=r,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=B(h,Q0),q=i,I=U0,N=B0}}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}else if(H0 instanceof c8){let K0=D.head;if(K0 instanceof c8){let d=H0,n=R.tail,_=K0,l=D.tail,r=p0(i,E,_.key),h=AK(!1,r,B0,d.attributes,_.attributes,y,y),p,u,L0;p=h.added,u=h.removed,L0=h.events;let s0;if(p instanceof A&&u instanceof A)s0=y;else s0=G([vY(p,u)]);let A8=s0,Z8;if(d.inner_html===_.inner_html)Z8=A8;else Z8=B(PX(_.inner_html),A8);let Q8=Z8,F6;if(Q8 instanceof A)F6=Q0;else F6=B(hY(E,0,Q8,G([])),Q0);let Y9=F6;Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=Y9,q=i,I=U0,N=L0}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}else if(H0 instanceof G8){let K0=D.head;if(K0 instanceof G8){let d=H0,n=R.tail,_=K0,l=D.tail,r=p0(i,E,_.key),h=b9(r),p=$Z(B(d.child,y),D0(),B(_.child,y),D0(),D0(),0,0,0,E,y,y,xZ(r),U0,GK(B0,h,d.mapper)),u,L0,s0;u=p.patch,L0=p.cache,s0=p.events;let A8=WK(B0,h,_.mapper,s0),Z8;if(u.changes instanceof A)if(u.children instanceof A)if(u.removed===0)Z8=Q0;else Z8=B(u,Q0);else Z8=B(u,Q0);else Z8=B(u,Q0);let Q8=Z8;Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=Q8,q=i,I=L0,N=A8}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}else{let K0=D.head;if(K0 instanceof c6){let d=H0,n=R.tail,_=K0,l=D.tail;if(VX(d.dependencies,_.dependencies)){let h=KK(U0,d.view,_.view);Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=Z0,U=Q0,q=i,I=h,N=B0}else{let h=XK(U0,d.view,d.view),p=_.view(),u=VK(U0,_.view,p);Z=B(h,n),Q=O,Y=B(p,l),J=T,X=C,K=P,V=L,W=E,H=v,z=Z0,U=Q0,q=i,I=u,N=B0}}else{let d=H0,n=R.tail,_=K0,l=D.tail,r=p8(E-P,_),h=r8(U0,B0,i,E,d,_),p,u;p=h[0],u=h[1],Z=n,Q=O,Y=l,J=T,X=C,K=P,V=L,W=E+1,H=v,z=B(r,Z0),U=Q0,q=i,I=p,N=u}}}}}}function f9(Z,Q,Y){let J=QK(Z),X=$Z(B(Q,y),D0(),B(Y,y),D0(),D0(),0,0,0,0,y,y,wZ,J,YK(J)),K,V,W;return K=X.patch,V=X.cache,W=X.events,new jK(K,JK(V,W))}var SK=(Z)=>Z.reduceRight((Q,Y)=>C6(Y,Q),y),d0=(Z,Q)=>{if(Array.isArray(Z))for(let Y=0;Y{if(!l0(Z))return Q;else if(!l0(Q))return Z;else return v8(Z,Q)};var mZ="http://www.w3.org/1999/xhtml",TK=1,LK=3,z7=8;var EK=!!globalThis.HTMLElement?.prototype?.moveBefore;var{setTimeout:WH,clearTimeout:U7}=globalThis,HH=(Z,Q)=>globalThis.document.createElementNS(Z,Q),CK=(Z)=>globalThis.document.createTextNode(Z),wK=(Z)=>globalThis.document.createComment(Z),FH=()=>globalThis.document.createDocumentFragment(),W6=(Z,Q,Y)=>Z.insertBefore(Q,Y),xK=EK?(Z,Q,Y)=>Z.moveBefore(Q,Y):W6,kK=(Z,Q)=>Z.removeChild(Q),zH=(Z,Q)=>Z.getAttribute(Q),yK=(Z,Q,Y)=>Z.setAttribute(Q,Y),UH=(Z,Q)=>Z.removeAttribute(Q),qH=(Z,Q,Y,J)=>Z.addEventListener(Q,Y,J),bK=(Z,Q,Y)=>Z.removeEventListener(Q,Y),IH=(Z,Q)=>Z.innerHTML=Q,NH=(Z,Q)=>Z.data=Q,W8=Symbol("lustre");class _K{constructor(Z,Q,Y,J){this.kind=Z,this.key=J,this.parent=Q,this.children=[],this.node=Y,this.endNode=null,this.handlers=new Map,this.throttles=new Map,this.debouncers=new Map}get isVirtual(){return this.kind===p6||this.kind===K6}get parentNode(){return this.isVirtual?this.node.parentNode:this.node}}var R8=(Z,Q,Y,J,X)=>{let K=new _K(Z,Q,Y,X);return Y[W8]=K,Q?.children.splice(J,0,K),K},RH=(Z)=>{let Q="";for(let Y=Z[W8];Y.parent;Y=Y.parent){let J=Y.parent&&Y.parent.kind===K6?CZ:EZ;if(Y.key)Q=`${J}${Y.key}${Q}`;else{let X=Y.parent.children.indexOf(Y);Q=`${J}${X}${Q}`}}return Q.slice(1)};class I7{#Z=null;#K;#J;#Y=!1;constructor(Z,Q,Y,{debug:J=!1}={}){this.#Z=Z,this.#K=Q,this.#J=Y,this.#Y=J}mount(Z){R8(S6,null,this.#Z,0,null),this.#N(this.#Z,null,this.#Z[W8],0,Z)}push(Z,Q=null){this.#Q=Q,this.#X.push({node:this.#Z[W8],patch:Z}),this.#V()}#Q;#X=[];#V(){let Z=this.#X;while(Z.length){let{node:Q,patch:Y}=Z.pop(),{children:J}=Q,{changes:X,removed:K,children:V}=Y;if(d0(X,(W)=>this.#W(Q,W)),K)this.#q(Q,J.length-K,K);d0(V,(W)=>{let H=J[W.index|0];this.#X.push({node:H,patch:W})})}}#W(Z,Q){switch(Q.kind){case xY:this.#T(Z,Q);break;case kY:this.#O(Z,Q);break;case yY:this.#S(Z,Q);break;case bY:this.#z(Z,Q);break;case fY:this.#R(Z,Q);break;case gY:this.#G(Z,Q);break;case _Y:this.#F(Z,Q);break}}#F(Z,{children:Q,before:Y}){let J=FH(),X=this.#H(Z,Y);this.#D(J,null,Z,Y|0,Q),W6(Z.parentNode,J,X)}#G(Z,{index:Q,with:Y}){this.#q(Z,Q|0,1);let J=this.#H(Z,Q);this.#N(Z.parentNode,J,Z,Q|0,Y)}#H(Z,Q){Q=Q|0;let{children:Y}=Z,J=Y.length;if(Qthis.#M(J))}#S({node:Z,handlers:Q,throttles:Y,debouncers:J},{added:X,removed:K}){d0(K,({name:V})=>{if(Q.delete(V))bK(Z,V,q7),this.#B(Y,V,0),this.#B(J,V,0);else UH(Z,V),gK[V]?.removed?.(Z,V)}),d0(X,(V)=>this.#P(Z,V))}#T({node:Z},{content:Q}){NH(Z,Q??"")}#O({node:Z},{inner_html:Q}){IH(Z,Q??"")}#D(Z,Q,Y,J,X){d0(X,(K)=>this.#N(Z,Q,Y,J++,K))}#N(Z,Q,Y,J,X){switch(X.kind){case S6:{let K=this.#A(Y,J,X);this.#D(K,null,K[W8],0,X.children),W6(Z,K,Q);break}case d6:{let K=this.#L(Y,J,X);W6(Z,K,Q);break}case p6:{let V=this.#j("lustre:fragment",Y,J,X);if(W6(Z,V,Q),this.#D(Z,Q,V[W8],0,X.children),this.#Y)V[W8].endNode=wK(" /lustre:fragment "),W6(Z,V[W8].endNode,Q);break}case GX:{let K=this.#A(Y,J,X);this.#O({node:K},X),W6(Z,K,Q);break}case K6:{let K=this.#j("lustre:map",Y,J,X);W6(Z,K,Q),this.#N(Z,Q,K[W8],0,X.child);break}case LY:{let K=this.#Q?.get(X.view)??X.view();this.#N(Z,Q,Y,J,K);break}}}#A(Z,Q,{kind:Y,key:J,tag:X,namespace:K,attributes:V}){let W=HH(K||mZ,X);if(R8(Y,Z,W,Q,J),this.#Y&&J)yK(W,"data-lustre-key",J);return d0(V,(H)=>this.#P(W,H)),W}#L(Z,Q,{kind:Y,key:J,content:X}){let K=CK(X??"");return R8(Y,Z,K,Q,J),K}#j(Z,Q,Y,{kind:J,key:X}){let K=this.#Y?wK(BH(Z,X)):CK("");return R8(J,Q,K,Y,X),K}#P(Z,Q){let{debouncers:Y,handlers:J,throttles:X}=Z[W8],{kind:K,name:V,value:W,prevent_default:H,debounce:z,throttle:U}=Q;switch(K){case IY:{let q=W??"";if(V==="virtual:defaultValue"){Z.defaultValue=q;return}else if(V==="virtual:defaultChecked"){Z.defaultChecked=!0;return}else if(V==="virtual:defaultSelected"){Z.defaultSelected=!0;return}if(q!==zH(Z,V))yK(Z,V,q);gK[V]?.added?.(Z,q);break}case NY:Z[V]=W;break;case RY:{if(J.has(V))bK(Z,V,q7);let q=H.kind===BY;qH(Z,V,q7,{passive:q}),this.#B(X,V,U),this.#B(Y,V,z),J.set(V,(I)=>this.#E(Q,I));break}}}#B(Z,Q,Y){let J=Z.get(Q);if(Y>0)if(J)J.delay=Y;else Z.set(Q,{delay:Y});else if(J){let{timeout:X}=J;if(X)U7(X);Z.delete(Q)}}#E(Z,Q){let{currentTarget:Y,type:J}=Q,{debouncers:X,throttles:K}=Y[W8],V=RH(Y),{prevent_default:W,stop_propagation:H,include:z}=Z;if(W.kind===MY)Q.preventDefault();if(H.kind===MY)Q.stopPropagation();if(J==="submit")Q.detail??={},Q.detail.formData=[...new FormData(Q.target,Q.submitter).entries()];let U=this.#K(Q,V,J,z),q=K.get(J);if(q){let N=Date.now(),R=q.last||0;if(N>R+q.delay)q.last=N,q.lastEvent=Q,this.#J(Q,U)}let I=X.get(J);if(I)U7(I.timeout),I.timeout=WH(()=>{if(Q===K.get(J)?.lastEvent)return;this.#J(Q,U)},I.delay);if(!q&&!I)this.#J(Q,U)}}var BH=(Z,Q)=>{if(Q)return` ${Z} key="${A9(Q)}" `;else return` ${Z} `},q7=(Z)=>{let{currentTarget:Q,type:Y}=Z;Q[W8].handlers.get(Y)(Z)},fK=(Z)=>{return{added(Q){Q[Z]=!0},removed(Q){Q[Z]=!1}}},DH=(Z)=>{return{added(Q,Y){Q[Z]=Y}}},gK={checked:fK("checked"),selected:fK("selected"),value:DH("value"),autofocus:{added(Z){queueMicrotask(()=>{Z.focus?.()})}},autoplay:{added(Z){try{Z.play?.()}catch(Q){console.error(Q)}}}};function MH(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return[X,a(K)];else{let V=J.tail,W=J.head[0],H=J.head[1],z=MZ(W,H),U;if(W==="")U=X;else U=V8(X,W,z);let q=U,I=B(z,K);Z=V,Q=q,Y=I}}}function N7(Z){return MH(Z,D0(),y)}function hK(Z,Q,Y){let J=N7(Y),X,K;return X=J[0],K=J[1],T9("","",Z,Q,K,X,!1,L9(Z,""))}function vK(Z,Q,Y,J){let X=N7(J),K,V;return K=X[0],V=X[1],T9("",Z,Q,Y,V,K,!1,L9(Q,Z))}function $K(Z){let Q=N7(Z),Y,J;return Y=Q[0],J=Q[1],OZ("",J,Y)}var mK=(Z)=>{let Q=R8(S6,null,Z,0,null);for(let J=Z.firstChild;J;J=J.nextSibling){let X=uZ(Q,Z,J,0);if(X)return X.vnode}let Y=globalThis.document.createTextNode("");return R8(d6,Q,Y,0,null),Z.insertBefore(Y,Z.firstChild),r6()},uZ=(Z,Q,Y,J)=>{if(Y.nodeType===z7){let X=Y.data.trim();if(X.startsWith("lustre:fragment"))return jH(Z,Q,Y,J);if(X.startsWith("lustre:map"))return PH(Z,Q,Y,J);if(X.startsWith("lustre:memo"))return SH(Z,Q,Y,J);return null}if(Y.nodeType===TK)return OH(Z,Y,J);if(Y.nodeType===LK)return AH(Z,Y,J);return null},OH=(Z,Q,Y)=>{let J=Q.getAttribute("data-lustre-key")??"";if(J)Q.removeAttribute("data-lustre-key");let X=R8(S6,Z,Q,Y,J),K=Q.localName,V=Q.namespaceURI,W=!V||V===mZ;if(W&&LH.includes(K))EH(K,Q);let H=TH(Q),z=[];for(let q=Q.firstChild;q;){let I=uZ(X,Q,q,z.length);if(I)z.push([I.key,I.vnode]),q=I.next;else q=q.nextSibling}let U=W?hK(K,H,g9(z)):vK(V,K,H,g9(z));return _9(J,U,Q.nextSibling)},AH=(Z,Q,Y)=>{return R8(d6,Z,Q,Y,null),_9("",AZ(Q.data),Q.nextSibling)},jH=(Z,Q,Y,J)=>{let X=R7(Y.data),K=R8(p6,Z,Y,J,X),V=[];Y=Y.nextSibling;while(Y&&(Y.nodeType!==z7||Y.data.trim()!=="/lustre:fragment")){let H=uZ(K,Q,Y,V.length);if(H)V.push([H.key,H.vnode]),Y=H.next;else Y=Y.nextSibling}K.endNode=Y;let W=$K(g9(V));return _9(X,W,Y?.nextSibling)},PH=(Z,Q,Y,J)=>{let X=R7(Y.data),K=R8(K6,Z,Y,J,X),V=uK(K,Q,Y,0);if(!V)return null;let W=jZ(V.vnode,(H)=>H);return _9(X,W,V.next)},SH=(Z,Q,Y,J)=>{let X=R7(Y.data),K=uK(Z,Q,Y,J);if(!K)return null;Q.removeChild(Y);let V=E9(g9([C9({})]),()=>K.vnode);return _9(X,V,K.next)},uK=(Z,Q,Y,J)=>{while(!0){if(Y=Y.nextSibling,!Y)return null;let X=uZ(Z,Q,Y,J);if(X)return X}},_9=(Z,Q,Y)=>{return{key:Z,vnode:Q,next:Y}},TH=(Z)=>{let Q=[];for(let Y=0;Y{let{value:Y,checked:J}=Q;if(Z==="input"&&Q.type==="checkbox"&&!J)return;if(Z==="input"&&Q.type==="radio"&&!J)return;if(Q.type!=="checkbox"&&Q.type!=="radio"&&!Y)return;queueMicrotask(()=>{if(Q.value=Y,Q.checked=J,Q.dispatchEvent(new Event("input",{bubbles:!0})),Q.dispatchEvent(new Event("change",{bubbles:!0})),globalThis.document.activeElement!==Q)Q.dispatchEvent(new Event("blur",{bubbles:!0}))})},R7=(Z)=>{let Q=Z.match(/key="([^"]*)"/);if(!Q)return"";return CH(Q[1])},CH=(Z)=>{return Z.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/&/g,"&").replace(/'/g,"'")},g9=(Z)=>Z.reduceRight((Q,Y)=>C6(Y,Q),y);var W0=()=>!!globalThis.document;class h9{constructor(Z,[Q,Y],J,X,K){this.root=Z,this.#Z=Q,this.#K=J,this.#J=X,this.root.addEventListener("context-request",(H)=>{if(!(H.context&&H.callback))return;if(!this.#V.has(H.context))return;H.stopImmediatePropagation();let z=this.#V.get(H.context);if(H.subscribe){let U=()=>{z.subscribers=z.subscribers.filter((q)=>q!==H.callback)};z.subscribers.push([H.callback,U]),H.callback(z.value,U)}else H.callback(z.value)});let V=(H,z,U)=>eY(this.#Q,z,U,H),W=(H,z)=>{let[U,q]=Z7(this.#Q,z);if(this.#Q=U,C0(q)){let I=y0(q);if(I.stop_propagation)H.stopPropagation();if(I.prevent_default)H.preventDefault();this.dispatch(I.message,!1)}};this.#X=new I7(this.root,V,W,K),this.#Y=mK(this.root),this.#Q=oY(),this.#R(Y),this.#q()}root=null;dispatch(Z,Q=!1){if(this.#W)this.#F.push(Z);else{let[Y,J]=this.#J(this.#Z,Z);this.#Z=Y,this.#I(J,Q)}}emit(Z,Q){(this.root.host??this.root).dispatchEvent(new CustomEvent(Z,{detail:Q,bubbles:!0,composed:!0}))}provide(Z,Q){if(!this.#V.has(Z))this.#V.set(Z,{value:Q,subscribers:[]});else{let Y=this.#V.get(Z);if(N8(Y.value,Q))return;Y.value=Q;for(let J=Y.subscribers.length-1;J>=0;J--){let[X,K]=Y.subscribers[J];if(!X){Y.subscribers.splice(J,1);continue}X(Q,K)}}}#Z;#K;#J;#Y;#Q;#X;#V=new Map;#W=!1;#F=[];#G=y;#H=y;#z=null;#U={dispatch:(Z)=>this.dispatch(Z),emit:(Z,Q)=>this.emit(Z,Q),select:()=>{},root:()=>this.root,provide:(Z,Q)=>this.provide(Z,Q)};#I(Z,Q=!1){if(this.#R(Z),!this.#z)if(Q)this.#z="sync",queueMicrotask(()=>this.#q());else this.#z=window.requestAnimationFrame(()=>this.#q())}#R(Z){this.#W=!0;while(!0){if(d0(Z.synchronous,(Y)=>Y(this.#U)),this.#G=F7(this.#G,Z.before_paint),this.#H=F7(this.#H,Z.after_paint),!this.#F.length)break;let Q=this.#F.shift();[this.#Z,Z]=this.#J(this.#Z,Q)}this.#W=!1}#q(){this.#z=null;let Z=this.#K(this.#Z),{patch:Q,cache:Y}=f9(this.#Q,this.#Y,Z);if(this.#Q=Y,this.#Y=Z,this.#X.push(Q,s6(Y)),f8(this.#G)){let J=cK(this.#G);this.#G=y,queueMicrotask(()=>{this.#I(J,!0)})}if(f8(this.#H)){let J=cK(this.#H);this.#H=y,window.requestAnimationFrame(()=>this.#I(J,!0))}}}function cK(Z){return{synchronous:Z,after_paint:y,before_paint:y}}var pK=new WeakMap;async function dK(Z){let Q=[];for(let J of globalThis.document.querySelectorAll("link[rel=stylesheet], style")){if(J.sheet)continue;Q.push(new Promise((X,K)=>{J.addEventListener("load",X),J.addEventListener("error",K)}))}if(await Promise.allSettled(Q),!Z.host.isConnected)return[];Z.adoptedStyleSheets=Z.host.getRootNode().adoptedStyleSheets;let Y=[];for(let J of globalThis.document.styleSheets)try{Z.adoptedStyleSheets.push(J)}catch{try{let X=pK.get(J);if(!X){X=new CSSStyleSheet;for(let K of J.cssRules)X.insertRule(K.cssText,X.cssRules.length);pK.set(J,X)}Z.adoptedStyleSheets.push(X)}catch{let X=J.ownerNode.cloneNode();Z.prepend(X),Y.push(X)}}return Y}class B7 extends Event{constructor(Z,Q,Y){super("context-request",{bubbles:!0,composed:!0});this.context=Z,this.callback=Q,this.subscribe=Y}}var D7=({init:Z,update:Q,view:Y,config:J},X)=>{if(!W0())return h0(M7());if(!X.includes("-"))return h0(rK(X));if(globalThis.customElements.get(X))return h0(sK(X));let K=new Map,V=[];d0(J.attributes,([U,q])=>{if(K.has(U))return;K.set(U,q),V.push(U)});let[W,H]=Z(void 0),z=class extends globalThis.HTMLElement{static get observedAttributes(){return V}static formAssociated=J.is_form_associated;#Z;#K=[];#J=new Map;constructor(){super();if(this.internals=this.attachInternals(),!this.internals.shadowRoot)this.attachShadow({mode:J.open_shadow_root?"open":"closed",delegatesFocus:J.delegates_focus});if(J.adopt_styles)this.#X();this.#Z=new h9(this.internals.shadowRoot,[W,H],Y,Q)}connectedCallback(){if(this.#Y(),j8(J.on_connect))this.dispatch(P8(J.on_connect))}adoptedCallback(){if(J.adopt_styles)this.#X();if(this.#Q(),j8(J.on_adopt))this.dispatch(P8(J.on_adopt))}disconnectedCallback(){if(this.#Q(),j8(J.on_disconnect))this.dispatch(P8(J.on_disconnect))}attributeChangedCallback(q,I,N){let R=K.get(q)(N??"");if(C0(R))this.dispatch(y0(R),!0)}formResetCallback(){if(j8(J.on_form_reset))this.dispatch(P8(J.on_form_reset))}formStateRestoreCallback(q,I){switch(I){case"restore":if(j8(J.on_form_restore))this.dispatch(P8(J.on_form_restore)(q));break;case"autocomplete":if(j8(J.on_form_autofill))this.dispatch(P8(J.on_form_autofill)(q));break}}send(q){if(i6(q))this.dispatch(q.message,!1);else if(n6(q))this.emit(q.name,q.data);else if(a6(q));}dispatch(q,I=!1){this.#Z.dispatch(q,I)}emit(q,I){this.#Z.emit(q,I)}provide(q,I){this.#Z.provide(q,I)}#Y(){let q=new Set;d0(J.contexts,([I,N])=>{if(!I)return;if(q.has(I))return;this.dispatchEvent(new B7(I,(R,O)=>{let D=this.#J.get(I);if(D!==O)D?.();let T=K8(R,N);if(this.#J.set(I,O),C0(T))this.dispatch(y0(T),!0)},!0)),q.add(I)})}#Q(){for(let[q,I]of this.#J)I?.();this.#J.clear()}async#X(){while(this.#K.length)this.#K.pop().remove(),this.shadowRoot.firstChild.remove();this.#K=await dK(this.internals.shadowRoot)}};return d0(J.properties,([U,q])=>{if(Object.hasOwn(z.prototype,U))return;Object.defineProperty(z.prototype,U,{get(){return this[`_${U}`]},set(I){this[`_${U}`]=I;let N=K8(I,q);if(C0(N))this.dispatch(y0(N),!0)}})}),globalThis.customElements.define(X,z),E0(void 0)};function lK(Z,Q){return new v0((Y)=>{let J=B([Z,Q],Y.attributes);return new _0(Y.open_shadow_root,Y.adopt_styles,Y.delegates_focus,J,Y.properties,Y.contexts,Y.is_form_associated,Y.on_form_autofill,Y.on_form_reset,Y.on_form_restore,Y.on_connect,Y.on_adopt,Y.on_disconnect)})}function iK(Z,Q){return new v0((Y)=>{let J=B([Z,Q],Y.properties);return new _0(Y.open_shadow_root,Y.adopt_styles,Y.delegates_focus,Y.attributes,J,Y.contexts,Y.is_form_associated,Y.on_form_autofill,Y.on_form_reset,Y.on_form_restore,Y.on_connect,Y.on_adopt,Y.on_disconnect)})}function nK(Z,Q){return new v0((Y)=>{let J=B([Z,Q],Y.contexts);return new _0(Y.open_shadow_root,Y.adopt_styles,Y.delegates_focus,Y.attributes,Y.properties,J,Y.is_form_associated,Y.on_form_autofill,Y.on_form_reset,Y.on_form_restore,Y.on_connect,Y.on_adopt,Y.on_disconnect)})}function aK(){return new v0((Z)=>{return new _0(Z.open_shadow_root,Z.adopt_styles,Z.delegates_focus,Z.attributes,Z.properties,Z.contexts,!0,Z.on_form_autofill,Z.on_form_reset,Z.on_form_restore,Z.on_connect,Z.on_adopt,Z.on_disconnect)})}function oK(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,!0,new k(Z),Q.on_form_reset,Q.on_form_restore,Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function tK(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,!0,Q.on_form_autofill,new k(Z),Q.on_form_restore,Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function eK(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,!0,Q.on_form_autofill,Q.on_form_reset,new k(Z),Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function ZV(Z){return new v0((Q)=>{return new _0(Z,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function QV(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Z,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function YV(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Z,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,Q.on_connect,Q.on_adopt,Q.on_disconnect)})}function JV(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,new k(Z),Q.on_adopt,Q.on_disconnect)})}function XV(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,Q.on_connect,new k(Z),Q.on_disconnect)})}function KV(Z){return new v0((Q)=>{return new _0(Q.open_shadow_root,Q.adopt_styles,Q.delegates_focus,Q.attributes,Q.properties,Q.contexts,Q.is_form_associated,Q.on_form_autofill,Q.on_form_reset,Q.on_form_restore,Q.on_connect,Q.on_adopt,new k(Z))})}class wH{#Z;constructor(Z,[Q,Y],J,X){this.#Z=new h9(Z,[Q,Y],X,J)}send(Z){if(i6(Z))this.dispatch(Z.message,!1);else if(n6(Z))this.emit(Z.name,Z.data);else if(a6(Z));}dispatch(Z){this.#Z.dispatch(Z)}emit(Z,Q){this.#Z.emit(Z,Q)}}class xH{#Z;#K;#J;#Y;#Q;#X;#V=k6();#W=new Set;constructor(Z,Q,Y,J,X,K){let[V,W]=Q(K);this.#Z=V,this.#K=Y,this.#J=J,this.#Y=X,this.#Q=this.#J(this.#Z),this.#X=ZK(this.#Q),this.#U(W)}send(Z){if(zK(Z)){let{message:Q}=Z,Y=this.#F(Q),J=f9(this.#X,this.#Q,Y);this.#Q=Y,this.#X=J.cache,this.broadcast(mY(J.patch,s6(J.cache)))}else if(UK(Z)){let{callback:Q}=Z;if(this.#W.add(Q),Q(uX(this.#Y.open_shadow_root,this.#Y.adopt_styles,F9(this.#Y.attributes),F9(this.#Y.properties),F9(this.#Y.contexts),this.#V,this.#Q,s6(this.#X))),j8(config.on_connect))this.#G(P8(config.on_connect))}else if(qK(Z)){let{callback:Q}=Z;if(this.#W.delete(Q),j8(config.on_disconnect))this.#G(P8(config.on_disconnect))}else if(i6(Z)){let{message:Q}=Z,[Y,J]=this.#K(this.#Z,Q),X=this.#J(Y),K=f9(this.#X,this.#Q,X);this.#U(J),this.#Z=Y,this.#Q=X,this.#X=K.cache,this.broadcast(mY(K.patch,s6(K.cache)))}else if(n6(Z)){let{name:Q,data:Y}=Z;this.broadcast(cX(Q,Y))}else if(BK(Z)){let{key:Q,value:Y}=Z,J=g8(this.#V,Q);if(C0(J)&&N8(y0(J),Y))return;this.#V=y6(this.#V,Q,Y),this.broadcast(pX(Q,Y))}else if(a6(Z))this.#Z=null,this.#K=null,this.#J=null,this.#Y=null,this.#Q=null,this.#X=null,this.#V=null,this.#W.clear()}broadcast(Z){for(let Q of this.#W)Q(Z)}#F(Z){if(yX(Z)){let{messages:Q}=Z,Y=this.#Z,J=j();for(let X=Q;l0(X);X=l0(X)){let K=this.#F(i8(X));if(C0(K)){Y=y0(K)[0],J=U8(SK([J,y0(K)[1]]));break}}return this.#U(J),this.#Z=Y,this.#J(Y)}else if(fX(Z)){let{name:Q,value:Y}=Z,J=this.#H(Q,Y);if(!C0(J))return this.#Q;return this.#G(y0(J))}else if(_X(Z)){let{name:Q,value:Y}=Z,J=this.#z(Q,Y);if(!C0(J))return this.#Q;return this.#G(y0(J))}else if(vX(Z)){let{path:Q,name:Y,event:J}=Z,[X,K]=FK(this.#X,Q,Y,J);if(this.#X=X,!C0(K))return this.#Q;let{message:V}=y0(K);return this.#G(V)}else if(mX(Z)){let{key:Q,value:Y}=Z,J=g8(this.#Y.contexts,Q);if(!C0(J))return this.#Q;if(J=K8(Y,y0(J)),!C0(J))return this.#Q;return this.#G(y0(J))}}#G(Z){let[Q,Y]=this.#K(this.#Z,Z);return this.#U(Y),this.#Z=Q,this.#J(this.#Z)}#H(Z,Q){let Y=g8(this.#Y.attributes,Z);if(!C0(Y))return Y;return y0(Y)(Q)}#z(Z,Q){let Y=g8(this.#Y.properties,Z);if(!C0(Y))return Y;return y0(Y)(Q)}#U(Z){let Q=(V)=>this.send(IK(V)),Y=(V,W)=>this.send(NK(V,W)),J=()=>{return},X=()=>{return},K=(V,W)=>this.send(RK(V,W));globalThis.queueMicrotask(()=>{JX(Z,Q,Y,J,X,K)})}}class VV extends F{constructor(Z){super();this.name=Z}}var rK=(Z)=>new VV(Z);class GV extends F{constructor(Z){super();this.name=Z}}var sK=(Z)=>new GV(Z);class WV extends F{}var M7=()=>new WV;function HV(Z,Q,Y,J){return new W7(new m,Z,Q,Y,MK(J))}function FV(Z,Q){if(Z instanceof k0)return new k0(Z.kind,Z.name,Z.handler,Q,Z.prevent_default,Z.stop_propagation,Z.debounce,Z.throttle);else return Z}class dZ extends F{constructor(Z,Q){super();this.name=Z,this.decoder=Q}}class rZ extends F{constructor(Z,Q){super();this.name=Z,this.decoder=Q}}class sZ extends F{constructor(Z,Q){super();this.key=Z,this.decoder=Q}}class O7 extends F{}class lZ extends F{constructor(Z){super();this.handler=Z}}class iZ extends F{constructor(Z){super();this.message=Z}}class nZ extends F{constructor(Z){super();this.handler=Z}}class A7 extends F{constructor(Z){super();this.open=Z}}class j7 extends F{constructor(Z){super();this.adopt=Z}}class P7 extends F{constructor(Z){super();this.delegates=Z}}class o6 extends F{constructor(Z){super();this.message=Z}}class aZ extends F{constructor(Z){super();this.message=Z}}class oZ extends F{constructor(Z){super();this.message=Z}}function zV(Z,Q){if(Z instanceof dZ){let{name:Y,decoder:J}=Z;return new dZ(Y,(X)=>{return M9(J(X),Q)})}else if(Z instanceof rZ){let{name:Y,decoder:J}=Z;return new rZ(Y,z0(J,Q))}else if(Z instanceof sZ){let{key:Y,decoder:J}=Z;return new sZ(Y,z0(J,Q))}else if(Z instanceof O7)return Z;else if(Z instanceof lZ){let Y=Z.handler;return new lZ((J)=>{return Q(Y(J))})}else if(Z instanceof iZ){let Y=Z.message;return new iZ(Q(Y))}else if(Z instanceof nZ){let Y=Z.handler;return new nZ((J)=>{return Q(Y(J))})}else if(Z instanceof A7)return Z;else if(Z instanceof j7)return Z;else if(Z instanceof P7)return Z;else if(Z instanceof o6){let Y=Z.message;return new o6(Q(Y))}else if(Z instanceof aZ){let Y=Z.message;return new aZ(Q(Y))}else{let Y=Z.message;return new oZ(Q(Y))}}function fH(Z){if(Z instanceof dZ){let{name:Q,decoder:Y}=Z;return lK(Q,Y)}else if(Z instanceof rZ){let{name:Q,decoder:Y}=Z;return iK(Q,Y)}else if(Z instanceof sZ){let{key:Q,decoder:Y}=Z;return nK(Q,Y)}else if(Z instanceof O7)return aK();else if(Z instanceof lZ){let Q=Z.handler;return oK(Q)}else if(Z instanceof iZ){let Q=Z.message;return tK(Q)}else if(Z instanceof nZ){let Q=Z.handler;return eK(Q)}else if(Z instanceof A7){let Q=Z.open;return ZV(Q)}else if(Z instanceof j7){let Q=Z.adopt;return QV(Q)}else if(Z instanceof P7){let Q=Z.delegates;return YV(Q)}else if(Z instanceof o6){let Q=Z.message;return JV(Q)}else if(Z instanceof aZ){let Q=Z.message;return XV(Q)}else{let Q=Z.message;return KV(Q)}}function UV(Z){return T0(Z,fH)}function qV(Z){return x0(Z,[new m,new m],(Q,Y)=>{let J,X;if(J=Q[0],X=Q[1],Y instanceof o6){let K=Y.message;return[new k(K),X]}else if(Y instanceof oZ){let K=Y.message;return[J,new k(K)]}else return Q})}function tZ(Z){return new o6(Z)}function IV(Z){return new oZ(Z)}function RV(Z,Q){if(Array.isArray(Z))for(let Y=0;YX.removeEventListener(Q,Y)}function k7(Z){let Q=()=>{if(document.visibilityState==="hidden")return"hidden";if(document.hasFocus())return"active";return"passive"},Y=(V)=>{if(V!==J)Z(V),J=V},J=Q();if(J!=="active")Z(J);let X=new AbortController,K={capture:!0,signal:X.signal};return["pageshow","focus","blur","visibilitychange","resume"].forEach((V)=>{window.addEventListener(V,()=>Y(Q()),K)}),window.addEventListener("freeze",()=>{Y("frozen")},K),window.addEventListener("pagehide",(V)=>{Y(V.persisted?"frozen":"terminated")},K),()=>X.abort()}function y7(Z){let Q=()=>-new Date().getTimezoneOffset(),Y=Q();Z(Y);let J=setInterval(()=>{let X=Q();if(X!==Y)Y=X,Z(X)},60000);return()=>clearInterval(J)}var b7=DV(window,"online","offline",()=>navigator.onLine),f7=DV(document.documentElement,"pointerenter","pointerleave",()=>document.documentElement.matches(":hover"));function g7(Z){let Q=(Y)=>{Y.preventDefault(),Z()};return window.addEventListener("beforeunload",Q),()=>window.removeEventListener("beforeunload",Q)}function _7(Z,Q){let Y=setInterval(Q,Z);return()=>clearInterval(Y)}function h7(Z,Q){let Y=!1,J=null,X=()=>{if(Y)Y=!1,Q(!1);clearTimeout(J),J=setTimeout(()=>{Y=!0,Q(!0)},Z)},K=()=>{if(!document.hidden)X()},V=["pointermove","pointerdown","keydown","scroll","wheel","resize"];for(let W of V)window.addEventListener(W,X,{passive:!0});return document.addEventListener("visibilitychange",K),X(),()=>{clearTimeout(J);for(let W of V)window.removeEventListener(W,X);document.removeEventListener("visibilitychange",K)}}function v7(Z){return ZQ(null,window,"resize",()=>Z(window.innerWidth,window.innerHeight))}function $7(Z){return ZQ(null,window,"scroll",()=>Z(window.scrollX,window.scrollY))}function m7(Z){return ZQ(null,screen.orientation,"change",()=>Z(screen.orientation.type))}function u7(Z){return ZQ(null,window,"languagechange",()=>Z(navigator.language))}function c7(Z,Q,Y){let J=[];RV(Q,([V,W])=>{J.push([window.matchMedia(V),W])});let X=()=>J.find(([V])=>V.matches)?.[1]??Z,K=()=>Y(X());K();for(let[V]of J)V.addEventListener("change",K);return()=>{for(let[V]of J)V.removeEventListener("change",K)}}var p7=BV(localStorage),d7=BV(sessionStorage);function BV(Z){return(Q,Y)=>{let J=Z.getItem(Q);return Y(J!==null,J??""),v9(window,"storage",(K)=>{if(K.storageArea===Z&&K.key===Q){let V=K.newValue;Y(V!==null,V??"")}})}}function r7(Z,Q,Y){let J=x8(Z,Y);if(!J)return QQ;let{width:X,height:K}=J.getBoundingClientRect();Q(Math.round(X),Math.round(K));let V=new ResizeObserver((W)=>{let{width:H,height:z}=W[0].contentRect;Q(Math.round(H),Math.round(z))});return V.observe(J),()=>V.disconnect()}function s7(Z,Q,Y){let J=x8(Z,Y);if(!J)return QQ;let X=J.getBoundingClientRect();Q(X.bottom>0&&X.right>0&&X.top{Q(V[0].isIntersecting)});return K.observe(J),()=>K.disconnect()}function l7(Z,Q,Y){let J=x8(Z,Y);if(!J)return QQ;let X=()=>Q(Math.round(J.scrollLeft),Math.round(J.scrollTop));return X(),J.addEventListener("scroll",X,{passive:!0}),()=>J.removeEventListener("scroll",X)}function ZQ(Z,Q,Y,J){let X=x8(Q,Z);return J(),v9(X,Y,J)}function DV(Z,Q,Y,J){return(X)=>{X(J());let K=()=>X(!0),V=()=>X(!1);return Z.addEventListener(Q,K),Z.addEventListener(Y,V),()=>{Z.removeEventListener(Q,K),Z.removeEventListener(Y,V)}}}function QQ(){}function MV(Z,Q,Y){setTimeout(Y,Z,Q)}class WQ extends F{constructor(Z,Q,Y,J){super();this.dependencies=Z,this.start=Q,this.throttle=Y,this.delay=J}}class HQ extends F{constructor(Z,Q,Y,J){super();this.dependencies=Z,this.start=Q,this.throttle=Y,this.delay=J}}class Z9 extends F{constructor(Z,Q,Y,J,X,K,V){super();this.name=Z,this.params=Q,this.dependencies=Y,this.decoder=J,this.include=X,this.throttle=K,this.delay=V}}class p9 extends F{constructor(Z){super();this.subscriptions=Z}}class t0 extends F{constructor(Z){super();this.model=Z}}class B8 extends F{constructor(Z,Q,Y){super();this.model=Z,this.tick=Q,this.state=Y}}class e6 extends F{constructor(Z,Q,Y,J){super();this.model=Z,this.tick=Q,this.state=Y,this.clients=J}}class a7 extends F{}class D8 extends F{constructor(Z){super();this.message=Z}}class KQ extends F{constructor(Z,Q,Y){super();this.tick=Z,this.path=Q,this.cleanup=Y}}class o7 extends F{constructor(Z,Q,Y){super();this.tick=Z,this.path=Q,this.message=Y}}class t7 extends F{constructor(Z,Q,Y,J){super();this.tick=Z,this.path=Q,this.message=Y,this.ts=J}}class i7 extends F{constructor(Z,Q,Y,J){super();this.tick=Z,this.path=Q,this.delay_tick=Y,this.message=J}}class e7 extends F{constructor(Z){super();this.forward=Z}}class EV extends F{constructor(Z){super();this.forward=Z}}class CV extends F{constructor(Z,Q,Y,J,X,K){super();this.key=Z,this.code=Q,this.ctrl=Y,this.alt=J,this.meta=X,this.shift=K}}class d9 extends F{}class FQ extends F{}class Z5 extends F{}class wV extends F{constructor(Z,Q,Y,J,X,K,V,W,H,z,U,q,I){super();this.x=Z,this.y=Q,this.button=Y,this.pointer_type=J,this.pointer_id=X,this.is_primary=K,this.pressure=V,this.tilt_x=W,this.tilt_y=H,this.ctrl=z,this.alt=U,this.meta=q,this.shift=I}}class xV extends F{}class kV extends F{}class yV extends F{}class bV extends F{constructor(Z,Q,Y,J,X,K,V,W){super();this.delta_x=Z,this.delta_y=Q,this.delta_z=Y,this.delta_mode=J,this.ctrl=X,this.alt=K,this.meta=V,this.shift=W}}class r9 extends F{}class zQ extends F{}class UQ extends F{}class qQ extends F{}class s8 extends F{}class IQ extends F{}class s9 extends F{}class l9 extends F{}class NQ extends F{}class RQ extends F{}class Q5 extends F{}class BQ extends F{}class i9 extends F{}class DQ extends F{}class fV extends F{}class gV extends F{}class MQ extends F{}class y8 extends F{}class OQ extends F{}class AQ extends F{}class jQ extends F{}class PQ extends F{}class SQ extends F{}class TQ extends F{}class LQ extends F{}class EQ extends F{}class n9 extends F{}class CQ extends F{}class wQ extends F{}class xQ extends F{}class kQ extends F{}class Y5 extends F{}class k8 extends F{constructor(Z,Q,Y,J,X){super();this.tick=Z,this.element=Q,this.dependencies=Y,this.throttle=J,this.delay=X}}class H8 extends F{constructor(Z,Q,Y,J,X,K,V,W){super();this.tick=Z,this.element=Q,this.dependencies=Y,this.cleanup=J,this.throttle=X,this.delay=K,this.last_fired=V,this.delay_tick=W}}class Q9 extends F{constructor(Z,Q,Y,J,X,K,V,W){super();this.tick=Z,this.name=Q,this.params=Y,this.dependencies=J,this.decoder=X,this.include=K,this.throttle=V,this.delay=W}}class e0 extends F{constructor(Z){super();this.group=Z}}class u9 extends F{constructor(Z,Q){super();this.state=Z,this.message=Q}}class c9 extends F{constructor(Z,Q,Y,J){super();this.state=Z,this.message=Q,this.delay=Y,this.delay_tick=J}}class $9 extends F{}var J5=G(["key","code","ctrlKey","altKey","metaKey","shiftKey"]),a9=G(["clientX","clientY","button","pointerType","pointerId","isPrimary","pressure","tiltX","tiltY","ctrlKey","altKey","metaKey","shiftKey"]),sH=G(["deltaX","deltaY","deltaZ","deltaMode","ctrlKey","altKey","metaKey","shiftKey"]);function YQ(Z,Q){if(Z instanceof t0)return Z;else if(Z instanceof B8)return new B8(Z.model,Z.tick,Q);else return new e6(Z.model,Z.tick,Q,Z.clients)}function OV(Z,Q,Y,J){if(Z instanceof t0)return new t0(Q);else if(Z instanceof B8)return new B8(Q,Y,J);else{let X=Z.clients;return new e6(Q,Y,J,X)}}function _V(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(X instanceof A)return W;else{let{head:H,tail:z}=X,U=X5(H,B(K,V),W);Z=z,Q=K+1,Y=V,J=U}}}function X5(Z,Q,Y){if(Z instanceof k8)return Y;else if(Z instanceof H8){let J=Z.cleanup;return B(X6((X)=>{return J()}),Y)}else if(Z instanceof Q9){let{tick:J,name:X}=Z,K=a(Q),V=v6(G([["tick",c0(J)],["name",X0(X)],["path",M6(a(K),c0)]]));return B(PZ("off-topic-unsubscribe",V),Y)}else{let J=Z.group;return _V(J,0,Q,Y)}}function AV(Z){if(Z instanceof k){let Q=Z[0];return R6(Q)}else return 0}function hV(Z,Q,Y,J,X,K,V){let W=v6(G([["tick",c0(Z)],["name",X0(Y)],["path",M6(Q,c0)],["params",O9(J)],["include",M6(X,X0)],["throttle",c0(AV(K))],["delay",c0(AV(V))]]));return PZ("off-topic:subscribe",W)}function jV(Z,Q,Y,J,X){if(Q instanceof m&&Y instanceof m)return(K)=>{return Z(new D8(K))};else return(K)=>{let V=k9();return Z(new t7(J,X,K,V))}}function vV(Z,Q,Y,J,X,K){while(!0){let V=Z,W=Q,H=Y,z=J,U=X,q=K;if(z instanceof A)return[new e0(a(U)),q];else{let{head:I,tail:N}=z,R=K5(V,B(W,H),I,q),O,D;O=R[0],D=R[1],Z=V,Q=W+1,Y=H,J=N,X=B(O,U),K=D}}}function K5(Z,Q,Y,J){if(Y instanceof WQ){let{dependencies:X,start:K,throttle:V,delay:W}=Y,H=a(Q),z=X6((q)=>{let I=K(jV(q,V,W,Z,H));return q(new KQ(Z,H,I))});return[new k8(Z,!1,X,V,W),B(z,J)]}else if(Y instanceof HQ){let{dependencies:X,start:K,throttle:V,delay:W}=Y,H=a(Q),z=A6((q,I)=>{let N=K(jV(q,V,W,Z,H),I);return q(new KQ(Z,H,N))});return[new k8(Z,!0,X,V,W),B(z,J)]}else if(Y instanceof Z9){let{name:X,params:K,dependencies:V,decoder:W,include:H,throttle:z,delay:U}=Y,q=a(Q),I=hV(Z,q,X,K,H,z,U);return[new Q9(Z,X,K,V,W,H,z,U),B(I,J)]}else{let X=Y.subscriptions;return vV(Z,0,Q,X,G([]),J)}}function o0(Z,Q,Y,J,X){return K5(Z,Q,J,X5(Y,Q,X))}function lH(Z,Q,Y,J,X,K,V){while(!0){let W=Z,H=Q,z=Y,U=J,q=X,I=K,N=V;if(U instanceof A)if(q instanceof A)return[new e0(a(I)),N];else return vV(W,H,z,q,I,N);else if(q instanceof A)return[new e0(a(I)),_V(U,H,z,N)];else{let{head:R,tail:O}=U,D=q.head,T=q.tail,C=n7(W,B(H,z),R,D,N),P,L;P=C[0],L=C[1],Z=W,Q=H+1,Y=z,J=O,X=T,K=B(P,I),V=L}}}function n7(Z,Q,Y,J,X){if(J instanceof WQ)if(Y instanceof k8)if(!Y.element){let{dependencies:V,throttle:W,delay:H}=J,z=Y.dependencies,U=Y.throttle,q=Y.delay;if(J0(z,V)&&J0(U,W)&&J0(q,H))return[Y,X];else{let I=J.dependencies,N=Y.dependencies;if(J0(N,I)){let{throttle:R,delay:O}=J;return[new k8(Y.tick,Y.element,Y.dependencies,R,O),X]}else return o0(Z,Q,Y,J,X)}}else return o0(Z,Q,Y,J,X);else if(Y instanceof H8)if(!Y.element){let{dependencies:V,throttle:W,delay:H}=J,z=Y.dependencies,U=Y.throttle,q=Y.delay;if(J0(z,V)&&J0(U,W)&&J0(q,H))return[Y,X];else{let I=J.dependencies,N=Y.dependencies;if(J0(N,I)){let{throttle:R,delay:O}=J;return[new H8(Y.tick,Y.element,Y.dependencies,Y.cleanup,R,O,Y.last_fired,Y.delay_tick),X]}else return o0(Z,Q,Y,J,X)}}else return o0(Z,Q,Y,J,X);else return o0(Z,Q,Y,J,X);else if(J instanceof HQ)if(Y instanceof k8)if(Y.element){let{dependencies:V,throttle:W,delay:H}=J,z=Y.dependencies,U=Y.throttle,q=Y.delay;if(J0(z,V)&&J0(U,W)&&J0(q,H))return[Y,X];else{let I=J.dependencies,N=Y.dependencies;if(J0(N,I)){let{throttle:R,delay:O}=J;return[new k8(Y.tick,Y.element,Y.dependencies,R,O),X]}else return o0(Z,Q,Y,J,X)}}else return o0(Z,Q,Y,J,X);else if(Y instanceof H8)if(Y.element){let{dependencies:V,throttle:W,delay:H}=J,z=Y.dependencies,U=Y.throttle,q=Y.delay;if(J0(z,V)&&J0(U,W)&&J0(q,H))return[Y,X];else{let I=J.dependencies,N=Y.dependencies;if(J0(N,I)){let{throttle:R,delay:O}=J;return[new H8(Y.tick,Y.element,Y.dependencies,Y.cleanup,R,O,Y.last_fired,Y.delay_tick),X]}else return o0(Z,Q,Y,J,X)}}else return o0(Z,Q,Y,J,X);else return o0(Z,Q,Y,J,X);else if(J instanceof Z9)if(Y instanceof Q9){let{name:K,params:V,dependencies:W}=J,H=Y.name,z=Y.params,U=Y.dependencies;if(H===K&&J0(z,V)&&J0(U,W))return[Y,X];else return o0(Z,Q,Y,J,X)}else return o0(Z,Q,Y,J,X);else if(Y instanceof e0){let K=J.subscriptions,V=Y.group;return lH(Z,0,Q,V,K,G([]),X)}else return o0(Z,Q,Y,J,X)}function m9(Z,Q,Y,J){let X=J(Z.model,Q),K,V;if(K=X[0],V=X[1],Z instanceof t0)return[new t0(K),j6(V,(W)=>{return new D8(W)})];else if(Z instanceof B8){let{tick:W,state:H}=Z,z=W+1,U=n7(z,G([]),H,Y(K),G([])),q,I;q=U[0],I=U[1];let N=U8(B(j6(V,(R)=>{return new D8(R)}),I));return[OV(Z,K,z,q),N]}else{let{tick:W,state:H}=Z,z=W+1,U=n7(z,G([]),H,Y(K),G([])),q,I;q=U[0],I=U[1];let N=U8(B(j6(V,(R)=>{return new D8(R)}),I));return[OV(Z,K,z,q),N]}}function VQ(Z,Q,Y){return x0(Z,B(Q,Y),f6)}function V5(Z,Q,Y){while(!0){let J=Z,X=Q,K=Y;if(J instanceof A)return new Y0(void 0);else if(X>0){let V=J.head;Z=J.tail,Q=X-1,Y=B(V,K)}else{let{head:V,tail:W}=J;return new w([K,V,W])}}}function $V(Z,Q,Y,J,X){if(Y instanceof A)if(Z instanceof H8&&Z.tick===Q&&Z.delay_tick===J)return new w([Z,X]);else return new Y0(void 0);else if(Z instanceof e0){let{head:K,tail:V}=Y,W=Z.group;return Z6(V5(W,K,G([])),(H)=>{let z,U,q;return z=H[0],U=H[1],q=H[2],Z6($V(U,Q,V,J,X),(I)=>{let N,R;return N=I[0],R=I[1],new w([new e0(VQ(z,N,q)),R])})})}else return new Y0(void 0)}function JQ(Z){if(Z instanceof t0)return new Y0(void 0);else if(Z instanceof B8){let Q=Z.state;return new w(Q)}else{let Q=Z.state;return new w(Q)}}function G5(Z,Q){return X6((Y)=>{return MV(R6(Z),Q,Y)})}function mV(Z,Q,Y,J,X){if(J instanceof A)if(Q instanceof H8&&Q.tick===Y){let{throttle:K,delay:V,last_fired:W,delay_tick:H}=Q,z;if(K instanceof k&&W instanceof k){let I=K[0],N=W[0],R=qX(Z,N);if(yJ(R,I)instanceof M0)z=!1;else z=!0}else z=!0;let U=z,q=H+1;if(U){let I=new H8(Q.tick,Q.element,Q.dependencies,Q.cleanup,Q.throttle,Q.delay,new k(Z),q);return new u9(I,X)}else if(V instanceof k){let I=V[0];return new c9(new H8(Q.tick,Q.element,Q.dependencies,Q.cleanup,Q.throttle,Q.delay,Q.last_fired,q),X,I,q)}else return new $9}else return new $9;else if(Q instanceof e0){let{head:K,tail:V}=J,W=Q.group,H=V5(W,K,G([]));if(H instanceof w){let z=H[0][0],U=H[0][1],q=H[0][2],I=mV(Z,U,Y,V,X);if(I instanceof u9){let{state:N,message:R}=I;return new u9(new e0(VQ(z,N,q)),R)}else if(I instanceof c9){let{state:N,message:R,delay:O,delay_tick:D}=I;return new c9(new e0(VQ(z,N,q)),R,O,D)}else return I}else return new $9}else return new $9}function iH(Z,Q){while(!0){let Y=Z,J=Q;if(Y instanceof A)return new Y0(void 0);else if(J<=0){let X=Y.head;return new w(X)}else Z=Y.tail,Q=J-1}}function nH(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(V instanceof A)if(X instanceof Q9&&X.tick===K){let H=X.decoder,z=K8(W,H);return gJ(z,void 0)}else return new Y0(void 0);else if(X instanceof e0){let{head:H,tail:z}=V,U=X.group,q=iH(U,H);if(q instanceof w)Z=q[0],Q=K,Y=z,J=W;else return q}else return new Y0(void 0)}}function uV(Z,Q,Y,J){if(Q instanceof A)if(Y instanceof k8&&Y.tick===Z){let{element:X,dependencies:K,throttle:V,delay:W}=Y;return new w(new H8(Z,X,K,J,V,W,new m,0))}else return new Y0(void 0);else if(Y instanceof e0){let{head:X,tail:K}=Q,V=Y.group;return Z6(V5(V,X,G([])),(W)=>{let H,z,U;return H=W[0],z=W[1],U=W[2],Z6(uV(Z,K,z,J),(q)=>{return new w(new e0(VQ(H,q,U)))})})}else return new Y0(void 0)}function t6(Z,Q,Y,J){if(Q instanceof k){let X=Q[0];return m9(Z,X,Y,J)}else return[Z,j()]}function aH(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(X instanceof A)return W;else{let H=X.head;Z=X.tail,Q=K+1,Y=V,J=cV(H,B(K,V),W)}}}function cV(Z,Q,Y){if(Z instanceof k8)return Y;else if(Z instanceof H8)return Y;else if(Z instanceof Q9){let{tick:J,name:X,params:K,include:V,throttle:W,delay:H}=Z;return B(hV(J,a(Q),X,K,V,W,H),Y)}else{let J=Z.group;return aH(J,0,Q,Y)}}function PV(Z,Q,Y){let J=K5(0,G([]),Y(Z),G([])),X,K;X=J[0],K=J[1];let V=U8(B(j6(Q,(W)=>{return new D8(W)}),K));return[X,V]}function oH(Z,Q,Y,J){if(Q instanceof a7)if(Z instanceof t0){let X=Z.model,K=PV(X,j(),Y),V,W;return V=K[0],W=K[1],[new B8(X,0,V),W]}else return[Z,j()];else if(Q instanceof D8){let X=Q.message;return m9(Z,X,Y,J)}else if(Q instanceof KQ){let{tick:X,path:K,cleanup:V}=Q,W,H=JQ(Z),z=Z6(H,(I)=>{return uV(X,K,I,V)}),U=M9(z,(I)=>{return YQ(Z,I)});return W=h6(U,Z),[W,j()]}else if(Q instanceof o7){let{tick:X,path:K,message:V}=Q,W=JQ(Z);if(W instanceof w){let H=W[0],z=nH(H,X,K,V);if(z instanceof w){let U=z[0];return m9(Z,U,Y,J)}else return[Z,j()]}else return[Z,j()]}else if(Q instanceof t7){let{tick:X,path:K,message:V,ts:W}=Q,H=JQ(Z);if(H instanceof w){let z=H[0],U=mV(W,z,X,K,V);if(U instanceof u9){let{state:q,message:I}=U;return m9(YQ(Z,q),I,Y,J)}else if(U instanceof c9){let{state:q,message:I,delay:N,delay_tick:R}=U;return[YQ(Z,q),G5(N,new i7(X,K,R,I))]}else return[Z,j()]}else return[Z,j()]}else if(Q instanceof i7){let{tick:X,path:K,delay_tick:V,message:W}=Q,H=JQ(Z);if(H instanceof w){let z=H[0],U=$V(z,X,K,V,W);if(U instanceof w){let q=U[0][0],I=U[0][1];return m9(YQ(Z,q),I,Y,J)}else return[Z,j()]}else return[Z,j()]}else if(Q instanceof e7){let X=Q.forward;if(Z instanceof t0){let K=Z.model,V;if(X instanceof k){let R=X[0],O=J(K,R),D,T;D=O[0],T=O[1],V=[D,j6(T,(C)=>{return new D8(C)})]}else V=[K,j()];let W=V,H,z;H=W[0],z=W[1];let U=PV(H,j(),Y),q,I;return q=U[0],I=U[1],[new e6(H,0,q,1),U8(G([z,I]))]}else if(Z instanceof B8)return t6(Z,X,Y,J);else{let{state:K,clients:V}=Z,W=U8(cV(K,G([]),G([]))),H=t6(new e6(Z.model,Z.tick,Z.state,V+1),X,Y,J),z,U;return z=H[0],U=H[1],[z,U8(G([W,U]))]}}else{let X=Q.forward;if(Z instanceof t0)return t6(Z,X,Y,J);else if(Z instanceof B8)return t6(Z,X,Y,J);else{let K=Z.clients;if(K===1){let{model:V,state:W}=Z,H=U8(X5(W,G([]),G([]))),z=t6(new t0(V),X,Y,J),U,q;return U=z[0],q=z[1],[U,U8(G([H,q]))]}else{let V=K,W=new e6(Z.model,Z.tick,Z.state,V-1);return t6(W,X,Y,J)}}}}function yQ(Z,Q){return new HQ(Z,Q,new m,new m)}function tH(Z,Q){let Y;if(W0())Y=r6();else{let W,H=x9("off-topic:dispatch",q6(G(["detail","tick"]),J8,(q)=>{return q6(G(["detail","path"]),qZ(J8),(I)=>{return q6(G(["detail","message"]),KY,(N)=>{return O0(new o7(q,I,N))})})}));W=FV(H,G(["detail"]));let z=W,U=x9("off-topic:mount",O0(new a7));Y=I8("ot-client-runtime",G([z,U]),G([]))}let X=Y,K;if(Z instanceof t0){let W=Z.model;K=Q(W)}else if(Z instanceof B8){let{model:W,tick:H}=Z;K=E9(G([C9(G([H]))]),()=>{return Q(W)})}else{let{model:W,tick:H}=Z;K=E9(G([C9(G([H]))]),()=>{return Q(W)})}return FX(G([X,jZ(K,(W)=>{return new D8(W)})]))}function eH(Z,Q,Y){return[(K,V)=>{return oH(K,V,Q,Z)},(K)=>{return tH(K,Y)}]}function b(Z,Q,Y,J,X){let K=eH(Q,Y,J),V,W;V=K[0],W=K[1];let H=(D)=>{let T=Z(D),C,P;return C=T[0],P=T[1],[new t0(C),j6(P,(L)=>{return new D8(L)})]},z=qV(X),U,q;U=z[0],q=z[1];let I,N=T0(X,(D)=>{return zV(D,(T)=>{return new D8(T)})}),R=v8(N,G([tZ(new e7(U)),IV(new EV(q))]));return I=UV(R),HV(H,V,W,I)}function l8(){return new p9(G([]))}function r0(Z,Q){return new WQ(Z,Q,new m,new m)}function bQ(Z){return new p9(Z)}function g0(Z,Q,Y){return new Z9(Z,Q,G([]),Y,G([]),new m,new m)}function T6(Z,Q){return PZ("off-topic:command",v6(G([["name",X0(Z)],["params",O9(Q)]])))}function b8(Z,Q){if(Z instanceof Z9){let Y=Z.include;return new Z9(Z.name,Z.params,Z.dependencies,Z.decoder,v8(Q,Y),Z.throttle,Z.delay)}else if(Z instanceof p9){let Y=Z.subscriptions;return new p9(T0(Y,(J)=>{return b8(J,Q)}))}else return Z}function W5(){return V0("key",X8,(Z)=>{return V0("code",X8,(Q)=>{return V0("ctrlKey",S0,(Y)=>{return V0("altKey",S0,(J)=>{return V0("metaKey",S0,(X)=>{return V0("shiftKey",S0,(K)=>{return O0(new CV(Z,Q,Y,J,X,K))})})})})})})}function M8(Z,Q,Y){let J;if(Z instanceof fV)J="host";else if(Z instanceof gV)J="root";else if(Z instanceof MQ)J="window";else J="document";let X=J;if(W0())return yQ(G([s(X),s(Q)]),(V,W)=>{return v9(X,Q,(z)=>{let U=K8(z,Y);if(U instanceof w){let q=U[0];return V(q)}else return},W)});else return g0("off-topic/event",G([X0(X),X0(Q)]),Y)}function pV(Z){let Q=M8(new y8,"keypress",z0(W5(),Z));return b8(Q,J5)}function dV(Z){let Q=M8(new y8,"keydown",z0(W5(),Z));return b8(Q,J5)}function rV(Z){let Q=M8(new y8,"keyup",z0(W5(),Z));return b8(Q,J5)}function ZF(){return IZ(X8,(Z)=>{if(Z==="pen")return O0(new FQ);else if(Z==="touch")return O0(new Z5);else return O0(new d9)})}function XQ(){return R9(UZ,G([z0(J8,F0)]))}function H6(){return R9(J8,G([z0(UZ,$8)]))}function o9(){return V0("clientX",H6(),(Z)=>{return V0("clientY",H6(),(Q)=>{return V0("button",H6(),(Y)=>{return I6("pointerType",new d9,ZF(),(J)=>{return I6("pointerId",-1,H6(),(X)=>{return I6("isPrimary",!0,S0,(K)=>{return I6("pressure",0,XQ(),(V)=>{return I6("tiltX",0,H6(),(W)=>{return I6("tiltY",0,H6(),(H)=>{return V0("ctrlKey",S0,(z)=>{return V0("altKey",S0,(U)=>{return V0("metaKey",S0,(q)=>{return V0("shiftKey",S0,(I)=>{return O0(new wV(Z,Q,Y,J,X,K,V,W,H,z,U,q,I))})})})})})})})})})})})})})}function sV(Z){let Q=M8(new y8,"click",z0(o9(),Z));return b8(Q,a9)}function lV(Z){let Q=M8(new y8,"pointermove",z0(o9(),Z));return b8(Q,a9)}function iV(Z){let Q=M8(new y8,"pointerdown",z0(o9(),Z));return b8(Q,a9)}function nV(Z){let Q=M8(new y8,"pointerup",z0(o9(),Z));return b8(Q,a9)}function aV(Z){let Q=M8(new y8,"pointercancel",z0(o9(),Z));return b8(Q,a9)}function QF(){return IZ(J8,(Z)=>{if(Z===1)return O0(new kV);else if(Z===2)return O0(new yV);else return O0(new xV)})}function YF(){return V0("deltaX",XQ(),(Z)=>{return V0("deltaY",XQ(),(Q)=>{return V0("deltaZ",XQ(),(Y)=>{return V0("deltaMode",QF(),(J)=>{return V0("ctrlKey",S0,(X)=>{return V0("altKey",S0,(K)=>{return V0("metaKey",S0,(V)=>{return V0("shiftKey",S0,(W)=>{return O0(new bV(Z,Q,Y,J,X,K,V,W))})})})})})})})})}function oV(Z){let Q=M8(new y8,"wheel",z0(YF(),Z));return b8(Q,sH)}function fQ(Z){return V0("0",H6(),(Q)=>{return V0("1",H6(),(Y)=>{return O0(Z(Q,Y))})})}function tV(Z){if(W0())return r0(G([s("off-topic/window-size")]),(Y)=>{return v7((J,X)=>{return Y(Z(J,X))})});else return g0("off-topic/window-size",G([]),fQ(Z))}function SV(Z){if(Z==="portrait-primary")return new w(new r9);else if(Z==="portrait-secondary")return new w(new zQ);else if(Z==="landscape-primary")return new w(new UQ);else if(Z==="landscape-secondary")return new w(new qQ);else return new Y0(void 0)}function eV(Z,Q,Y){return IZ(X8,(J)=>{let X=Z(J);if(X instanceof w){let K=X[0];return O0(Y(K))}else return GY(Y(Q),"String")})}function Z4(Z,Q,Y){return(J)=>{let X=Z(J);if(X instanceof w){let K=X[0];return Y(Q(K))}else return}}function Q4(Z){if(W0())return r0(G([s("off-topic/screen-orientation")]),(Y)=>{return m7(Z4(SV,Z,Y))});else return g0("off-topic/screen-orientation",G([]),eV(SV,new qQ,Z))}function Y4(Z){if(W0())return r0(G([s("off-topic/window-scroll")]),(Y)=>{return $7((J,X)=>{return Y(Z(J,X))})});else return g0("off-topic/window-scroll",G([]),fQ(Z))}function H5(Z){if(Z instanceof s8)return"smooth";else if(Z instanceof IQ)return"instant";else return"auto"}function J4(Z,Q,Y){let J=H5(Y);if(W0())return A6((K,V)=>{return eZ("window",Z,Q,J,V)});else return T6("off-topic/scroll-to",G([X0("window"),c0(Z),c0(Q),X0(J)]))}function TV(Z){if(Z==="active")return new w(new s9);else if(Z==="passive")return new w(new l9);else if(Z==="hidden")return new w(new NQ);else if(Z==="frozen")return new w(new RQ);else if(Z==="terminated")return new w(new Q5);else return new Y0(void 0)}function X4(Z){if(W0())return r0(G([s("off-topic/page-state")]),(Y)=>{return k7(Z4(TV,Z,Y))});else return g0("off-topic/page-state",G([]),eV(TV,new l9,Z))}function K4(Z){if(W0())return r0(G([s("off-topic/window-hover")]),(Y)=>{return f7((J)=>{return Y(Z(J))})});else return g0("off-topic/window-hover",G([]),z0(S0,Z))}function V4(Z,Q){return QZ(!Z,l8(),()=>{if(W0())return r0(G([s("off-topic/prevent-unload")]),(J)=>{return g7(()=>{return J(Q)})});else return g0("off-topic/prevent-unload",G([]),O0(Q))})}function G4(Z,Q){if(W0())return yQ(G([s(Z)]),(J,X)=>{return r7(Z,(V,W)=>{return J(Q(V,W))},X)});else return g0("off-topic/element-size",G([X0(Z)]),fQ(Q))}function W4(Z,Q){if(W0())return yQ(G([s(Z)]),(J,X)=>{return l7(Z,(V,W)=>{return J(Q(V,W))},X)});else return g0("off-topic/element-scroll",G([X0(Z)]),fQ(Q))}function t9(Z,Q,Y,J){let X=H5(J);if(W0())return A6((V,W)=>{return eZ(Z,Q,Y,X,W)});else return T6("off-topic/scroll-to",G([X0(Z),c0(Q),c0(Y),X0(X)]))}function LV(Z){if(Z instanceof BQ)return"start";else if(Z instanceof i9)return"center";else if(Z instanceof DQ)return"end";else return"nearest"}function H4(Z,Q,Y,J){let X=LV(Q),K=LV(Y),V=H5(J);if(W0())return A6((H,z)=>{return C7(Z,X,K,V,z)});else return T6("off-topic/scroll-into-view",G([X0(Z),X0(X),X0(K),X0(V)]))}function F4(Z,Q){if(W0())return yQ(G([s(Z)]),(J,X)=>{return s7(Z,(V)=>{return J(Q(V))},X)});else return g0("off-topic/element-visible",G([X0(Z)]),z0(S0,Q))}function F5(Z){if(W0())return A6((Y,J)=>{return w7(Z,J)});else return T6("off-topic/focus",G([X0(Z)]))}function z4(){if(W0())return X6((Q)=>{return x7()});else return T6("off-topic/blur",G([]))}function U4(Z){if(W0())return r0(G([s("off-topic/language")]),(Y)=>{return u7((J)=>{return Y(Z(J))})});else return g0("off-topic/language",G([]),z0(X8,Z))}function gQ(Z,Q,Y,J){if(W0())return r0(G([Z]),(K)=>{return c7(Q,Y,(V)=>{return K(J(V))})});else return g0("off-topic/media-query",G([X0(Q),M6(Y,(K)=>{return O9(G([X0(K[0]),X0(K[1])]))})]),z0(X8,J))}function q4(Z){return gQ(s("off-topic/color-scheme"),"light",G([["(prefers-color-scheme: dark)","dark"]]),(Q)=>{if(Q==="dark")return Z(new OQ);else return Z(new AQ)})}function I4(Z){return gQ(s("off-topic/contrast"),"none",G([["(prefers-contrast: more)","more"],["(prefers-contrast: less)","less"],["(prefers-contrast: forced)","forced"]]),(Q)=>{if(Q==="more")return Z(new jQ);else if(Q==="less")return Z(new PQ);else if(Q==="forced")return Z(new SQ);else return Z(new TQ)})}function N4(Z){return gQ(s("off-topic/reduced-motion"),"no-preference",G([["(prefers-reduced-motion: reduce)","reduce"]]),(Q)=>{if(Q==="reduce")return Z(new LQ);else return Z(new EQ)})}function JF(Z,Q,Y){let J,K=FZ(Q,(W,H)=>{return N6(H[0],W[0])});J=T0(K,(W)=>{return["(min-width: "+$(W[0])+"px)",W[1]]});let V=J;return gQ(s(V),Z,V,Y)}function R4(Z){return JF("xs",G([[640,"sm"],[768,"md"],[1024,"lg"],[1280,"xl"],[1536,"2xl"]]),(Q)=>{if(Q==="sm")return Z(new CQ);else if(Q==="md")return Z(new wQ);else if(Q==="lg")return Z(new xQ);else if(Q==="xl")return Z(new kQ);else if(Q==="2xl")return Z(new Y5);else return Z(new n9)})}function GQ(Z,Q){if(Z)return new k(Q);else return new m}function B4(Z,Q){return V0("0",S0,(Y)=>{return V0("1",X8,(J)=>{return O0(Q(Z(Y,J)))})})}function D4(Z,Q,Y){return(J,X)=>{return Y(Q(Z(J,X)))}}function M4(Z,Q){if(W0())return r0(G([s("off-topic/local-storage"),s(Z)]),(J)=>{return p7(Z,D4(GQ,Q,J))});else return g0("off-topic/local-storage",G([X0(Z)]),B4(GQ,Q))}function O4(Z,Q){if(W0())return r0(G([s("off-topic/session-storage"),s(Z)]),(J)=>{return d7(Z,D4(GQ,Q,J))});else return g0("off-topic/session-storage",G([X0(Z)]),B4(GQ,Q))}function z5(Z,Q){if(W0())return X6((J)=>{if(Q instanceof k){let X=Q[0];return S7(Z,X)}else return T7(Z)});else return T6("off-topic/set-local-storage",G([X0(Z),(()=>{if(Q instanceof k){let J=Q[0];return X0(J)}else return UY()})()]))}function U5(Z,Q){if(W0())return X6((J)=>{if(Q instanceof k){let X=Q[0];return L7(Z,X)}else return E7(Z)});else return T6("off-topic/set-session-storage",G([X0(Z),(()=>{if(Q instanceof k){let J=Q[0];return X0(J)}else return UY()})()]))}function A4(Z){return X6((Q)=>{return Q(Z(k9()))})}function j4(Z){if(W0())return r0(G([s("off-topic/here")]),(Y)=>{return y7((J)=>{return Y(Z(zY(J)))})});else return g0("off-topic/here",G([]),z0(J8,(Y)=>{return Z(zY(Y))}))}function P4(Z,Q,Y){let J=R6(Z);return r0(G([s(J),s(Q)]),(X)=>{if(Q)X(Y(k9()));return _7(J,()=>{return X(Y(k9()))})})}function S4(Z,Q){let Y=R6(Z);if(W0())return r0(G([s(Y)]),(X)=>{return h7(Y,(K)=>{return X(Q(K))})});else return g0("off-topic/on-idle",G([c0(Y)]),z0(S0,Q))}function T4(Z){if(W0())return r0(G([s("off-topic/online")]),(Y)=>{return b7((J)=>{return Y(Z(J))})});else return g0("off-topic/online",G([]),z0(S0,Z))}function O8(Z,Q){if(Q==="")return Z;else return B(Q,Z)}function XF(Z){return I9(Z)!==Z}function KF(Z,Q,Y,J){while(!0){let X=Z,K=Q,V=Y,W=J;if(X instanceof A)if(V==="")return a(W);else return a(O8(W,V));else{let H=X.head;if(H===` +`)Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="\t")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="!")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="?")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="#")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H===".")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="-")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H==="_")Z=X.tail,Q=!1,Y="",J=O8(W,V);else if(H===" ")Z=X.tail,Q=!1,Y="",J=O8(W,V);else{let z=H,U=X.tail;if(XF(z))if(K)Z=U,Q=K,Y=V+z,J=W;else Z=U,Q=!0,Y=z,J=O8(W,V);else Z=U,Q=!1,Y=V+z,J=W}}}}function VF(Z){let Y=q9(Z);return KF(Y,!1,"",G([]))}function L4(Z){let Y=VF(Z),J=U6(Y,"-");return I9(J)}var WF="src/demo.gleam",HF="https://github.com/joshi-monster/off_topic/blob/main/demos/src/demos/";function f(Z,Q){return c(G([M("rounded overflow-hidden border border-card-border bg-card-bg text-card-text")]),G([c(G([M("max-h-32 flex items-center justify-center p-6 overflow-hidden")]),G([Q])),c(G([M("border-t border-card-border px-4 py-1.5 flex justify-between")]),G([CY(G([OY(HF+Z+".gleam"),eJ("_blank"),ZX("noopener noreferrer"),M("text-xs font-mono text-link hover:underline")]),G([S("source")])),CY(G([OY("#"),M("text-xs font-mono text-link hover:underline")]),G([S("↑ top")]))]))]))}function g(Z,Q){return zZ(Q,(Y)=>{let J=D7(Z,"ot-demo-"+L4(Y));if(!(J instanceof w))throw G9("let_assert",WF,"demo",57,"register","Pattern match failed, no pattern matched the value.",{value:J,start:1474,end:1552,pattern_start:1485,pattern_end:1490});return J})}class q5 extends F{}class _Q extends F{}class I5 extends F{}class N5 extends F{}class E4 extends F{}function FF(Z){return f("after",c(G([M("flex items-center gap-3 font-mono text-sm")]),G([N0(G([R0(new N5),QX(Z instanceof _Q),M("px-3 py-1 border border-card-border rounded cursor-pointer disabled:opacity-40")]),G([(()=>{if(Z instanceof I5)return S("again");else return S("fire")})()])),x(G([M("opacity-60")]),G([(()=>{if(Z instanceof q5)return r6();else if(Z instanceof _Q)return S("waiting 2s…");else return S("fired!")})()]))])))}function zF(Z){return l8()}function UF(Z,Q){if(Q instanceof N5)return[new _Q,G5(S8(2),new E4)];else return[new I5,j()]}function qF(Z){return[new q5,j()]}function IF(){return b(qF,UF,zF,FF,G([]))}function C4(){return g(IF(),G(["after"]))}class R5 extends F{constructor(Z){super();this.breakpoint=Z}}class w4 extends F{constructor(Z){super();this[0]=Z}}function RF(Z){let Q,Y=Z.breakpoint;if(Y instanceof n9)Q="xs";else if(Y instanceof CQ)Q="sm";else if(Y instanceof wQ)Q="md";else if(Y instanceof xQ)Q="lg";else if(Y instanceof kQ)Q="xl";else Q="2xl";let J=Q;return f("breakpoints",x(G([M("font-mono text-sm")]),G([S("Current breakpoint: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function BF(Z){return R4((Q)=>{return new w4(Q)})}function DF(Z,Q){let Y=Q[0];return[new R5(Y),j()]}function MF(Z){return[new R5(new n9),j()]}function OF(){return b(MF,DF,BF,RF,G([]))}function x4(){return g(OF(),G(["breakpoints","tailwind_breakpoints"]))}class B5 extends F{constructor(Z){super();this.scheme=Z}}class k4 extends F{constructor(Z){super();this[0]=Z}}function jF(Z){let Q;if(Z.scheme instanceof OQ)Q="dark";else Q="light";let J=Q;return f("colour_scheme",x(G([M("font-mono text-sm")]),G([S("colour scheme: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function PF(Z){return q4((Q)=>{return new k4(Q)})}function SF(Z,Q){let Y=Q[0];return[new B5(Y),j()]}function TF(Z){return[new B5(new AQ),j()]}function LF(){return b(TF,SF,PF,jF,G([]))}function y4(){return g(LF(),G(["colour_scheme"]))}class D5 extends F{constructor(Z){super();this.pref=Z}}class b4 extends F{constructor(Z){super();this[0]=Z}}function CF(Z){let Q,Y=Z.pref;if(Y instanceof jQ)Q="more";else if(Y instanceof PQ)Q="less";else if(Y instanceof SQ)Q="forced";else Q="no-preference";let J=Q;return f("contrast",x(G([M("font-mono text-sm")]),G([S("contrast: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function wF(Z){return I4((Q)=>{return new b4(Q)})}function xF(Z,Q){let Y=Q[0];return[new D5(Y),j()]}function kF(Z){return[new D5(new TQ),j()]}function yF(){return b(kF,xF,wF,CF,G([]))}function f4(){return g(yF(),G(["contrast"]))}class O5 extends F{constructor(Z,Q){super();this.x=Z,this.y=Q}}class A5 extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}class j5 extends F{}class g4 extends F{}var M5="#scroll-box";function fF(Z){let Q="text-xs px-2 py-1 border border-card-border rounded cursor-pointer";return f("element_scroll",c(G([M("flex items-center gap-3 font-mono text-sm")]),G([c(G([M("flex flex-col justify-between h-16")]),G([N0(G([R0(new j5),M(Q)]),G([S("↑ top")])),N0(G([R0(new g4),M(Q)]),G([S("↓ bottom")]))])),c(G([T8("scroll-box"),M("overflow-y-auto h-16 w-24 border border-card-border rounded shrink-0")]),T0(G([1,2,3,4,5,6,7,8,9,10,11,12]),(Y)=>{return c(G([M("px-2 py-0.5 text-xs")]),G([S("item "+$(Y))]))})),x(G([M("opacity-40 text-xs")]),G([S("y: "+$(Z.y))]))])))}function gF(Z){return W4(M5,(Q,Y)=>{return new A5(Q,Y)})}function _F(Z,Q){if(Q instanceof A5){let Y=Q[0],J=Q[1];return[new O5(Y,J),j()]}else if(Q instanceof j5)return[Z,t9(M5,0,0,new s8)];else return[Z,t9(M5,0,999,new s8)]}function hF(Z){return[new O5(0,0),j()]}function vF(){return b(hF,_F,gF,fF,G([]))}function _4(){return g(vF(),G(["element_scroll","element_scroll_to"]))}class P5 extends F{constructor(Z,Q){super();this.w=Z,this.h=Q}}class h4 extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}function mF(Z){return f("element_size",c(G([T8("size-target"),tJ(G([["resize","both"],["overflow","hidden"],["min-width","80px"],["min-height","48px"],["width","120px"],["height","64px"]])),M("flex items-center justify-center border border-dashed border-card-border rounded font-mono text-sm")]),G([S($(Z.w)+" × "+$(Z.h))])))}function uF(Z){return G4("#size-target",(Q,Y)=>{return new h4(Q,Y)})}function cF(Z,Q){let Y=Q[0],J=Q[1];return[new P5(Y,J),j()]}function pF(Z){return[new P5(0,0),j()]}function dF(){return b(pF,cF,uF,mF,G([]))}function v4(){return g(dF(),G(["element_size"]))}class hQ extends F{constructor(Z){super();this.visible=Z}}class S5 extends F{constructor(Z){super();this[0]=Z}}class $4 extends F{}function sF(Z){return f("element_visible",c(G([M("flex items-center gap-4 font-mono text-sm")]),G([c(G([T8("vis-scroll"),M("overflow-auto h-20 w-40 border border-card-border rounded shrink-0")]),G([c(G([M("w-96 h-40 flex items-center justify-center")]),G([c(G([T8("vis-target"),M("w-8 h-8 rounded bg-card-border")]),G([]))]))])),x(G([M("w-20 text-center"),(()=>{if(Z.visible)return oJ();else return M("opacity-40")})()]),G([(()=>{if(Z.visible)return S("visible");else return S("not visible")})()]))])))}function lF(Z){return F4("#vis-target",(Q)=>{return new S5(Q)})}function iF(Z,Q){if(Q instanceof S5){let Y=Q[0];return[new hQ(Y),j()]}else return[new hQ(!0),t9("#vis-scroll",112,40,new IQ)]}function nF(Z){return[new hQ(!0),j()]}function aF(){return b(nF,iF,lF,sF,G([tZ(new $4)]))}function m4(){return g(aF(),G(["element_visible"]))}class T5 extends F{constructor(Z){super();this.ts=Z}}class u4 extends F{constructor(Z){super();this[0]=Z}}function tF(Z){let Q,Y=Z.ts;if(Y instanceof k){let X=Y[0];Q=$($8(TZ(X)))}else Q="—";let J=Q;return f("every",x(G([M("font-mono text-3xl font-light tabular-nums")]),G([S(J)])))}function eF(Z){return P4(S8(1),!0,(Q)=>{return new u4(Q)})}function Zz(Z,Q){let Y=Q[0];return[new T5(new k(Y)),j()]}function Qz(Z){return[new T5(new m),j()]}function Yz(){return b(Qz,Zz,eF,tF,G([]))}function c4(){return g(Yz(),G(["every"]))}class p4 extends F{}class L5 extends F{}class E5 extends F{}class d4 extends F{}function Xz(Z){let Q="border border-card-border rounded px-2 py-1 text-xs bg-card-bg w-24",Y="text-xs px-2 py-1 border border-card-border rounded cursor-pointer";return f("focus_blur",c(G([M("flex flex-col gap-2 font-mono text-sm")]),G([c(G([M("flex items-center gap-2")]),G([w9(G([T8("input-a"),P9("input a"),M(Q)])),N0(G([R0(new L5),M(Y)]),G([S("focus")]))])),c(G([M("flex items-center gap-2")]),G([w9(G([T8("input-b"),P9("input b"),M(Q)])),N0(G([R0(new E5),M(Y)]),G([S("focus")])),N0(G([R0(new d4),M(Y)]),G([S("blur")]))]))])))}function Kz(Z){return l8()}function Vz(Z,Q){if(Q instanceof L5)return[Z,F5("#input-a")];else if(Q instanceof E5)return[Z,F5("#input-b")];else return[Z,z4()]}function Gz(Z){return[new p4,j()]}function Wz(){return b(Gz,Vz,Kz,Xz,G([]))}function r4(){return g(Wz(),G(["focus","blur"]))}class C5 extends F{constructor(Z){super();this.offset=Z}}class s4 extends F{constructor(Z){super();this[0]=Z}}function Fz(Z){let Q=globalThis.Math.trunc(R6(Z)/60000),Y;if(Q>=0)Y="+";else Y="-";let X=Y,K=FY(Q),V=globalThis.Math.trunc(K/60),W=K%60,H;if(W<10)H="0"+$(W);else H=$(W);let U=H;return"UTC"+X+$(V)+":"+U}function zz(Z){let Q,Y=Z.offset;if(Y instanceof k){let X=Y[0];Q=Fz(X)}else Q="—";let J=Q;return f("here",x(G([M("font-mono text-2xl font-light")]),G([S(J)])))}function Uz(Z){return j4((Q)=>{return new s4(Q)})}function qz(Z,Q){let Y=Q[0];return[new C5(new k(Y)),j()]}function Iz(Z){return[new C5(new m),j()]}function Nz(){return b(Iz,qz,Uz,zz,G([]))}function l4(){return g(Nz(),G(["here"]))}class w5 extends F{}class i4 extends F{constructor(Z,Q){super();this.event_type=Z,this.key=Q}}class x5 extends F{constructor(Z){super();this.last=Z}}class $Q extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}function vQ(Z,Q,Y){if(Z)return Q;else return Y}function Bz(Z){let Q,Y=Z.last;if(Y instanceof w5)Q=x(G([M("opacity-40")]),G([S("press any key")]));else{let{event_type:X,key:K}=Y,V,W=G([vQ(K.ctrl,"ctrl",""),vQ(K.alt,"alt",""),vQ(K.meta,"meta",""),vQ(K.shift,"shift",""),K.key]),H=BJ(W,(U)=>{return U!==""});V=U6(H,"+");let z=V;Q=c(G([M("flex items-center gap-3")]),G([x(G([M("opacity-40 text-xs")]),G([S(X)])),x(G([]),G([S(z)]))]))}let J=Q;return f("keyboard",c(G([M("font-mono text-sm")]),G([J])))}function Dz(Z){return bQ(G([pV((Q)=>{return new $Q("press",Q)}),dV((Q)=>{return new $Q("down",Q)}),rV((Q)=>{return new $Q("up",Q)})]))}function Mz(Z,Q){let Y=Q[0],J=Q[1];return[new x5(new i4(Y,J)),j()]}function Oz(Z){return[new x5(new w5),j()]}function Az(){return b(Oz,Mz,Dz,Bz,G([]))}function n4(){return g(Az(),G(["on_key_press","on_key_down","on_key_up"]))}class k5 extends F{constructor(Z){super();this.lang=Z}}class a4 extends F{constructor(Z){super();this[0]=Z}}function Pz(Z){return f("language",x(G([M("font-mono text-sm")]),G([S("preferred language: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(Z.lang)]))])))}function Sz(Z){return U4((Q)=>{return new a4(Q)})}function Tz(Z,Q){let Y=Q[0];return[new k5(Y),j()]}function Lz(Z){return[new k5(""),j()]}function Ez(){return b(Lz,Tz,Sz,Pz,G([]))}function o4(){return g(Ez(),G(["language"]))}class e9 extends F{constructor(Z){super();this.count=Z}}class b5 extends F{constructor(Z){super();this[0]=Z}}class f5 extends F{}class t4 extends F{}var y5="ot-demo-ls-count";function wz(Z){let Q=M("w-8 h-8 border border-card-border rounded font-mono cursor-pointer flex items-center justify-center");return f("local_storage",c(G([M("flex items-center gap-4 font-mono")]),G([N0(G([R0(new t4),Q]),G([S("−")])),x(G([M("text-2xl font-light w-12 text-center tabular-nums")]),G([S($(Z.count))])),N0(G([R0(new f5),Q]),G([S("+")]))])))}function xz(Z){return M4(y5,(Q)=>{return new b5(Q)})}function kz(Z,Q){if(Q instanceof b5){let Y=Q[0],J;if(Y instanceof k){let K=Y[0];J=h6(D9(K),0)}else J=0;return[new e9(J),j()]}else if(Q instanceof f5){let Y=Z.count+1;return[new e9(Y),z5(y5,new k($(Y)))]}else{let Y=Z.count-1;return[new e9(Y),z5(y5,new k($(Y)))]}}function yz(Z){return[new e9(0),j()]}function bz(){return b(yz,kz,xz,wz,G([]))}function e4(){return g(bz(),G(["local_storage","set_local_storage"]))}class g5 extends F{constructor(Z){super();this.timestamp=Z}}class _5 extends F{}class ZG extends F{constructor(Z){super();this[0]=Z}}function gz(Z){let Q,Y=Z.timestamp;if(Y instanceof k){let X=Y[0];Q=$($8(TZ(X)))}else Q="—";let J=Q;return f("now",c(G([M("flex items-center gap-3 font-mono text-sm")]),G([N0(G([R0(new _5),M("px-3 py-1 border border-card-border rounded cursor-pointer")]),G([S("capture")])),x(G([M("text-xl font-light tabular-nums")]),G([S(J)]))])))}function _z(Z){return l8()}function hz(Z,Q){if(Q instanceof _5)return[Z,A4((Y)=>{return new ZG(Y)})];else{let Y=Q[0];return[new g5(new k(Y)),j()]}}function vz(Z){return[new g5(new m),j()]}function $z(){return b(vz,hz,_z,gz,G([]))}function QG(){return g($z(),G(["now"]))}class h5 extends F{constructor(Z,Q){super();this.x=Z,this.y=Q}}class YG extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}function uz(Z){return f("on",x(G([M("font-mono text-sm opacity-60")]),G([S("("+$(Z.x)+", "+$(Z.y)+")")])))}function cz(Z){return M8(new MQ,"click",V0("clientX",J8,(Q)=>{return V0("clientY",J8,(Y)=>{return O0(new YG(Q,Y))})}))}function pz(Z,Q){let Y=Q[0],J=Q[1];return[new h5(Y,J),j()]}function dz(Z){return[new h5(0,0),j()]}function rz(){return b(dz,pz,cz,uz,G([]))}function JG(){return g(rz(),G(["on"]))}class v5 extends F{constructor(Z){super();this.idle=Z}}class XG extends F{constructor(Z){super();this[0]=Z}}function lz(Z){let Q;if(Z.idle)Q="idle";else Q="active";let J=Q;return f("on_idle",x(G([M("font-mono text-sm px-3 py-1 border border-card-border rounded")]),G([S(J)])))}function iz(Z){return S4(S8(3),(Q)=>{return new XG(Q)})}function nz(Z,Q){let Y=Q[0];return[new v5(Y),j()]}function az(Z){return[new v5(!1),j()]}function oz(){return b(az,nz,iz,lz,G([]))}function KG(){return g(oz(),G(["on_idle"]))}class $5 extends F{constructor(Z){super();this.online=Z}}class VG extends F{constructor(Z){super();this[0]=Z}}function ez(Z){let Q;if(Z.online)Q=["online","text-green-500"];else Q=["offline","text-red-500"];let J=Q,X,K;return X=J[0],K=J[1],f("online",x(G([M("font-mono text-sm px-3 py-1 border border-card-border rounded flex items-center gap-2"),M(K)]),G([S("● "+X)])))}function ZU(Z){return T4((Q)=>{return new VG(Q)})}function QU(Z,Q){let Y=Q[0];return[new $5(Y),j()]}function YU(Z){return[new $5(!0),j()]}function JU(){return b(YU,QU,ZU,ez,G([]))}function GG(){return g(JU(),G(["online"]))}class m5 extends F{constructor(Z){super();this.state=Z}}class WG extends F{constructor(Z){super();this[0]=Z}}function KU(Z){let Q,Y=Z.state;if(Y instanceof s9)Q="active";else if(Y instanceof l9)Q="passive";else if(Y instanceof NQ)Q="hidden";else if(Y instanceof RQ)Q="frozen";else Q="terminated";let J=Q;return f("page_state",x(G([M("font-mono text-sm")]),G([S("page state: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function VU(Z){return X4((Q)=>{return new WG(Q)})}function GU(Z,Q){let Y=Q[0];return[new m5(Y),j()]}function WU(Z){return[new m5(new s9),j()]}function HU(){return b(WU,GU,VU,KU,G([]))}function HG(){return g(HU(),G(["page_state"]))}class u5 extends F{}class c5 extends F{constructor(Z,Q){super();this.event_type=Z,this.pointer=Q}}class FG extends F{constructor(Z){super();this.wheel=Z}}class mQ extends F{constructor(Z){super();this.last=Z}}class L6 extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}class zG extends F{constructor(Z){super();this[0]=Z}}function zU(Z){if(Z instanceof d9)return"mouse";else if(Z instanceof FQ)return"pen";else return"touch"}function UU(Z){let Q,Y=Z.last;if(Y instanceof u5)Q=x(G([M("opacity-40")]),G([S("move or click")]));else if(Y instanceof c5){let X=Y.event_type,K=Y.pointer.x,V=Y.pointer.y,W=Y.pointer.pointer_type;Q=c(G([M("flex items-center gap-3")]),G([x(G([M("opacity-40 text-xs")]),G([S(X)])),x(G([]),G([S("("+$(K)+", "+$(V)+")")])),x(G([M("opacity-40 text-xs")]),G([S(zU(W))]))]))}else{let X=Y.wheel.delta_y;Q=c(G([M("flex items-center gap-3")]),G([x(G([M("opacity-40 text-xs")]),G([S("wheel")])),x(G([]),G([S("dy="+g6(X))]))]))}let J=Q;return f("pointer",c(G([M("font-mono text-sm")]),G([J])))}function qU(Z){return bQ(G([sV((Q)=>{return new L6("click",Q)}),lV((Q)=>{return new L6("move",Q)}),iV((Q)=>{return new L6("down",Q)}),nV((Q)=>{return new L6("up",Q)}),aV((Q)=>{return new L6("cancel",Q)}),oV((Q)=>{return new zG(Q)})]))}function IU(Z,Q){if(Q instanceof L6){let Y=Q[0],J=Q[1];return[new mQ(new c5(Y,J)),j()]}else{let Y=Q[0];return[new mQ(new FG(Y)),j()]}}function NU(Z){return[new mQ(new u5),j()]}function RU(){return b(NU,IU,qU,UU,G([]))}function UG(){return g(RU(),G(["on_click","on_pointer_move","on_pointer_down","on_pointer_up","on_pointer_cancel","on_wheel"]))}class uQ extends F{constructor(Z,Q){super();this.text=Z,this.saved=Q}}class p5 extends F{constructor(Z){super();this[0]=Z}}class d5 extends F{}class qG extends F{}function DU(Z){let Q=Z.text!==Z.saved;return f("prevent_unload",c(G([M("flex flex-col gap-2 w-full font-mono text-sm")]),G([w9(G([YX(Z.text),P9("type something…"),M("border border-card-border rounded px-2 py-1 text-xs bg-card-bg"),zX((Y)=>{return new p5(Y)})])),c(G([M("flex items-center gap-2")]),G([N0(G([R0(new d5),M("text-xs px-2 py-1 border border-card-border rounded cursor-pointer")]),G([S("save")])),x(G([M((()=>{if(Q)return"text-xs text-yellow-500";else return"text-xs opacity-40"})())]),G([(()=>{if(Q)return S("unsaved changes");else return S("saved")})()]))]))])))}function MU(Z){return V4(Z.text!==Z.saved,new qG)}function OU(Z,Q){if(Q instanceof p5){let Y=Q[0];return[new uQ(Y,Z.saved),j()]}else if(Q instanceof d5)return[new uQ(Z.text,Z.text),j()];else return[Z,j()]}function AU(Z){return[new uQ("",""),j()]}function jU(){return b(AU,OU,MU,DU,G([]))}function IG(){return g(jU(),G(["prevent_unload"]))}class r5 extends F{constructor(Z){super();this.motion=Z}}class NG extends F{constructor(Z){super();this[0]=Z}}function SU(Z){let Q;if(Z.motion instanceof LQ)Q="reduce";else Q="no-preference";let J=Q;return f("reduced_motion",x(G([M("font-mono text-sm")]),G([S("motion: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function TU(Z){return N4((Q)=>{return new NG(Q)})}function LU(Z,Q){let Y=Q[0];return[new r5(Y),j()]}function EU(Z){return[new r5(new EQ),j()]}function CU(){return b(EU,LU,TU,SU,G([]))}function RG(){return g(CU(),G(["reduced_motion"]))}class s5 extends F{constructor(Z){super();this.orientation=Z}}class BG extends F{constructor(Z){super();this[0]=Z}}function xU(Z){let Q,Y=Z.orientation;if(Y instanceof r9)Q="portrait";else if(Y instanceof zQ)Q="portrait (flipped)";else if(Y instanceof UQ)Q="landscape";else Q="landscape (flipped)";let J=Q;return f("screen_orientation",x(G([M("font-mono text-sm")]),G([S("orientation: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function kU(Z){return Q4((Q)=>{return new BG(Q)})}function yU(Z,Q){let Y=Q[0];return[new s5(Y),j()]}function bU(Z){return[new s5(new r9),j()]}function fU(){return b(bU,yU,kU,xU,G([]))}function DG(){return g(fU(),G(["screen_orientation"]))}class MG extends F{}class cQ extends F{constructor(Z){super();this[0]=Z}}function _U(Z){let Q="text-xs px-2 py-1 border border-card-border rounded cursor-pointer";return f("scroll_into_view",c(G([M("flex items-center gap-2 font-mono text-sm")]),G([N0(G([R0(new cQ(new BQ)),M(Q)]),G([S("↑ start")])),N0(G([R0(new cQ(new i9)),M(Q)]),G([S("⊞ center")])),N0(G([R0(new cQ(new DQ)),M(Q)]),G([S("↓ end")]))])))}function hU(Z){return l8()}function vU(Z,Q){let Y=Q[0];return[Z,H4("host",Y,new i9,new s8)]}function $U(Z){return[new MG,j()]}function mU(){return b($U,vU,hU,_U,G([]))}function OG(){return g(mU(),G(["scroll_into_view"]))}class ZZ extends F{constructor(Z){super();this.count=Z}}class i5 extends F{constructor(Z){super();this[0]=Z}}class n5 extends F{}class AG extends F{}var l5="ot-demo-ss-count";function cU(Z){let Q=M("w-8 h-8 border border-card-border rounded font-mono cursor-pointer flex items-center justify-center");return f("session_storage",c(G([M("flex items-center gap-4 font-mono")]),G([N0(G([R0(new AG),Q]),G([S("−")])),x(G([M("text-2xl font-light w-12 text-center tabular-nums")]),G([S($(Z.count))])),N0(G([R0(new n5),Q]),G([S("+")]))])))}function pU(Z){return O4(l5,(Q)=>{return new i5(Q)})}function dU(Z,Q){if(Q instanceof i5){let Y=Q[0],J;if(Y instanceof k){let K=Y[0];J=h6(D9(K),0)}else J=0;return[new ZZ(J),j()]}else if(Q instanceof n5){let Y=Z.count+1;return[new ZZ(Y),U5(l5,new k($(Y)))]}else{let Y=Z.count-1;return[new ZZ(Y),U5(l5,new k($(Y)))]}}function rU(Z){return[new ZZ(0),j()]}function sU(){return b(rU,dU,pU,cU,G([]))}function jG(){return g(sU(),G(["session_storage","set_session_storage"]))}class a5 extends F{constructor(Z){super();this.hovering=Z}}class PG extends F{constructor(Z){super();this[0]=Z}}function iU(Z){let Q;if(Z.hovering)Q="hovering";else Q="not hovering";let J=Q;return f("window_hover",x(G([M("font-mono text-sm")]),G([S("hover: "),x(G([M("px-3 py-1 border border-card-border rounded")]),G([S(J)]))])))}function nU(Z){return K4((Q)=>{return new PG(Q)})}function aU(Z,Q){let Y=Q[0];return[new a5(Y),j()]}function oU(Z){return[new a5(!1),j()]}function tU(){return b(oU,aU,nU,iU,G([]))}function SG(){return g(tU(),G(["window_hover"]))}class o5 extends F{constructor(Z,Q){super();this.x=Z,this.y=Q}}class t5 extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}class TG extends F{}function Zq(Z){return f("window_scroll",c(G([M("flex items-center gap-4 font-mono text-sm")]),G([x(G([M("tabular-nums opacity-60")]),G([S($(Z.x)+", "+$(Z.y))])),N0(G([R0(new TG),M("text-xs px-2 py-1 border border-card-border rounded cursor-pointer")]),G([S("↓ +100px")]))])))}function Qq(Z){return Y4((Q,Y)=>{return new t5(Q,Y)})}function Yq(Z,Q){if(Q instanceof t5){let Y=Q[0],J=Q[1];return[new o5(Y,J),j()]}else return[Z,J4(Z.x,Z.y+100,new s8)]}function Jq(Z){return[new o5(0,0),j()]}function Xq(){return b(Jq,Yq,Qq,Zq,G([]))}function LG(){return g(Xq(),G(["window_scroll","scroll_to"]))}class e5 extends F{constructor(Z,Q){super();this.w=Z,this.h=Q}}class EG extends F{constructor(Z,Q){super();this[0]=Z,this[1]=Q}}function Vq(Z){return f("window_size",x(G([M("font-mono text-3xl font-light")]),G([S($(Z.w)+" × "+$(Z.h))])))}function Gq(Z){return tV((Q,Y)=>{return new EG(Q,Y)})}function Wq(Z,Q){let Y=Q[0],J=Q[1];return[new e5(Y,J),j()]}function Hq(Z){return[new e5(0,0),j()]}function Fq(){return b(Hq,Wq,Gq,Vq,G([]))}function CG(){return g(Fq(),G(["window_size"]))}function Uq(){return n4(),UG(),CG(),DG(),LG(),SG(),HG(),IG(),v4(),_4(),OG(),m4(),r4(),JG(),y4(),RG(),f4(),o4(),x4(),e4(),jG(),c4(),C4(),QG(),l4(),KG(),GG()}function wG(){return Uq()}wG(); diff --git a/demos/dist/index.html b/demos/dist/index.html new file mode 100644 index 0000000..d19a8c8 --- /dev/null +++ b/demos/dist/index.html @@ -0,0 +1,2 @@ + +component
\ No newline at end of file diff --git a/demos/gleam.toml b/demos/gleam.toml new file mode 100644 index 0000000..f152abe --- /dev/null +++ b/demos/gleam.toml @@ -0,0 +1,24 @@ +name = "demos" +version = "1.0.0" +target = "javascript" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "", repo = "" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +lustre = ">= 5.6.0 and < 6.0.0" +off_topic = { path = ".." } +gleam_stdlib = ">= 1.0.0 and < 2.0.0" +gleam_time = ">= 1.8.0 and < 2.0.0" +justin = ">= 1.0.0 and < 2.0.0" + +[dev_dependencies] +lustre_dev_tools = ">= 2.3.6 and < 3.0.0" diff --git a/demos/manifest.toml b/demos/manifest.toml new file mode 100644 index 0000000..7abd464 --- /dev/null +++ b/demos/manifest.toml @@ -0,0 +1,49 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "booklet", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "booklet", source = "hex", outer_checksum = "08E0FDB78DC4D8A5D3C80295B021505C7D2A2E7B6C6D5EAB7286C36F4A53C851" }, + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, + { name = "envoy", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "9C6FBB6BFA02A52798BEEC5977A738CAD6E4A057F4B67FD0C8061AD2502C191A" }, + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "gleam_community_ansi", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "B5AA433AF84313E23FDF90CCFF752B9380FE9FFCE02B2949D49B7AACCC77B16D" }, + { name = "gleam_community_colour", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "6DB4665555D7D2B27F0EA32EF47E8BEBC4303821765F9C73D483F38EE24894F0" }, + { name = "gleam_crypto", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "2DE9E4EF53CF6FEE049D4F765731F7178F7A11AEFAE00EEE63BF7536B354AD3F" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "960090C2FB391784BB34267B099DC9315CC1B1F6013E7415BC763CEF1905D7D3" }, + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, + { name = "glisten", version = "9.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "7795AA50830656F3A0316A6B26595F893C83272DA901B3405E31339CAA31A10B" }, + { name = "gramps", version = "6.0.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "D55636072DEE173F6586A5679D3C02EC7A0DE3F8646B78C351B72908FF223DF7" }, + { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, + { name = "houdini", version = "1.2.1", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "6F8AC2F12974567FB744BEA66AC93CEB76AAEA19AD28564623F76CDA9BC26A85" }, + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, + { name = "justin", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "8B1C62269E8607D0A0ED698B7903984CE0BF7B6B3C0DEA3C6C8302B39402837B" }, + { name = "logging", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "BC5F18CE5DD9686100229FE5409BDC3DD5C46D5A7DF2F804AD2D8F0DD6C5060E" }, + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, + { name = "lustre_dev_tools", version = "2.3.6", build_tools = ["gleam"], requirements = ["argv", "booklet", "filepath", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_regexp", "gleam_stdlib", "glint", "group_registry", "justin", "lustre", "mist", "polly", "simplifile", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "7A650A107C7E767D902A8CB608460B927DE0CDD47D5BA04DED71F9F622FE6AD9" }, + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, + { name = "mist", version = "6.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "1B07F321D5FA0CB162D81496F2DE96AEB6EF8980F4F38230A4CC3F849497E020" }, + { name = "off_topic", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib", "gleam_time", "lustre"], source = "local", path = ".." }, + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, + { name = "polly", version = "3.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_erlang", "gleam_otp", "gleam_stdlib", "simplifile"], otp_app = "polly", source = "hex", outer_checksum = "51FB565D81FF6212FDF3306D44419601F2A7C4EDD1F00FC9DA5C376A00AED4FE" }, + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, + { name = "snag", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "274F41D6C3ECF99F7686FDCE54183333E41D2C1CA5A3A673F9A8B2C7A4401077" }, + { name = "tom", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "234A842F3D087D35737483F5DFB6DE9839E3366EF0CAF8726D2D094210227670" }, + { name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" }, +] + +[requirements] +gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } +gleam_time = { version = ">= 1.8.0 and < 2.0.0" } +justin = { version = ">= 1.0.0 and < 2.0.0" } +lustre = { version = ">= 5.6.0 and < 6.0.0" } +lustre_dev_tools = { version = ">= 2.3.6 and < 3.0.0" } +off_topic = { path = ".." } diff --git a/demos/src/component.css b/demos/src/component.css new file mode 100644 index 0000000..154ec62 --- /dev/null +++ b/demos/src/component.css @@ -0,0 +1,20 @@ +@import "tailwindcss"; +@source "./src/**/*.gleam"; + +@theme { + --color-card-bg: var(--background, #ffffff); + --color-card-border: var(--code-border, #ffaff3); + --color-card-text: var(--text, #2a2020); + --color-link: var(--links, #d900b8); +} + +.theme-dark { + --bg: #292d3e; + --taupe-mono-1: #fdf1d4; + + --background: var(--bg); + --code-background: #202431; + --text: var(--taupe-mono-1); + --links: var(--pink, #ffaff3); + --code-border: var(--pink, #ffaff3); +} diff --git a/demos/src/component.gleam b/demos/src/component.gleam new file mode 100644 index 0000000..c79a2ea --- /dev/null +++ b/demos/src/component.gleam @@ -0,0 +1,61 @@ +import demos/after +import demos/breakpoints +import demos/colour_scheme +import demos/contrast +import demos/element_scroll +import demos/element_size +import demos/element_visible +import demos/every +import demos/focus_blur +import demos/here +import demos/keyboard +import demos/language +import demos/local_storage +import demos/now +import demos/on +import demos/on_idle +import demos/online +import demos/page_state +import demos/pointer +import demos/prevent_unload +import demos/reduced_motion +import demos/screen_orientation +import demos/scroll_into_view +import demos/session_storage +import demos/window_hover +import demos/window_scroll +import demos/window_size + +pub fn main() -> Nil { + register() +} + +pub fn register() -> Nil { + keyboard.register() + pointer.register() + window_size.register() + screen_orientation.register() + window_scroll.register() + window_hover.register() + page_state.register() + prevent_unload.register() + element_size.register() + element_scroll.register() + scroll_into_view.register() + element_visible.register() + focus_blur.register() + on.register() + colour_scheme.register() + reduced_motion.register() + contrast.register() + language.register() + breakpoints.register() + local_storage.register() + session_storage.register() + every.register() + after.register() + now.register() + here.register() + on_idle.register() + online.register() +} diff --git a/demos/src/demo.gleam b/demos/src/demo.gleam new file mode 100644 index 0000000..3e573a1 --- /dev/null +++ b/demos/src/demo.gleam @@ -0,0 +1,58 @@ +import gleam/list +import justin +import lustre +import lustre/attribute +import lustre/element.{type Element} +import lustre/element/html + +const base_url = "https://github.com/joshi-monster/off_topic/blob/main/demos/src/demos/" + +pub fn card(module: String, content: Element(msg)) -> Element(msg) { + html.div( + [ + attribute.class( + "rounded overflow-hidden border border-card-border bg-card-bg text-card-text", + ), + ], + [ + html.div( + [ + attribute.class( + "max-h-32 flex items-center justify-center p-6 overflow-hidden", + ), + ], + [content], + ), + html.div( + [ + attribute.class( + "border-t border-card-border px-4 py-1.5 flex justify-between", + ), + ], + [ + html.a( + [ + attribute.href(base_url <> module <> ".gleam"), + attribute.target("_blank"), + attribute.rel("noopener noreferrer"), + attribute.class("text-xs font-mono text-link hover:underline"), + ], + [html.text("source")], + ), + html.a( + [ + attribute.href("#"), + attribute.class("text-xs font-mono text-link hover:underline"), + ], + [html.text("↑ top")], + ), + ], + ), + ], + ) +} + +pub fn register(app: lustre.App(Nil, model, msg), names: List(String)) -> Nil { + use name <- list.each(names) + let assert Ok(_) = lustre.register(app, "ot-demo-" <> justin.kebab_case(name)) +} diff --git a/demos/src/demos.css b/demos/src/demos.css new file mode 100644 index 0000000..154ec62 --- /dev/null +++ b/demos/src/demos.css @@ -0,0 +1,20 @@ +@import "tailwindcss"; +@source "./src/**/*.gleam"; + +@theme { + --color-card-bg: var(--background, #ffffff); + --color-card-border: var(--code-border, #ffaff3); + --color-card-text: var(--text, #2a2020); + --color-link: var(--links, #d900b8); +} + +.theme-dark { + --bg: #292d3e; + --taupe-mono-1: #fdf1d4; + + --background: var(--bg); + --code-background: #202431; + --text: var(--taupe-mono-1); + --links: var(--pink, #ffaff3); + --code-border: var(--pink, #ffaff3); +} diff --git a/demos/src/demos.ffi.mjs b/demos/src/demos.ffi.mjs new file mode 100644 index 0000000..ddd65a1 --- /dev/null +++ b/demos/src/demos.ffi.mjs @@ -0,0 +1,3 @@ +export function set_theme(dark) { + document.documentElement.classList.toggle("theme-dark", dark) +} diff --git a/demos/src/demos.gleam b/demos/src/demos.gleam new file mode 100644 index 0000000..6e9123d --- /dev/null +++ b/demos/src/demos.gleam @@ -0,0 +1,202 @@ +import component +import gleam/list +import gleam/option.{type Option, Some} +import justin +import lustre +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +const demos = [ + "on_key_press", + "on_key_down", + "on_key_up", + "on_click", + "on_pointer_move", + "on_pointer_down", + "on_pointer_up", + "on_pointer_cancel", + "on_wheel", + "window_size", + "screen_orientation", + "window_scroll", + "scroll_to", + "window_hover", + "page_state", + "prevent_unload", + "element_size", + "element_scroll", + "element_scroll_to", + "scroll_into_view", + "element_visible", + "focus", + "blur", + "on", + "colour_scheme", + "reduced_motion", + "contrast", + "language", + "breakpoints", + "tailwind_breakpoints", + "local_storage", + "set_local_storage", + "session_storage", + "set_session_storage", + "every", + "after", + "now", + "here", + "on_idle", + "online", +] + +const selected_key = "ot-demos-selected" + +const theme_key = "ot-demos-theme" + +type Theme { + Light + Dark +} + +type Model { + Model(selected: String, theme: Theme) +} + +type Msg { + DemoSelected(String) + ToggleTheme + GotSelected(Option(String)) + GotTheme(Option(String)) +} + +pub fn main() -> Nil { + component.register() + + let app = off_topic.application(init:, update:, subscriptions:, view:) + let assert Ok(_) = lustre.start(app, "#app", Nil) + + Nil +} + +fn init(_) -> #(Model, Effect(Msg)) { + #( + Model( + selected: case list.first(demos) { + Ok(name) -> name + Error(_) -> "" + }, + theme: Light, + ), + effect.none(), + ) +} + +fn subscriptions(_model: Model) -> Subscription(Msg) { + off_topic.batch([ + off_topic.local_storage(key: selected_key, send: GotSelected), + off_topic.local_storage(key: theme_key, send: GotTheme), + ]) +} + +fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { + case msg { + DemoSelected(name) -> #( + Model(..model, selected: name), + off_topic.set_local_storage(key: selected_key, value: Some(name)), + ) + + ToggleTheme -> { + let theme = case model.theme { + Light -> Dark + Dark -> Light + } + #( + Model(..model, theme:), + effect.batch([ + apply_theme(theme), + off_topic.set_local_storage( + key: theme_key, + value: Some(theme_string(theme)), + ), + ]), + ) + } + + GotSelected(value) -> { + let selected = option.unwrap(value, model.selected) + #(Model(..model, selected:), effect.none()) + } + + GotTheme(value) -> { + let theme = case value { + Some("dark") -> Dark + _ -> Light + } + #(Model(..model, theme:), apply_theme(theme)) + } + } +} + +fn theme_string(theme: Theme) -> String { + case theme { + Dark -> "dark" + Light -> "light" + } +} + +fn apply_theme(theme: Theme) -> Effect(Msg) { + effect.from(fn(_) { do_set_html_theme(theme == Dark) }) +} + +@external(javascript, "./demos.ffi.mjs", "set_theme") +fn do_set_html_theme(_dark: Bool) -> Nil { + Nil +} + +fn view(model: Model) -> Element(Msg) { + html.div([attribute.class("min-h-screen bg-card-bg text-card-text")], [ + html.div( + [attribute.class("max-w-2xl mx-auto py-12 px-6 flex flex-col gap-4")], + [ + html.div([attribute.class("flex justify-end")], [ + html.button( + [ + event.on_click(ToggleTheme), + attribute.class( + "text-xs font-mono border border-card-border rounded px-2 py-1 bg-card-bg text-card-text cursor-pointer", + ), + ], + [ + html.text(case model.theme { + Light -> "dark" + Dark -> "light" + }), + ], + ), + ]), + html.select( + [ + event.on_change(DemoSelected), + attribute.class( + "self-start text-sm font-mono border border-card-border rounded px-2 py-1 bg-card-bg text-card-text", + ), + ], + list.map(demos, fn(name) { + html.option( + [ + attribute.value(name), + attribute.selected(name == model.selected), + ], + name, + ) + }), + ), + element.element("ot-demo-" <> justin.kebab_case(model.selected), [], []), + ], + ), + ]) +} diff --git a/demos/src/demos/after.gleam b/demos/src/demos/after.gleam new file mode 100644 index 0000000..1487558 --- /dev/null +++ b/demos/src/demos/after.gleam @@ -0,0 +1,74 @@ +import demo +import gleam/time/duration +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["after"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Idle + Waiting + Done +} + +type Message { + UserClickedFire + TimeoutElapsed +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Idle, effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserClickedFire -> { + #(Waiting, off_topic.after(duration.seconds(2), TimeoutElapsed)) + } + TimeoutElapsed -> #(Done, effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.none() +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "after", + html.div([attribute.class("flex items-center gap-3 font-mono text-sm")], [ + html.button( + [ + event.on_click(UserClickedFire), + attribute.disabled(model == Waiting), + attribute.class( + "px-3 py-1 border border-card-border rounded cursor-pointer disabled:opacity-40", + ), + ], + [ + case model { + Done -> html.text("again") + _ -> html.text("fire") + }, + ], + ), + html.span([attribute.class("opacity-60")], [ + case model { + Idle -> element.none() + Waiting -> html.text("waiting 2s…") + Done -> html.text("fired!") + }, + ]), + ]), + ) +} diff --git a/demos/src/demos/breakpoints.gleam b/demos/src/demos/breakpoints.gleam new file mode 100644 index 0000000..8cb7238 --- /dev/null +++ b/demos/src/demos/breakpoints.gleam @@ -0,0 +1,59 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type Subscription, type TailwindBreakpoint, Lg, Md, Sm, Xl, Xs, Xxl, +} + +pub fn register() -> Nil { + demo.register(component(), ["breakpoints", "tailwind_breakpoints"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(breakpoint: TailwindBreakpoint) +} + +type Message { + BreakpointChanged(TailwindBreakpoint) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(breakpoint: Xs), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + BreakpointChanged(breakpoint) -> #(Model(breakpoint:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.tailwind_breakpoints(BreakpointChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.breakpoint { + Xs -> "xs" + Sm -> "sm" + Md -> "md" + Lg -> "lg" + Xl -> "xl" + Xxl -> "2xl" + } + demo.card( + "breakpoints", + html.span([attribute.class("font-mono text-sm")], [ + html.text("Current breakpoint: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/colour_scheme.gleam b/demos/src/demos/colour_scheme.gleam new file mode 100644 index 0000000..8c24e6b --- /dev/null +++ b/demos/src/demos/colour_scheme.gleam @@ -0,0 +1,53 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type ColourScheme, type Subscription, Dark, Light} + +pub fn register() -> Nil { + demo.register(component(), ["colour_scheme"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(scheme: ColourScheme) +} + +type Message { + ColourSchemeChanged(ColourScheme) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(scheme: Light), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + ColourSchemeChanged(scheme) -> #(Model(scheme:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.colour_scheme(ColourSchemeChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.scheme { + Dark -> "dark" + Light -> "light" + } + demo.card( + "colour_scheme", + html.span([attribute.class("font-mono text-sm")], [ + html.text("colour scheme: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/contrast.gleam b/demos/src/demos/contrast.gleam new file mode 100644 index 0000000..3d4833c --- /dev/null +++ b/demos/src/demos/contrast.gleam @@ -0,0 +1,58 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type ContrastPreference, type Subscription, ForcedContrast, LessContrast, + MoreContrast, NoContrastPreference, +} + +pub fn register() -> Nil { + demo.register(component(), ["contrast"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(pref: ContrastPreference) +} + +type Message { + ContrastChanged(ContrastPreference) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(pref: NoContrastPreference), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + ContrastChanged(pref) -> #(Model(pref:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.contrast(ContrastChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.pref { + MoreContrast -> "more" + LessContrast -> "less" + ForcedContrast -> "forced" + NoContrastPreference -> "no-preference" + } + demo.card( + "contrast", + html.span([attribute.class("font-mono text-sm")], [ + html.text("contrast: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/element_scroll.gleam b/demos/src/demos/element_scroll.gleam new file mode 100644 index 0000000..599a3db --- /dev/null +++ b/demos/src/demos/element_scroll.gleam @@ -0,0 +1,100 @@ +import demo +import gleam/int +import gleam/list +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["element_scroll", "element_scroll_to"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +const container = "#scroll-box" + +type Model { + Model(x: Int, y: Int) +} + +type Message { + ContainerScrolled(Int, Int) + UserClickedScrollToTop + UserClickedScrollToBottom +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(x: 0, y: 0), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + ContainerScrolled(x, y) -> #(Model(x:, y:), effect.none()) + UserClickedScrollToTop -> #( + model, + off_topic.element_scroll_to( + selector: container, + x: 0, + y: 0, + behaviour: off_topic.Smooth, + ), + ) + UserClickedScrollToBottom -> #( + model, + off_topic.element_scroll_to( + selector: container, + x: 0, + y: 999, + behaviour: off_topic.Smooth, + ), + ) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.element_scroll(selector: container, send: ContainerScrolled) +} + +fn view(model: Model) -> Element(Message) { + let btn_class = + "text-xs px-2 py-1 border border-card-border rounded cursor-pointer" + demo.card( + "element_scroll", + html.div([attribute.class("flex items-center gap-3 font-mono text-sm")], [ + html.div([attribute.class("flex flex-col justify-between h-16")], [ + html.button( + [event.on_click(UserClickedScrollToTop), attribute.class(btn_class)], + [html.text("↑ top")], + ), + html.button( + [ + event.on_click(UserClickedScrollToBottom), + attribute.class(btn_class), + ], + [html.text("↓ bottom")], + ), + ]), + html.div( + [ + attribute.id("scroll-box"), + attribute.class( + "overflow-y-auto h-16 w-24 border border-card-border rounded shrink-0", + ), + ], + list.map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], fn(i) { + html.div([attribute.class("px-2 py-0.5 text-xs")], [ + html.text("item " <> int.to_string(i)), + ]) + }), + ), + html.span([attribute.class("opacity-40 text-xs")], [ + html.text("y: " <> int.to_string(model.y)), + ]), + ]), + ) +} diff --git a/demos/src/demos/element_size.gleam b/demos/src/demos/element_size.gleam new file mode 100644 index 0000000..6d26bc9 --- /dev/null +++ b/demos/src/demos/element_size.gleam @@ -0,0 +1,62 @@ +import demo +import gleam/int +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["element_size"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(w: Int, h: Int) +} + +type Message { + WindowSizeChanged(Int, Int) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(w: 0, h: 0), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + WindowSizeChanged(w, h) -> #(Model(w:, h:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.element_size(selector: "#size-target", send: WindowSizeChanged) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "element_size", + html.div( + [ + attribute.id("size-target"), + attribute.styles([ + #("resize", "both"), + #("overflow", "hidden"), + #("min-width", "80px"), + #("min-height", "48px"), + #("width", "120px"), + #("height", "64px"), + ]), + attribute.class( + "flex items-center justify-center border border-dashed border-card-border rounded font-mono text-sm", + ), + ], + [ + html.text(int.to_string(model.w) <> " × " <> int.to_string(model.h)), + ], + ), + ) +} diff --git a/demos/src/demos/element_visible.gleam b/demos/src/demos/element_visible.gleam new file mode 100644 index 0000000..68a0ea1 --- /dev/null +++ b/demos/src/demos/element_visible.gleam @@ -0,0 +1,97 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription, Instant} +import off_topic/component + +pub fn register() -> Nil { + demo.register(component(), ["element_visible"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: [ + component.on_connect(ComponentConnected), + ]) +} + +type Model { + Model(visible: Bool) +} + +type Message { + SquareVisibilityChanged(Bool) + ComponentConnected +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(visible: True), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + SquareVisibilityChanged(visible) -> #(Model(visible:), effect.none()) + ComponentConnected -> #( + Model(visible: True), + off_topic.element_scroll_to( + selector: "#vis-scroll", + x: 112, + y: 40, + behaviour: Instant, + ), + ) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.element_visible( + selector: "#vis-target", + send: SquareVisibilityChanged, + ) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "element_visible", + html.div([attribute.class("flex items-center gap-4 font-mono text-sm")], [ + html.div( + [ + attribute.id("vis-scroll"), + attribute.class( + "overflow-auto h-20 w-40 border border-card-border rounded shrink-0", + ), + ], + [ + html.div( + [attribute.class("w-96 h-40 flex items-center justify-center")], + [ + html.div( + [ + attribute.id("vis-target"), + attribute.class("w-8 h-8 rounded bg-card-border"), + ], + [], + ), + ], + ), + ], + ), + html.span( + [ + attribute.class("w-20 text-center"), + case model.visible { + True -> attribute.none() + False -> attribute.class("opacity-40") + }, + ], + [ + case model.visible { + True -> html.text("visible") + False -> html.text("not visible") + }, + ], + ), + ]), + ) +} diff --git a/demos/src/demos/every.gleam b/demos/src/demos/every.gleam new file mode 100644 index 0000000..87cd2f4 --- /dev/null +++ b/demos/src/demos/every.gleam @@ -0,0 +1,54 @@ +import demo +import gleam/float +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/time/duration +import gleam/time/timestamp.{type Timestamp} +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["every"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(ts: Option(Timestamp)) +} + +type Message { + Tick(Timestamp) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(ts: None), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + Tick(ts) -> #(Model(ts: Some(ts)), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.every(every: duration.seconds(1), immediate: True, on_elapsed: Tick) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.ts { + None -> "—" + Some(ts) -> int.to_string(float.round(timestamp.to_unix_seconds(ts))) + } + demo.card( + "every", + html.span([attribute.class("font-mono text-3xl font-light tabular-nums")], [ + html.text(label), + ]), + ) +} diff --git a/demos/src/demos/focus_blur.gleam b/demos/src/demos/focus_blur.gleam new file mode 100644 index 0000000..18f4df8 --- /dev/null +++ b/demos/src/demos/focus_blur.gleam @@ -0,0 +1,85 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["focus", "blur"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model +} + +type Message { + UserClickedFocusA + UserClickedFocusB + UserClickedBlur +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model, effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserClickedFocusA -> #(model, off_topic.focus("#input-a")) + UserClickedFocusB -> #(model, off_topic.focus("#input-b")) + UserClickedBlur -> #(model, off_topic.blur()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.none() +} + +fn view(_model: Model) -> Element(Message) { + let input_class = + "border border-card-border rounded px-2 py-1 text-xs bg-card-bg w-24" + let btn_class = + "text-xs px-2 py-1 border border-card-border rounded cursor-pointer" + demo.card( + "focus_blur", + html.div([attribute.class("flex flex-col gap-2 font-mono text-sm")], [ + html.div([attribute.class("flex items-center gap-2")], [ + html.input([ + attribute.id("input-a"), + attribute.placeholder("input a"), + attribute.class(input_class), + ]), + html.button( + [event.on_click(UserClickedFocusA), attribute.class(btn_class)], + [ + html.text("focus"), + ], + ), + ]), + html.div([attribute.class("flex items-center gap-2")], [ + html.input([ + attribute.id("input-b"), + attribute.placeholder("input b"), + attribute.class(input_class), + ]), + html.button( + [event.on_click(UserClickedFocusB), attribute.class(btn_class)], + [ + html.text("focus"), + ], + ), + html.button( + [event.on_click(UserClickedBlur), attribute.class(btn_class)], + [ + html.text("blur"), + ], + ), + ]), + ]), + ) +} diff --git a/demos/src/demos/here.gleam b/demos/src/demos/here.gleam new file mode 100644 index 0000000..721ed5f --- /dev/null +++ b/demos/src/demos/here.gleam @@ -0,0 +1,72 @@ +import demo +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/time/duration.{type Duration} +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["here"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(offset: Option(Duration)) +} + +type Message { + TimezoneOffsetChanged(Duration) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(offset: None), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + TimezoneOffsetChanged(offset) -> { + #(Model(offset: Some(offset)), effect.none()) + } + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.here(TimezoneOffsetChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.offset { + None -> "—" + Some(offset) -> { + duration_to_timezone_string(offset) + } + } + demo.card( + "here", + html.span([attribute.class("font-mono text-2xl font-light")], [ + html.text(label), + ]), + ) +} + +fn duration_to_timezone_string(offset: Duration) -> String { + let total_mins = duration.to_milliseconds(offset) / 60_000 + let sign = case total_mins >= 0 { + True -> "+" + False -> "-" + } + let abs_mins = int.absolute_value(total_mins) + let h = abs_mins / 60 + let m = abs_mins % 60 + let m_str = case m < 10 { + True -> "0" <> int.to_string(m) + False -> int.to_string(m) + } + "UTC" <> sign <> int.to_string(h) <> ":" <> m_str +} diff --git a/demos/src/demos/keyboard.gleam b/demos/src/demos/keyboard.gleam new file mode 100644 index 0000000..ca96c08 --- /dev/null +++ b/demos/src/demos/keyboard.gleam @@ -0,0 +1,89 @@ +import demo +import gleam/list +import gleam/string +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Key, type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["on_key_press", "on_key_down", "on_key_up"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type LastKey { + NoKey + Key(event_type: String, key: Key) +} + +type Model { + Model(last: LastKey) +} + +type Message { + UserPressedKey(String, Key) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(last: NoKey), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserPressedKey(event_type, key) -> #( + Model(last: Key(event_type:, key:)), + effect.none(), + ) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.batch([ + off_topic.on_key_press(fn(k) { UserPressedKey("press", k) }), + off_topic.on_key_down(fn(k) { UserPressedKey("down", k) }), + off_topic.on_key_up(fn(k) { UserPressedKey("up", k) }), + ]) +} + +fn view(model: Model) -> Element(Message) { + let content = case model.last { + NoKey -> + html.span([attribute.class("opacity-40")], [html.text("press any key")]) + + Key(event_type:, key:) -> { + let label = + [ + iff(key.ctrl, "ctrl", ""), + iff(key.alt, "alt", ""), + iff(key.meta, "meta", ""), + iff(key.shift, "shift", ""), + key.key, + ] + |> list.filter(fn(s) { s != "" }) + |> string.join("+") + + html.div([attribute.class("flex items-center gap-3")], [ + html.span([attribute.class("opacity-40 text-xs")], [ + html.text(event_type), + ]), + html.span([], [html.text(label)]), + ]) + } + } + + demo.card( + "keyboard", + html.div([attribute.class("font-mono text-sm")], [content]), + ) +} + +fn iff(when: Bool, yes: a, no: a) -> a { + case when { + True -> yes + False -> no + } +} diff --git a/demos/src/demos/language.gleam b/demos/src/demos/language.gleam new file mode 100644 index 0000000..0bce87e --- /dev/null +++ b/demos/src/demos/language.gleam @@ -0,0 +1,49 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["language"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(lang: String) +} + +type Message { + PreferredLanguageChanged(String) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(lang: ""), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + PreferredLanguageChanged(lang) -> #(Model(lang:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.language(PreferredLanguageChanged) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "language", + html.span([attribute.class("font-mono text-sm")], [ + html.text("preferred language: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(model.lang)], + ), + ]), + ) +} diff --git a/demos/src/demos/local_storage.gleam b/demos/src/demos/local_storage.gleam new file mode 100644 index 0000000..413763a --- /dev/null +++ b/demos/src/demos/local_storage.gleam @@ -0,0 +1,86 @@ +import demo +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/result +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["local_storage", "set_local_storage"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +const key = "ot-demo-ls-count" + +type Model { + Model(count: Int) +} + +type Message { + StoredValueChanged(Option(String)) + UserClickedIncrement + UserClickedDecrement +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(count: 0), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + StoredValueChanged(v) -> { + let count = case v { + None -> 0 + Some(s) -> result.unwrap(int.parse(s), 0) + } + #(Model(count:), effect.none()) + } + + UserClickedIncrement -> { + let count = model.count + 1 + #( + Model(count:), + off_topic.set_local_storage(key:, value: Some(int.to_string(count))), + ) + } + + UserClickedDecrement -> { + let count = model.count - 1 + #( + Model(count:), + off_topic.set_local_storage(key:, value: Some(int.to_string(count))), + ) + } + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.local_storage(key:, send: StoredValueChanged) +} + +fn view(model: Model) -> Element(Message) { + let btn = + attribute.class( + "w-8 h-8 border border-card-border rounded font-mono cursor-pointer flex items-center justify-center", + ) + demo.card( + "local_storage", + html.div([attribute.class("flex items-center gap-4 font-mono")], [ + html.button([event.on_click(UserClickedDecrement), btn], [html.text("−")]), + html.span( + [attribute.class("text-2xl font-light w-12 text-center tabular-nums")], + [ + html.text(int.to_string(model.count)), + ], + ), + html.button([event.on_click(UserClickedIncrement), btn], [html.text("+")]), + ]), + ) +} diff --git a/demos/src/demos/now.gleam b/demos/src/demos/now.gleam new file mode 100644 index 0000000..e9bd4fa --- /dev/null +++ b/demos/src/demos/now.gleam @@ -0,0 +1,67 @@ +import demo +import gleam/float +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/time/timestamp.{type Timestamp} +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["now"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(timestamp: Option(Timestamp)) +} + +type Message { + UserClickedCapture + ReceivedTimestamp(Timestamp) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(timestamp: None), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserClickedCapture -> #(model, off_topic.now(ReceivedTimestamp)) + ReceivedTimestamp(ts) -> #(Model(timestamp: Some(ts)), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.none() +} + +fn view(model: Model) -> Element(Message) { + let label = case model.timestamp { + None -> "—" + Some(ts) -> int.to_string(float.round(timestamp.to_unix_seconds(ts))) + } + demo.card( + "now", + html.div([attribute.class("flex items-center gap-3 font-mono text-sm")], [ + html.button( + [ + event.on_click(UserClickedCapture), + attribute.class( + "px-3 py-1 border border-card-border rounded cursor-pointer", + ), + ], + [html.text("capture")], + ), + html.span([attribute.class("text-xl font-light tabular-nums")], [ + html.text(label), + ]), + ]), + ) +} diff --git a/demos/src/demos/on.gleam b/demos/src/demos/on.gleam new file mode 100644 index 0000000..95cd132 --- /dev/null +++ b/demos/src/demos/on.gleam @@ -0,0 +1,53 @@ +import demo +import gleam/dynamic/decode +import gleam/int +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription, Window} + +pub fn register() -> Nil { + demo.register(component(), ["on"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(x: Int, y: Int) +} + +type Message { + GotClick(Int, Int) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(x: 0, y: 0), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + GotClick(x, y) -> #(Model(x:, y:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.on(on: Window, event: "click", run: { + use x <- decode.field("clientX", decode.int) + use y <- decode.field("clientY", decode.int) + decode.success(GotClick(x, y)) + }) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "on", + html.span([attribute.class("font-mono text-sm opacity-60")], [ + html.text( + "(" <> int.to_string(model.x) <> ", " <> int.to_string(model.y) <> ")", + ), + ]), + ) +} diff --git a/demos/src/demos/on_idle.gleam b/demos/src/demos/on_idle.gleam new file mode 100644 index 0000000..58ab488 --- /dev/null +++ b/demos/src/demos/on_idle.gleam @@ -0,0 +1,55 @@ +import demo +import gleam/time/duration +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["on_idle"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(idle: Bool) +} + +type Message { + IdleStatusChanged(Bool) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(idle: False), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + IdleStatusChanged(idle) -> #(Model(idle:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.on_idle(after: duration.seconds(3), send: IdleStatusChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.idle { + True -> "idle" + False -> "active" + } + demo.card( + "on_idle", + html.span( + [ + attribute.class( + "font-mono text-sm px-3 py-1 border border-card-border rounded", + ), + ], + [html.text(label)], + ), + ) +} diff --git a/demos/src/demos/online.gleam b/demos/src/demos/online.gleam new file mode 100644 index 0000000..c9b4adf --- /dev/null +++ b/demos/src/demos/online.gleam @@ -0,0 +1,55 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["online"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(online: Bool) +} + +type Message { + OnlineStatusChanged(Bool) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(online: True), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + OnlineStatusChanged(online) -> #(Model(online:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.online(OnlineStatusChanged) +} + +fn view(model: Model) -> Element(Message) { + let #(label, colour) = case model.online { + True -> #("online", "text-green-500") + False -> #("offline", "text-red-500") + } + demo.card( + "online", + html.span( + [ + attribute.class( + "font-mono text-sm px-3 py-1 border border-card-border rounded flex items-center gap-2", + ), + attribute.class(colour), + ], + [html.text("● " <> label)], + ), + ) +} diff --git a/demos/src/demos/page_state.gleam b/demos/src/demos/page_state.gleam new file mode 100644 index 0000000..6df3dca --- /dev/null +++ b/demos/src/demos/page_state.gleam @@ -0,0 +1,58 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type PageState, type Subscription, Active, Frozen, Hidden, Passive, Terminated, +} + +pub fn register() -> Nil { + demo.register(component(), ["page_state"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(state: PageState) +} + +type Message { + PageStateChanged(PageState) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(state: Active), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + PageStateChanged(state) -> #(Model(state:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.page_state(PageStateChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.state { + Active -> "active" + Passive -> "passive" + Hidden -> "hidden" + Frozen -> "frozen" + Terminated -> "terminated" + } + demo.card( + "page_state", + html.span([attribute.class("font-mono text-sm")], [ + html.text("page state: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/pointer.gleam b/demos/src/demos/pointer.gleam new file mode 100644 index 0000000..43195c3 --- /dev/null +++ b/demos/src/demos/pointer.gleam @@ -0,0 +1,105 @@ +import demo +import gleam/float +import gleam/int +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type Pointer, type PointerType, type Subscription, type Wheel, Mouse, Pen, + Touch, +} + +pub fn register() -> Nil { + demo.register(component(), [ + "on_click", + "on_pointer_move", + "on_pointer_down", + "on_pointer_up", + "on_pointer_cancel", + "on_wheel", + ]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type LastEvent { + NoEvent + Pointer(event_type: String, pointer: Pointer) + Wheel(wheel: Wheel) +} + +type Model { + Model(last: LastEvent) +} + +type Message { + UserMovedOrClickedPointer(String, Pointer) + UserScrolledWheel(Wheel) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(last: NoEvent), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserMovedOrClickedPointer(event_type, pointer) -> { + #(Model(last: Pointer(event_type:, pointer:)), effect.none()) + } + UserScrolledWheel(wheel) -> #(Model(last: Wheel(wheel:)), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.batch([ + off_topic.on_click(fn(p) { UserMovedOrClickedPointer("click", p) }), + off_topic.on_pointer_move(fn(p) { UserMovedOrClickedPointer("move", p) }), + off_topic.on_pointer_down(fn(p) { UserMovedOrClickedPointer("down", p) }), + off_topic.on_pointer_up(fn(p) { UserMovedOrClickedPointer("up", p) }), + off_topic.on_pointer_cancel(fn(p) { UserMovedOrClickedPointer("cancel", p) }), + off_topic.on_wheel(UserScrolledWheel), + ]) +} + +fn pointer_type_label(pt: PointerType) -> String { + case pt { + Mouse -> "mouse" + Pen -> "pen" + Touch -> "touch" + } +} + +fn view(model: Model) -> Element(Message) { + let content = case model.last { + NoEvent -> + html.span([attribute.class("opacity-40")], [html.text("move or click")]) + + Pointer(event_type:, pointer: off_topic.Pointer(x:, y:, pointer_type:, ..)) -> + html.div([attribute.class("flex items-center gap-3")], [ + html.span([attribute.class("opacity-40 text-xs")], [ + html.text(event_type), + ]), + html.span([], [ + html.text("(" <> int.to_string(x) <> ", " <> int.to_string(y) <> ")"), + ]), + html.span([attribute.class("opacity-40 text-xs")], [ + html.text(pointer_type_label(pointer_type)), + ]), + ]) + + Wheel(wheel: off_topic.Wheel(delta_y:, ..)) -> + html.div([attribute.class("flex items-center gap-3")], [ + html.span([attribute.class("opacity-40 text-xs")], [html.text("wheel")]), + html.span([], [ + html.text("dy=" <> float.to_string(delta_y)), + ]), + ]) + } + demo.card( + "pointer", + html.div([attribute.class("font-mono text-sm")], [content]), + ) +} diff --git a/demos/src/demos/prevent_unload.gleam b/demos/src/demos/prevent_unload.gleam new file mode 100644 index 0000000..07e3e2b --- /dev/null +++ b/demos/src/demos/prevent_unload.gleam @@ -0,0 +1,86 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["prevent_unload"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(text: String, saved: String) +} + +type Message { + UserChangedText(String) + UserClickedSave + UnloadPrevented +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(text: "", saved: ""), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserChangedText(text) -> #(Model(..model, text:), effect.none()) + UserClickedSave -> #(Model(..model, saved: model.text), effect.none()) + UnloadPrevented -> #(model, effect.none()) + } +} + +fn subscriptions(model: Model) -> Subscription(Message) { + off_topic.prevent_unload( + prevent: model.text != model.saved, + send: UnloadPrevented, + ) +} + +fn view(model: Model) -> Element(Message) { + let unsaved = model.text != model.saved + demo.card( + "prevent_unload", + html.div([attribute.class("flex flex-col gap-2 w-full font-mono text-sm")], [ + html.input([ + attribute.value(model.text), + attribute.placeholder("type something…"), + attribute.class( + "border border-card-border rounded px-2 py-1 text-xs bg-card-bg", + ), + event.on_input(UserChangedText), + ]), + html.div([attribute.class("flex items-center gap-2")], [ + html.button( + [ + event.on_click(UserClickedSave), + attribute.class( + "text-xs px-2 py-1 border border-card-border rounded cursor-pointer", + ), + ], + [html.text("save")], + ), + html.span( + [ + attribute.class(case unsaved { + True -> "text-xs text-yellow-500" + False -> "text-xs opacity-40" + }), + ], + [ + case unsaved { + True -> html.text("unsaved changes") + False -> html.text("saved") + }, + ], + ), + ]), + ]), + ) +} diff --git a/demos/src/demos/reduced_motion.gleam b/demos/src/demos/reduced_motion.gleam new file mode 100644 index 0000000..21a406a --- /dev/null +++ b/demos/src/demos/reduced_motion.gleam @@ -0,0 +1,55 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type MotionPreference, type Subscription, NoMotionPreference, ReduceMotion, +} + +pub fn register() -> Nil { + demo.register(component(), ["reduced_motion"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(motion: MotionPreference) +} + +type Message { + MotionPreferenceChanged(MotionPreference) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(motion: NoMotionPreference), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + MotionPreferenceChanged(motion) -> #(Model(motion:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.reduced_motion(MotionPreferenceChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.motion { + ReduceMotion -> "reduce" + NoMotionPreference -> "no-preference" + } + demo.card( + "reduced_motion", + html.span([attribute.class("font-mono text-sm")], [ + html.text("motion: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/screen_orientation.gleam b/demos/src/demos/screen_orientation.gleam new file mode 100644 index 0000000..194c12b --- /dev/null +++ b/demos/src/demos/screen_orientation.gleam @@ -0,0 +1,60 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{ + type ScreenOrientation, type Subscription, LandscapePrimary, + LandscapeSecondary, PortraitPrimary, PortraitSecondary, +} + +pub fn register() -> Nil { + demo.register(component(), ["screen_orientation"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(orientation: ScreenOrientation) +} + +type Message { + ScreenOrientationChanged(ScreenOrientation) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(orientation: PortraitPrimary), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + ScreenOrientationChanged(orientation) -> { + #(Model(orientation:), effect.none()) + } + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.screen_orientation(ScreenOrientationChanged) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.orientation { + PortraitPrimary -> "portrait" + PortraitSecondary -> "portrait (flipped)" + LandscapePrimary -> "landscape" + LandscapeSecondary -> "landscape (flipped)" + } + demo.card( + "screen_orientation", + html.span([attribute.class("font-mono text-sm")], [ + html.text("orientation: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/scroll_into_view.gleam b/demos/src/demos/scroll_into_view.gleam new file mode 100644 index 0000000..bf476a3 --- /dev/null +++ b/demos/src/demos/scroll_into_view.gleam @@ -0,0 +1,74 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{ + type ScrollAlignment, type Subscription, Center, End, Smooth, Start, +} + +pub fn register() -> Nil { + demo.register(component(), ["scroll_into_view"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model +} + +type Message { + UserClickedScrollTo(ScrollAlignment) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model, effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserClickedScrollTo(block) -> #( + model, + off_topic.scroll_into_view( + selector: "host", + block:, + inline: Center, + behaviour: Smooth, + ), + ) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.none() +} + +fn view(_model: Model) -> Element(Message) { + let btn_class = + "text-xs px-2 py-1 border border-card-border rounded cursor-pointer" + demo.card( + "scroll_into_view", + html.div([attribute.class("flex items-center gap-2 font-mono text-sm")], [ + html.button( + [event.on_click(UserClickedScrollTo(Start)), attribute.class(btn_class)], + [html.text("↑ start")], + ), + html.button( + [ + event.on_click(UserClickedScrollTo(Center)), + attribute.class(btn_class), + ], + [html.text("⊞ center")], + ), + html.button( + [event.on_click(UserClickedScrollTo(End)), attribute.class(btn_class)], + [ + html.text("↓ end"), + ], + ), + ]), + ) +} diff --git a/demos/src/demos/session_storage.gleam b/demos/src/demos/session_storage.gleam new file mode 100644 index 0000000..379f75b --- /dev/null +++ b/demos/src/demos/session_storage.gleam @@ -0,0 +1,86 @@ +import demo +import gleam/int +import gleam/option.{type Option, None, Some} +import gleam/result +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["session_storage", "set_session_storage"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +const key = "ot-demo-ss-count" + +type Model { + Model(count: Int) +} + +type Message { + StoredValueChanged(Option(String)) + UserClickedIncrement + UserClickedDecrement +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(count: 0), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + StoredValueChanged(v) -> { + let count = case v { + None -> 0 + Some(s) -> result.unwrap(int.parse(s), 0) + } + #(Model(count:), effect.none()) + } + + UserClickedIncrement -> { + let count = model.count + 1 + #( + Model(count:), + off_topic.set_session_storage(key:, value: Some(int.to_string(count))), + ) + } + + UserClickedDecrement -> { + let count = model.count - 1 + #( + Model(count:), + off_topic.set_session_storage(key:, value: Some(int.to_string(count))), + ) + } + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.session_storage(key:, send: StoredValueChanged) +} + +fn view(model: Model) -> Element(Message) { + let btn = + attribute.class( + "w-8 h-8 border border-card-border rounded font-mono cursor-pointer flex items-center justify-center", + ) + demo.card( + "session_storage", + html.div([attribute.class("flex items-center gap-4 font-mono")], [ + html.button([event.on_click(UserClickedDecrement), btn], [html.text("−")]), + html.span( + [ + attribute.class("text-2xl font-light w-12 text-center tabular-nums"), + ], + [html.text(int.to_string(model.count))], + ), + html.button([event.on_click(UserClickedIncrement), btn], [html.text("+")]), + ]), + ) +} diff --git a/demos/src/demos/window_hover.gleam b/demos/src/demos/window_hover.gleam new file mode 100644 index 0000000..d867157 --- /dev/null +++ b/demos/src/demos/window_hover.gleam @@ -0,0 +1,53 @@ +import demo +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["window_hover"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(hovering: Bool) +} + +type Message { + UserHoveredWindow(Bool) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(hovering: False), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserHoveredWindow(hovering) -> #(Model(hovering:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.window_hover(UserHoveredWindow) +} + +fn view(model: Model) -> Element(Message) { + let label = case model.hovering { + True -> "hovering" + False -> "not hovering" + } + demo.card( + "window_hover", + html.span([attribute.class("font-mono text-sm")], [ + html.text("hover: "), + html.span( + [attribute.class("px-3 py-1 border border-card-border rounded")], + [html.text(label)], + ), + ]), + ) +} diff --git a/demos/src/demos/window_scroll.gleam b/demos/src/demos/window_scroll.gleam new file mode 100644 index 0000000..b9c7171 --- /dev/null +++ b/demos/src/demos/window_scroll.gleam @@ -0,0 +1,63 @@ +import demo +import gleam/int +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import off_topic.{type Subscription, Smooth} + +pub fn register() -> Nil { + demo.register(component(), ["window_scroll", "scroll_to"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(x: Int, y: Int) +} + +type Message { + UserScrolledWindow(Int, Int) + UserClickedScrollDown +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(x: 0, y: 0), effect.none()) +} + +fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + UserScrolledWindow(x, y) -> #(Model(x:, y:), effect.none()) + UserClickedScrollDown -> #( + model, + off_topic.scroll_to(x: model.x, y: model.y + 100, behaviour: Smooth), + ) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.window_scroll(UserScrolledWindow) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "window_scroll", + html.div([attribute.class("flex items-center gap-4 font-mono text-sm")], [ + html.span([attribute.class("tabular-nums opacity-60")], [ + html.text(int.to_string(model.x) <> ", " <> int.to_string(model.y)), + ]), + html.button( + [ + event.on_click(UserClickedScrollDown), + attribute.class( + "text-xs px-2 py-1 border border-card-border rounded cursor-pointer", + ), + ], + [html.text("↓ +100px")], + ), + ]), + ) +} diff --git a/demos/src/demos/window_size.gleam b/demos/src/demos/window_size.gleam new file mode 100644 index 0000000..75c31e3 --- /dev/null +++ b/demos/src/demos/window_size.gleam @@ -0,0 +1,46 @@ +import demo +import gleam/int +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import off_topic.{type Subscription} + +pub fn register() -> Nil { + demo.register(component(), ["window_size"]) +} + +fn component() { + off_topic.component(init:, update:, subscriptions:, view:, options: []) +} + +type Model { + Model(w: Int, h: Int) +} + +type Message { + WindowSizeChanged(Int, Int) +} + +fn init(_) -> #(Model, Effect(Message)) { + #(Model(w: 0, h: 0), effect.none()) +} + +fn update(_model: Model, msg: Message) -> #(Model, Effect(Message)) { + case msg { + WindowSizeChanged(w, h) -> #(Model(w:, h:), effect.none()) + } +} + +fn subscriptions(_model: Model) -> Subscription(Message) { + off_topic.window_size(WindowSizeChanged) +} + +fn view(model: Model) -> Element(Message) { + demo.card( + "window_size", + html.span([attribute.class("font-mono text-3xl font-light")], [ + html.text(int.to_string(model.w) <> " × " <> int.to_string(model.h)), + ]), + ) +} diff --git a/dev/build.gleam b/dev/build.gleam new file mode 100644 index 0000000..0bdca11 --- /dev/null +++ b/dev/build.gleam @@ -0,0 +1,69 @@ +import esgleam +import esgleam/mod/install +import gleam/regexp +import gleam/result +import gleam/string +import simplifile + +pub fn main() { + case simplifile.is_file("build/dev/bin/package/bin/esbuild") { + Ok(True) -> Nil + _ -> install.fetch() + } + + let assert Ok(_) = bundle(minify: False) + let assert Ok(_) = bundle(minify: True) + let assert Ok(_) = inject_script() + + Nil +} + +fn bundle(minify minify) { + let filename = case minify { + True -> "off-topic.min.mjs" + False -> "off-topic.mjs" + } + + let path = "./priv/static/" <> filename + + // For whatever reason, esgleam needs the input path to be relative to the location + // of the esbuild binary + let entry = "./off_topic/internal/component.mjs" + + esgleam.new("") + |> esgleam.entry(entry) + |> esgleam.raw("--outfile=" <> path) + |> esgleam.minify(minify) + |> esgleam.bundle() +} + +fn inject_script() { + let script_path = "./priv/static/off-topic.min.mjs" + use script <- result.try(simplifile.read(script_path)) + + let module_path = "./src/off_topic.gleam" + use module <- result.try(simplifile.read(module_path)) + + let script = + script + |> string.trim + |> string.replace("\n", "\\n") + |> string.replace("\\", "\\\\") + |> string.replace("\"", "\\\"") + |> string.trim + + let inject_regexp = "// <>\\n .+," + let options = regexp.Options(case_insensitive: False, multi_line: True) + let assert Ok(re) = regexp.compile(inject_regexp, options) + + let assert [before, after] = regexp.split(re, module) + + simplifile.write( + to: module_path, + contents: before + <> "// <>\n \"" + <> script + <> "\"," + <> after, + ) +} diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..4012fd3 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,38 @@ +name = "off_topic" +version = "1.0.0" +target = "javascript" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "", repo = "" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[documentation] +pages = [ + { title = "Quickstart", path = "guide/quickstart.html", source = "./guides/quickstart.md" }, + # { title = "Subscriptions from scratch", path = "guide/01-subscriptions-from-scratch.html", source = "./guides/01-subscriptions-from-scratch.md" }, + # { title = "Listening for events", path = "guide/02-listening-for-events.html", source = "./guides/02-listening-for-events.md" }, + # { title = "Commands", path = "guide/03-commands.html", source = "./guides/03-commands.md" }, + # { title = "Extending the ot-client runtime", path = "guide/04-extending-the-runtime.html", source = "./guides/04-extending-the-runtime.md" }, + # { title = "", path = "js/demo.js", source = "demos/dist/component.js" }, + # { title = "", path = "css/demo.css", source = "demos/dist/component.css" } +] + +[dependencies] +gleam_stdlib = ">= 0.70.0 and < 2.0.0" +lustre = ">= 5.6.0 and < 6.0.0" +gleam_time = ">= 1.8.0 and < 2.0.0" +gleam_json = ">= 3.1.0 and < 4.0.0" + +[dev_dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" +esgleam = ">= 1.0.0 and < 2.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" +simplifile = ">= 2.4.0 and < 3.0.0" +child_process = ">= 2.1.1 and < 3.0.0" diff --git a/guides/quickstart.md b/guides/quickstart.md new file mode 100644 index 0000000..8609236 --- /dev/null +++ b/guides/quickstart.md @@ -0,0 +1,189 @@ +# Quickstart + +off_topic adds subscriptions to Lustre — a way to stay in sync with the browser +while your application is running. You can listen for keyboard events, track whether +the tab is visible, react to user preference changes, and more, all without writing +any FFI. + +## Installation + +Add off_topic to your Gleam project: + +```sh +gleam add off_topic +``` + +off_topic targets JavaScript, so make sure your `gleam.toml` has `target = +"javascript"`: + +```diff + name = "my_app" + version = "1.0.0" ++ target = "javascript" +``` + +## Wiring it up + +The entry point is `off_topic.application` — a drop-in replacement for +`lustre.application` that adds one extra callback: `subscriptions`. + +```gleam +import lustre +import off_topic + +pub fn main() { + let app = off_topic.application(init:, update:, subscriptions:, view:) + let assert Ok(_) = lustre.start(app, "#app", Nil) + Nil +} +``` + +The `subscriptions` callback receives your current model and returns a +`Subscription(message)` — the set of browser events the runtime should currently +be listening to. Everything else — `init`, `update`, `view` — works exactly as it +does in a plain Lustre application. + +The simplest subscriptions callback returns nothing: + +```gleam +fn subscriptions(_model: Model) -> off_topic.Subscription(Msg) { + off_topic.none() +} +``` + +off_topic calls `subscriptions` after every `update`. When the result changes, +it stops the subscriptions that disappeared and starts the new ones. You never +manage listener lifecycles by hand. + +## Your first subscription + +Let's build a counter that ticks once per second and pauses when the browser tab +loses focus. That takes two subscriptions: a repeating timer and the page lifecycle +state. + +First, the model and messages: + +```gleam +import gleam/time/duration +import gleam/time/timestamp.{type Timestamp} +import lustre/effect.{type Effect} +import off_topic.{type PageState, type Subscription} + +type Model { + Model(count: Int, page_state: PageState) +} + +type Msg { + Ticked(Timestamp) + PageStateChanged(PageState) +} + +fn init(_flags) -> #(Model, Effect(Msg)) { + #(Model(count: 0, page_state: off_topic.Active), effect.none()) +} + +fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { + case msg { + Ticked(_) -> #(Model(..model, count: model.count + 1), effect.none()) + PageStateChanged(state) -> #(Model(..model, page_state: state), effect.none()) + } +} +``` + +Now the subscriptions function: + +```gleam +fn subscriptions(model: Model) -> Subscription(Msg) { + let timer = case model.page_state { + off_topic.Active -> + off_topic.every(every: duration.seconds(1), on_elapsed: Ticked) + _ -> off_topic.none() + } + + off_topic.batch([ + off_topic.page_state(PageStateChanged), + timer, + ]) +} +``` + +`off_topic.batch` combines a list of subscriptions into one. + +`off_topic.page_state` is an *observable* subscription: it dispatches the current +page state immediately when it starts, then again whenever the state changes. + +`off_topic.every` is an *event* subscription: it fires only on each timer tick, +never immediately on start. + +Because `subscriptions` is called after every `update`, the timer starts and stops +automatically as `model.page_state` changes: when the tab is hidden the next call +returns `none()` for the timer and off_topic stops it; when the tab becomes active +again, the timer starts afresh. + +## Observable values and transient events + +off_topic subscriptions come in two flavours, and their names reflect the +difference. + +**Observable values** have a "current state" the browser holds persistently — the +window size, whether the device prefers dark mode, whether the page is visible. +Their subscriptions dispatch the current value immediately on start, then again on +each change. Their names are the value itself: + +```gleam +off_topic.window_size(GotWindowSize) // dispatches immediately + on resize +off_topic.color_scheme(GotColorScheme) // dispatches immediately + on change +off_topic.online(GotOnlineStatus) // dispatches immediately + on change +``` + +**Transient events** happen at a point in time. Their subscriptions only fire when +the event occurs. Their names start with `on_`: + +```gleam +off_topic.on_click(UserClicked) +off_topic.on_key_down(UserPressedKey) +off_topic.on_pointer_move(MouseMoved) +``` + +## Slowing things down + +Some events fire very rapidly. Pointer-move events, for example, can arrive +hundreds of times per second. off_topic lets you rate-limit any subscription with +`throttle` or `delay`: + +```gleam +off_topic.on_pointer_move(MouseMoved) +|> off_topic.throttle(wait: duration.milliseconds(50)) +``` + +`throttle` passes the first event through immediately, then drops the rest until +`wait` has elapsed. `delay` holds the last event in a burst and dispatches it only +once the subscription has been quiet for `wait` — useful for search-as-you-type: + +```gleam +off_topic.on_key_up(SearchQueryChanged) +|> off_topic.delay(wait: duration.milliseconds(300)) +``` + +You can combine both: `throttle` handles the running stream while `delay` catches +the final value after the burst ends. + +## Where next + +The [API reference](https://hexdocs.pm/off_topic/off_topic.html) lists every +function with its type signature and a short description. The guides cover the +topics this one skips: + +- [Subscriptions from scratch](./01-subscriptions-from-scratch.html) — build + your own subscriptions with `from`, `element`, and `resource`. Understand how + dependencies control restart behaviour and how the server-component path works. + +- [Listening for events](./02-listening-for-events.html) — the full set of built-in + `on_*` event subscriptions and when to reach for `on(target, event, decoder)` + directly to handle events off_topic doesn't cover out of the box. + +- [Commands](./03-commands.html) — the built-in commands for storage, scroll, + and focus, how they work in server-component apps, and how to write your own. + +- [Extending the ot-client runtime](./04-extending-the-runtime.html) — how to + register custom subscription and command handlers in `window.OffTopic`. diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..b6d10ea --- /dev/null +++ b/manifest.toml @@ -0,0 +1,30 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "child_process", version = "2.1.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "splitter"], otp_app = "child_process", source = "hex", outer_checksum = "ECCE26DB0E5566C649CA52D3666E3E3FA658E265A138B945AFE69CF061DBA069" }, + { name = "esgleam", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "simplifile"], otp_app = "esgleam", source = "hex", outer_checksum = "06341DC528DCA3DAF1BC92D03BBD6A97A6105B012AC064798CE04A108C2F11A4" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" }, + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, + { name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" }, +] + +[requirements] +child_process = { version = ">= 2.1.1 and < 3.0.0" } +esgleam = { version = ">= 1.0.0 and < 2.0.0" } +gleam_json = { version = ">= 3.1.0 and < 4.0.0" } +gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.70.0 and < 2.0.0" } +gleam_time = { version = ">= 1.8.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +lustre = { version = ">= 5.6.0 and < 6.0.0" } +simplifile = { version = ">= 2.4.0 and < 3.0.0" } diff --git a/priv/static/off-topic.min.mjs b/priv/static/off-topic.min.mjs new file mode 100644 index 0000000..e533b83 --- /dev/null +++ b/priv/static/off-topic.min.mjs @@ -0,0 +1,3 @@ +var de=e=>e.head,C=e=>e.tail;var Ce=5,ft=(1<i.removeEventListener(t,r)}function U(e){let t=()=>document.visibilityState==="hidden"?"hidden":document.hasFocus()?"active":"passive",r=s=>{s!==n&&(e(s),n=s)},n=t();n!=="active"&&e(n);let i=new AbortController,o={capture:!0,signal:i.signal};return["pageshow","focus","blur","visibilitychange","resume"].forEach(s=>{window.addEventListener(s,()=>r(t()),o)}),window.addEventListener("freeze",()=>{r("frozen")},o),window.addEventListener("pagehide",s=>{r(s.persisted?"frozen":"terminated")},o),()=>i.abort()}function j(e){let t=()=>-new Date().getTimezoneOffset(),r=t();e(r);let n=setInterval(()=>{let i=t();i!==r&&(r=i,e(i))},6e4);return()=>clearInterval(n)}var D=ze(window,"online","offline",()=>navigator.onLine),F=ze(document.documentElement,"pointerenter","pointerleave",()=>document.documentElement.matches(":hover"));function R(e){let t=r=>{r.preventDefault(),e()};return window.addEventListener("beforeunload",t),()=>window.removeEventListener("beforeunload",t)}function P(e,t){let r=!1,n=null,i=()=>{r&&(r=!1,t(!1)),clearTimeout(n),n=setTimeout(()=>{r=!0,t(!0)},e)},o=()=>{document.hidden||i()},s=["pointermove","pointerdown","keydown","scroll","wheel","resize"];for(let l of s)window.addEventListener(l,i,{passive:!0});return document.addEventListener("visibilitychange",o),i(),()=>{clearTimeout(n);for(let l of s)window.removeEventListener(l,i);document.removeEventListener("visibilitychange",o)}}function W(e){return I(null,window,"resize",()=>e(window.innerWidth,window.innerHeight))}function G(e){return I(null,window,"scroll",()=>e(window.scrollX,window.scrollY))}function V(e){return I(null,screen.orientation,"change",()=>e(screen.orientation.type))}function H(e){return I(null,window,"languagechange",()=>e(navigator.language))}function X(e,t,r){let n=[];Se(t,([s,l])=>{n.push([window.matchMedia(s),l])});let i=()=>n.find(([s])=>s.matches)?.[1]??e,o=()=>r(i());o();for(let[s]of n)s.addEventListener("change",o);return()=>{for(let[s]of n)s.removeEventListener("change",o)}}var K=Ee(localStorage),Y=Ee(sessionStorage);function Ee(e){return(t,r)=>{let n=e.getItem(t);return r(n!==null,n??""),b(window,"storage",o=>{if(o.storageArea===e&&o.key===t){let s=o.newValue;r(s!==null,s??"")}})}}function J(e,t,r){let n=u(e,r);if(!n)return c;let{width:i,height:o}=n.getBoundingClientRect();t(Math.round(i),Math.round(o));let s=new ResizeObserver(l=>{let{width:p,height:y}=l[0].contentRect;t(Math.round(p),Math.round(y))});return s.observe(n),()=>s.disconnect()}function Q(e,t,r){let n=u(e,r);if(!n)return c;let i=n.getBoundingClientRect();t(i.bottom>0&&i.right>0&&i.top{t(s[0].isIntersecting)});return o.observe(n),()=>o.disconnect()}function Z(e,t,r){let n=u(e,r);if(!n)return c;let i=()=>t(Math.round(n.scrollLeft),Math.round(n.scrollTop));return i(),n.addEventListener("scroll",i,{passive:!0}),()=>n.removeEventListener("scroll",i)}function ee(e){return document.title=e,c}function te(e,t){let r=document.head.querySelector(`meta[name="${CSS.escape(e)}"]`);return r||(r=document.createElement("meta"),r.setAttribute("name",e),document.head.appendChild(r)),r.setAttribute("content",t),c}function ne(e,t,r,n){let o=(n instanceof ShadowRoot?n.host:n)?.internals;if(o){let s=e==="role"?"role":"aria"+e.slice(5).replace(/-([a-z])/g,(l,p)=>p.toUpperCase()).replace(/^./,l=>l.toUpperCase());o[s]=t}return c}function re(e,t,r,n){let o=(n instanceof ShadowRoot?n.host:n)?.internals;return o&&(t?o.states.add(e):o.states.delete(e)),c}function ie(e,t,r,n){return(n instanceof ShadowRoot?n.host:n)?.internals?.setFormValue(e?t:void 0),c}function I(e,t,r,n){let i=u(t,e);return n(),b(i,r,n)}function ze(e,t,r,n){return i=>{i(n());let o=()=>i(!0),s=()=>i(!1);return e.addEventListener(t,o),e.addEventListener(r,s),()=>{e.removeEventListener(t,o),e.removeEventListener(r,s)}}}function c(){}function oe(e,t){t===null?localStorage.removeItem(e):localStorage.setItem(e,t)}function se(e,t){t===null?sessionStorage.removeItem(e):sessionStorage.setItem(e,t)}function le(e,t,r,n,i){u(e,i)?.scrollTo({left:t,top:r,behavior:n})}function fe(e,t,r,n,i){u(e,i)?.scrollIntoView({block:t,inline:r,behavior:n})}function ue(e,t){u(e,t)?.focus()}function ce(){document.activeElement?.blur()}var ae=class extends HTMLElement{#e=null;#t=new Map;connectedCallback(){this.#e=tt(this),this.#e&&(this.#e.addEventListener("off-topic:subscribe",this.#n),this.#e.addEventListener("off-topic:unsubscribe",this.#r),this.#e.addEventListener("off-topic:command",this.#i)),this.dispatchEvent(new CustomEvent("off-topic:mount"))}connectedMoveCallback(){}disconnectedCallback(){this.#e&&(this.#e.removeEventListener("off-topic:subscribe",this.#n),this.#e.removeEventListener("off-topic:unsubscribe",this.#r),this.#e=null),queueMicrotask(()=>{if(!this.isConnected){for(let t of this.#t.values())clearTimeout(t.delayTimeout),t.cleanup?.();this.#t.clear()}})}#n=t=>{let{detail:{tick:r,path:n,name:i,params:o,include:s,throttle:l=0,delay:p=0}}=t,y=`${r}:${n.join(":")}`;if(this.#t.has(y))return;let pe=window.OffTopic.subscriptions[i];if(!pe){console.warn(`off-topic: subscription '${i}' requested, but not registered`);return}let a={tick:r,path:n,params:o,cleanup:void 0,lastFired:0,lastThrottledData:void 0,delayTimeout:void 0},k=d=>{let _=Array.isArray(d)?d.map(ve=>Be(ve,s)):Be(d,s),S=new CustomEvent("off-topic:dispatch",{detail:{tick:r,path:n,message:_}});this.dispatchEvent(S)},Ae=(...d)=>{let _=d.length===1?d[0]:d;if(l>0){let S=Date.now();S>=a.lastFired+l&&(a.lastFired=S,a.lastThrottledData=_,k(_))}p>0&&(clearTimeout(a.delayTimeout),a.delayTimeout=setTimeout(()=>{_!==a.lastThrottledData&&k(_)},p)),!l&&!p&&k(_)};a.cleanup=pe(...o,Ae,this.#e),this.#t.set(y,a)};#r=t=>{let{detail:{tick:r,path:n}}=t,i=`${r}:${n.join(":")}`,o=this.#t.get(i);o&&(this.#t.delete(i),clearTimeout(o.delayTimeout),o.cleanup?.())};#i=t=>{let{detail:{name:r,params:n}}=t;window.OffTopic.commands[r]?.(...n,this.#e)}};function tt(e){for(;e!=null;){if(e instanceof ShadowRoot&&e.host.tagName.toLowerCase()==="lustre-server-component")return e.host;e=e.parentNode}return console.error(` is not mounted inside a Lustre server component and will not work! +If you are using lustre.start, you can use off_topic's effects directly and can remove this component.`),null}function Be(e,t){if(typeof e!="object")return e;let r={};for(let n of t){let i=n.split(".");for(let o=0,s=e,l=r;o value.head; +var List$NonEmpty$rest = (value) => value.tail; + +// build/dev/javascript/gleam_stdlib/dict.mjs +var bits = 5; +var mask = (1 << bits) - 1; +var noElementMarker = Symbol(); +var generationKey = Symbol(); + +// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs +var unicode_whitespaces = [ + " ", + // Space + " ", + // Horizontal tab + "\n", + // Line feed + "\v", + // Vertical tab + "\f", + // Form feed + "\r", + // Carriage return + "\x85", + // Next line + "\u2028", + // Line separator + "\u2029" + // Paragraph separator +].join(""); +var trim_start_regex = /* @__PURE__ */ new RegExp( + `^[${unicode_whitespaces}]*` +); +var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`); +var MIN_I32 = -(2 ** 31); +var MAX_I32 = 2 ** 31 - 1; +var U32 = 2 ** 32; +var MAX_SAFE = Number.MAX_SAFE_INTEGER; +var MIN_SAFE = Number.MIN_SAFE_INTEGER; + +// build/dev/javascript/off_topic/off_topic/internal/utils_ffi.mjs +function iterate(list2, callback) { + if (Array.isArray(list2)) { + for (let i = 0; i < list2.length; i++) { + callback(list2[i]); + } + } else if (list2) { + for (list2; List$NonEmpty$rest(list2); list2 = List$NonEmpty$rest(list2)) { + callback(List$NonEmpty$first(list2)); + } + } +} +function resolveElement(target, root) { + if (target instanceof EventTarget) return target; + if (target === "root" || target === ":scope") return root; + if (target === "host") return root?.host; + if (target === "document") return document; + if (target === "window") return window; + if (!root) root = document; + return root instanceof Element && root.matches(target) ? root : root.querySelector(target); +} + +// build/dev/javascript/off_topic/off_topic/internal/subscriptions_ffi.mjs +function on(target, eventName, dispatch, root) { + const element = resolveElement(target, root); + if (!element) throw new Error("invalid target"); + element.addEventListener(eventName, dispatch, { passive: true }); + return () => element.removeEventListener(eventName, dispatch); +} +function on_page_state(dispatch) { + const getState = () => { + if (document.visibilityState === "hidden") return "hidden"; + if (document.hasFocus()) return "active"; + return "passive"; + }; + const handler = (newState) => { + if (newState !== lastState) { + dispatch(newState); + lastState = newState; + } + }; + let lastState = getState(); + if (lastState !== "active") { + dispatch(lastState); + } + const abort = new AbortController(); + const opts = { capture: true, signal: abort.signal }; + ["pageshow", "focus", "blur", "visibilitychange", "resume"].forEach((type) => { + window.addEventListener(type, () => handler(getState()), opts); + }); + window.addEventListener( + "freeze", + () => { + handler("frozen"); + }, + opts + ); + window.addEventListener( + "pagehide", + (event) => { + handler(event.persisted ? "frozen" : "terminated"); + }, + opts + ); + return () => abort.abort(); +} +function here(dispatch) { + const getOffset = () => -(/* @__PURE__ */ new Date()).getTimezoneOffset(); + let lastOffset = getOffset(); + dispatch(lastOffset); + const handle = setInterval(() => { + const offset = getOffset(); + if (offset !== lastOffset) { + lastOffset = offset; + dispatch(offset); + } + }, 6e4); + return () => clearInterval(handle); +} +var online = enter_exit( + window, + "online", + "offline", + () => navigator.onLine +); +var window_hover = enter_exit( + document.documentElement, + "pointerenter", + "pointerleave", + () => document.documentElement.matches(":hover") +); +function prevent_unload(callback) { + const handler = (event) => { + event.preventDefault(); + callback(); + }; + window.addEventListener("beforeunload", handler); + return () => window.removeEventListener("beforeunload", handler); +} +function on_idle(timeout_ms, dispatch) { + let idle = false; + let timer2 = null; + const reset = () => { + if (idle) { + idle = false; + dispatch(false); + } + clearTimeout(timer2); + timer2 = setTimeout(() => { + idle = true; + dispatch(true); + }, timeout_ms); + }; + const onVisibilityChange = () => { + if (!document.hidden) reset(); + }; + const windowEvents = [ + "pointermove", + "pointerdown", + "keydown", + "scroll", + "wheel", + "resize" + ]; + for (const event of windowEvents) { + window.addEventListener(event, reset, { passive: true }); + } + document.addEventListener("visibilitychange", onVisibilityChange); + reset(); + return () => { + clearTimeout(timer2); + for (const event of windowEvents) { + window.removeEventListener(event, reset); + } + document.removeEventListener("visibilitychange", onVisibilityChange); + }; +} +function window_size(dispatch) { + return trigger( + null, + window, + "resize", + () => dispatch(window.innerWidth, window.innerHeight) + ); +} +function window_scroll(dispatch) { + return trigger( + null, + window, + "scroll", + () => dispatch(window.scrollX, window.scrollY) + ); +} +function screen_orientation(dispatch) { + return trigger( + null, + screen.orientation, + "change", + () => dispatch(screen.orientation.type) + ); +} +function language(dispatch) { + return trigger( + null, + window, + "languagechange", + () => dispatch(navigator.language) + ); +} +function media_query(fallback, pairs, dispatch) { + const matchers = []; + iterate(pairs, ([query, value]) => { + matchers.push([window.matchMedia(query), value]); + }); + const current = () => matchers.find(([mq]) => mq.matches)?.[1] ?? fallback; + const handler = () => dispatch(current()); + handler(); + for (const [mq] of matchers) mq.addEventListener("change", handler); + return () => { + for (const [mq] of matchers) mq.removeEventListener("change", handler); + }; +} +var local_storage = storage(localStorage); +var session_storage = storage(sessionStorage); +function storage(storage2) { + return (key, dispatch) => { + const value = storage2.getItem(key); + dispatch(value !== null, value ?? ""); + const handler = (event) => { + if (event.storageArea === storage2 && event.key === key) { + const v = event.newValue; + dispatch(v !== null, v ?? ""); + } + }; + return on(window, "storage", handler); + }; +} +function element_size(selector, dispatch, root) { + const el = resolveElement(selector, root); + if (!el) return noop; + const { width, height } = el.getBoundingClientRect(); + dispatch(Math.round(width), Math.round(height)); + const observer = new ResizeObserver((entries) => { + const { width: width2, height: height2 } = entries[0].contentRect; + dispatch(Math.round(width2), Math.round(height2)); + }); + observer.observe(el); + return () => observer.disconnect(); +} +function element_visible(selector, dispatch, root) { + const el = resolveElement(selector, root); + if (!el) return noop; + const rect = el.getBoundingClientRect(); + dispatch( + rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth + ); + const observer = new IntersectionObserver((entries) => { + dispatch(entries[0].isIntersecting); + }); + observer.observe(el); + return () => observer.disconnect(); +} +function element_scroll(selector, dispatch, root) { + const el = resolveElement(selector, root); + if (!el) return noop; + const handler = () => dispatch(Math.round(el.scrollLeft), Math.round(el.scrollTop)); + handler(); + el.addEventListener("scroll", handler, { passive: true }); + return () => el.removeEventListener("scroll", handler); +} +function title(value) { + document.title = value; + return noop; +} +function meta(name, content) { + let el = document.head.querySelector(`meta[name="${CSS.escape(name)}"]`); + if (!el) { + el = document.createElement("meta"); + el.setAttribute("name", name); + document.head.appendChild(el); + } + el.setAttribute("content", content); + return noop; +} +function aria(name, value, _handler, root) { + const host = root instanceof ShadowRoot ? root.host : root; + const internals = host?.internals; + if (internals) { + const prop = name === "role" ? "role" : "aria" + name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase()); + internals[prop] = value; + } + return noop; +} +function pseudo_state(name, checked, _handler, root) { + const host = root instanceof ShadowRoot ? root.host : root; + const internals = host?.internals; + if (internals) { + if (checked) { + internals.states.add(name); + } else { + internals.states.delete(name); + } + } + return noop; +} +function form_value(exists, value, _handler, root) { + const host = root instanceof ShadowRoot ? root.host : root; + host?.internals?.setFormValue(exists ? value : void 0); + return noop; +} +function trigger(root, selector, event, trigger2) { + const el = resolveElement(selector, root); + if (!el) noop; + trigger2(); + return on(el, event, trigger2); +} +function enter_exit(target, enterEvent, exitEvent, getValue) { + return (dispatch) => { + dispatch(getValue()); + const enter = () => dispatch(true); + const leave = () => dispatch(false); + target.addEventListener(enterEvent, enter); + target.addEventListener(exitEvent, leave); + return () => { + target.removeEventListener(enterEvent, enter); + target.removeEventListener(exitEvent, leave); + }; + }; +} +function noop() { +} + +// build/dev/javascript/off_topic/off_topic/internal/commands_ffi.mjs +function set_local_storage(key, value) { + if (value === null) localStorage.removeItem(key); + else localStorage.setItem(key, value); +} +function set_session_storage(key, value) { + if (value === null) sessionStorage.removeItem(key); + else sessionStorage.setItem(key, value); +} +function scroll_to(selector, x, y, behavior, root) { + resolveElement(selector, root)?.scrollTo({ left: x, top: y, behavior }); +} +function scroll_into_view(selector, block, inline, behavior, root) { + resolveElement(selector, root)?.scrollIntoView({ block, inline, behavior }); +} +function focus(selector, root) { + resolveElement(selector, root)?.focus(); +} +function blur() { + document.activeElement?.blur(); +} + +// build/dev/javascript/off_topic/off_topic/internal/component.mjs +var OffTopicClientRuntime = class extends HTMLElement { + #root = null; + #subscriptions = /* @__PURE__ */ new Map(); + connectedCallback() { + this.#root = findRoot(this); + if (this.#root) { + this.#root.addEventListener("off-topic:subscribe", this.#onSubscribe); + this.#root.addEventListener("off-topic:unsubscribe", this.#onUnsubscribe); + this.#root.addEventListener("off-topic:command", this.#onCommand); + } + this.dispatchEvent(new CustomEvent("off-topic:mount")); + } + connectedMoveCallback() { + } + disconnectedCallback() { + if (this.#root) { + this.#root.removeEventListener("off-topic:subscribe", this.#onSubscribe); + this.#root.removeEventListener( + "off-topic:unsubscribe", + this.#onUnsubscribe + ); + this.#root = null; + } + queueMicrotask(() => { + if (this.isConnected) return; + for (const subscription of this.#subscriptions.values()) { + clearTimeout(subscription.delayTimeout); + subscription.cleanup?.(); + } + this.#subscriptions.clear(); + }); + } + #onSubscribe = (event) => { + const { + detail: { tick, path, name, params, include, throttle = 0, delay = 0 } + } = event; + const key = `${tick}:${path.join(":")}`; + if (this.#subscriptions.has(key)) return; + const start = window.OffTopic.subscriptions[name]; + if (!start) { + console.warn( + `off-topic: subscription '${name}' requested, but not registered` + ); + return; + } + const state = { + tick, + path, + params, + cleanup: void 0, + lastFired: 0, + lastThrottledData: void 0, + delayTimeout: void 0 + }; + const dispatch = (data2) => { + const message = Array.isArray(data2) ? data2.map((arg) => pickPaths(arg, include)) : pickPaths(data2, include); + const ev = new CustomEvent("off-topic:dispatch", { + detail: { tick, path, message } + }); + this.dispatchEvent(ev); + }; + const handler = (...args) => { + const data2 = args.length === 1 ? args[0] : args; + if (throttle > 0) { + const now = Date.now(); + if (now >= state.lastFired + throttle) { + state.lastFired = now; + state.lastThrottledData = data2; + dispatch(data2); + } + } + if (delay > 0) { + clearTimeout(state.delayTimeout); + state.delayTimeout = setTimeout(() => { + if (data2 === state.lastThrottledData) return; + dispatch(data2); + }, delay); + } + if (!throttle && !delay) { + dispatch(data2); + } + }; + state.cleanup = start(...params, handler, this.#root); + this.#subscriptions.set(key, state); + }; + #onUnsubscribe = (event) => { + const { + detail: { tick, path } + } = event; + const key = `${tick}:${path.join(":")}`; + const subscription = this.#subscriptions.get(key); + if (!subscription) return; + this.#subscriptions.delete(key); + clearTimeout(subscription.delayTimeout); + subscription.cleanup?.(); + }; + #onCommand = (event) => { + const { + detail: { name, params } + } = event; + window.OffTopic.commands[name]?.(...params, this.#root); + }; +}; +function findRoot(node) { + while (node != null) { + if (node instanceof ShadowRoot && node.host.tagName.toLowerCase() === "lustre-server-component") { + return node.host; + } + node = node.parentNode; + } + console.error( + " is not mounted inside a Lustre server component and will not work!\nIf you are using lustre.start, you can use off_topic's effects directly and can remove this component." + ); + return null; +} +function pickPaths(event, include) { + if (typeof event !== "object") { + return event; + } + const data2 = {}; + for (const property of include) { + const path = property.split("."); + for (let i = 0, input = event, output = data2; i < path.length; i++) { + if (i === path.length - 1) { + output[path[i]] = input[path[i]]; + } else { + output = output[path[i]] ??= {}; + input = input[path[i]]; + } + } + } + return data2; +} +window.OffTopic ??= {}; +window.OffTopic.subscriptions ??= {}; +window.OffTopic.commands ??= {}; +customElements.define("ot-client-runtime", OffTopicClientRuntime); +Object.assign(window.OffTopic.subscriptions, { + "off-topic/event": on, + "off-topic/page-state": on_page_state, + "off-topic/prevent-unload": prevent_unload, + "off-topic/media-query": media_query, + "off-topic/window-hover": window_hover, + "off-topic/here": here, + "off-topic/online": online, + "off-topic/window-size": window_size, + "off-topic/window-scroll": window_scroll, + "off-topic/screen-orientation": screen_orientation, + "off-topic/language": language, + "off-topic/local-storage": local_storage, + "off-topic/session-storage": session_storage, + "off-topic/title": title, + "off-topic/meta": meta, + "off-topic/aria": aria, + "off-topic/pseudo-state": pseudo_state, + "off-topic/form-value": form_value, + "off-topic/element-size": element_size, + "off-topic/element-scroll": element_scroll, + "off-topic/element-visible": element_visible, + "off-topic/on-idle": on_idle +}); +Object.assign(window.OffTopic.commands, { + "off-topic/set-local-storage": set_local_storage, + "off-topic/set-session-storage": set_session_storage, + "off-topic/scroll-to": scroll_to, + "off-topic/scroll-into-view": scroll_into_view, + "off-topic/focus": focus, + "off-topic/blur": blur +}); diff --git a/src/off_topic.gleam b/src/off_topic.gleam new file mode 100644 index 0000000..42f5308 --- /dev/null +++ b/src/off_topic.gleam @@ -0,0 +1,2527 @@ +//// +//// +//// +//// **Tip:** Hover the links for short summaries! +//// +//// #### Starting apps +//// [application](#application "Create a Lustre application with subscriptions"), +//// [component](#component "Create a Lustre component with subscriptions"), +//// [script](#script "Inline the server component runtime as a script tag") +//// +//// #### Making subscriptions +//// [none](#none "A subscription that never fires"), +//// [from](#from "Build a subscription from an arbitrary setup callback"), +//// [element](#element "Build a subscription with access to the root element"), +//// [batch](#batch "Combine multiple subscriptions into one"), +//// [dep](#dep "Wrap a value as a subscription dependency"), +//// [map](#map "Transform the messages a subscription dispatches"), +//// [watch](#watch "Run a side effect whenever dependencies change") +//// +//// #### Keyboard +//// [on_key_press](#on_key_press "Subscribe to keypress events"), +//// [on_key_down](#on_key_down "Subscribe to keydown events"), +//// [on_key_up](#on_key_up "Subscribe to keyup events") +//// +//// #### Pointer +//// [on_click](#on_click "Subscribe to click events"), +//// [on_pointer_move](#on_pointer_move "Subscribe to pointer move events"), +//// [on_pointer_down](#on_pointer_down "Subscribe to pointer down events"), +//// [on_pointer_up](#on_pointer_up "Subscribe to pointer up events"), +//// [on_pointer_cancel](#on_pointer_cancel "Subscribe to pointer cancel events"), +//// [on_wheel](#on_wheel "Subscribe to wheel events") +//// +//// #### Browser +//// [window_size](#window_size "Dispatch the current window size"), +//// [screen_orientation](#screen_orientation "Dispatch the current screen orientation"), +//// [window_scroll](#window_scroll "Dispatch the current scroll position"), +//// [scroll_to](#scroll_to "Scroll the window to a position"), +//// [page_state](#page_state "Dispatch the current page lifecycle state"), +//// [window_hover](#window_hover "Dispatch the current window hover state"), +//// [title](#title "Update the document title"), +//// [meta](#meta "Upsert a document meta tag"), +//// [prevent_unload](#prevent_unload "Block the browser unload dialog") +//// +//// #### DOM +//// [element_size](#element_size "Dispatch element dimensions on resize"), +//// [element_scroll](#element_scroll "Dispatch element scroll position"), +//// [element_scroll_to](#element_scroll_to "Scroll an element to a position"), +//// [scroll_into_view](#scroll_into_view "Scroll an element into view"), +//// [element_visible](#element_visible "Dispatch element viewport visibility") +//// [focus](#focus "Focus an element by CSS selector"), +//// [blur](#blur "Blur the currently focused element"), +//// [on](#on "Subscribe to a DOM event on a target element") +//// +//// #### Component +//// [aria](#aria "Sync an ARIA property on the component host"), +//// [pseudo_state](#pseudo_state "Sync a CSS custom state on the component host"), +//// [form_value](#form_value "Sync the form-associated element value") +//// +//// #### Preferences +//// [colour_scheme](#colour_scheme "Dispatch the current color scheme preference"), +//// [contrast](#contrast "Dispatch the current contrast preference"), +//// [reduced_motion](#reduced_motion "Dispatch the current motion preference"), +//// [breakpoints](#breakpoints "Dispatch the active breakpoint from a list"), +//// [tailwind_breakpoints](#tailwind_breakpoints "Dispatch the active breakpoint using the Tailwind definitions"), +//// [language](#language "Dispatch the current navigator language"), +//// [local_storage](#local_storage "Dispatch a local storage value and its changes"), +//// [session_storage](#session_storage "Dispatch a session storage value and its changes"), +//// [set_local_storage](#set_local_storage "Write a localStorage key"), +//// [set_session_storage](#set_session_storage "Write a sessionStorage key") +//// +//// #### Time +//// [now](#now "Dispatch the current timestamp"), +//// [here](#here "Dispatch the current browser timezone offset"), +//// [after](#after "Dispatch a message after a delay"), +//// [every](#every "Dispatch a message on a repeating interval"), +//// [on_idle](#on_idle "Dispatch a message when the user stops interacting with the page for some time") +//// +//// #### Connections +//// [online](#online "Dispatch the current network status"), +//// [server_sent_events](#server_sent_events "Subscribe to a Server-Sent Events stream"), +//// [websocket](#websocket "Subscribe to a WebSocket connection"), +//// [websocket_send](#websocket_send "Send a frame over a WebSocket connection") +//// +//// #### Advanced +//// [init](#init "Run the application init function and start subscriptions"), +//// [update](#update "Handle a message in the subscription runtime"), +//// [view](#view "Render the application view with the subscription runtime") +//// [remote](#remote "Build a subscription that runs on the server component client"), +//// [command](#command "Dispatch a browser command via the off_topic client runtime"), +//// [watching](#watching "Add dependencies to a subscription"), +//// [including](#including "Specify which event fields to include in a remote subscription"), +//// [throttle](#throttle "Limit how often a subscription fires"), +//// [delay](#delay "Delay dispatching after the last event in a burst"), +//// [root](#root "CSS selector for the component mount point") + +import gleam/bool +import gleam/dynamic/decode.{type Decoder, type Dynamic} +import gleam/float +import gleam/int +import gleam/json.{type Json} +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/order.{Lt} +import gleam/result +import gleam/time/duration.{type Duration} +import gleam/time/timestamp.{type Timestamp} +import lustre.{type App} +import lustre/attribute +import lustre/effect.{type Effect} +import lustre/element.{type Element} +import lustre/element/html +import lustre/event +import lustre/server_component +import off_topic/component +import off_topic/internal/commands +import off_topic/internal/subscriptions + +// -- FUNDAMENTALS ------------------------------------------------------------ + +/// An active listener that dispatches messages to your Lustre application. +/// +/// Build subscriptions with the functions in this module and return them from +/// your application's `subscriptions` callback. Use [batch](#batch) to combine +/// multiple subscriptions, and [none](#none) to return an empty one. +pub opaque type Subscription(message) { + Simple( + dependencies: List(Dependency), + start: fn(fn(message) -> Nil) -> fn() -> Nil, + throttle: Option(Duration), + delay: Option(Duration), + ) + Element( + dependencies: List(Dependency), + start: fn(fn(message) -> Nil, Dynamic) -> fn() -> Nil, + throttle: Option(Duration), + delay: Option(Duration), + ) + Remote( + name: String, + params: List(Json), + dependencies: List(Dependency), + decoder: Decoder(message), + include: List(String), + throttle: Option(Duration), + delay: Option(Duration), + ) + Batch(subscriptions: List(Subscription(message))) +} + +/// An opaque value off_topic uses to detect when a subscription's inputs have +/// changed and it needs to be restarted. +/// +/// Create one with [dep](#dep). +pub type Dependency + +/// The subscription runtime's wrapper around your application model. +/// +/// Use with [init](#init), [update](#update), and [view](#view) when wiring +/// up off_topic manually instead of through [application](#application). +pub opaque type Model(model, message) { + Dormant(model: model) + Application(model: model, tick: Int, state: State(message)) + Component(model: model, tick: Int, state: State(message), clients: Int) +} + +/// The subscription runtime's wrapper around your application message type. +/// +/// Use with [init](#init), [update](#update), and [view](#view) when wiring +/// up off_topic manually instead of through [application](#application). +pub opaque type Message(message) { + Mounted + ToApp(message: message) + GotStarted(tick: Int, path: List(Int), cleanup: fn() -> Nil) + FromRemote(tick: Int, path: List(Int), message: Dynamic) + ThrottleDelay(tick: Int, path: List(Int), message: message, ts: Timestamp) + DelayFired(tick: Int, path: List(Int), delay_tick: Int, message: message) + ComponentConnected(forward: Option(message)) + ComponentDisconnected(forward: Option(message)) +} + +/// CSS selector for the component mount point. +/// +/// Pass `root` as the `selector` argument to any element-scoped subscription +/// to target the Lustre component mount element. +/// +/// See also: [target](#target), [element_size](#element_size), +/// [element_scroll](#element_scroll), [element_visible](#element_visible) +pub const root = ":scope" + +// -- STARTING APPS ----------------------------------------------------------- + +/// Create a Lustre application with subscription support. +/// +/// A drop-in replacement for `lustre.application` that adds a `subscriptions` +/// callback. The returned `App` is passed directly to `lustre.start` or +/// `lustre.start_server_component`. +/// +/// See also: [component](#component), [init](#init), [update](#update), [view](#view) +pub fn application( + init app_init: fn(flags) -> #(model, Effect(message)), + update app_update: fn(model, message) -> #(model, Effect(message)), + subscriptions app_subscriptions: fn(model) -> Subscription(message), + view app_view: fn(model) -> Element(message), +) -> App(flags, Model(model, message), Message(message)) { + let #(update, view) = wrap_runtime(app_update, app_subscriptions, app_view) + + // Applications always start subscriptions immediately — no shadow root check. + let init = fn(flags) { + let #(app_model, app_effect) = app_init(flags) + case lustre.is_browser() { + True -> { + let eff = + effect.batch([ + effect.map(app_effect, ToApp), + effect.from(fn(dispatch) { dispatch(Mounted) }), + ]) + #(Dormant(model: app_model), eff) + } + False -> #(Dormant(model: app_model), effect.map(app_effect, ToApp)) + } + } + + lustre.application(init:, update:, view:) +} + +/// Create a Lustre [component](https://hexdocs.pm/lustre/lustre/component.html) +/// that runs the off_topic subscription runtime. This is the component +/// equivalent of [application](#application). +/// +/// If you're using component options, swap your import to `off_topic/component`, +/// offering the same API. +/// +/// See also: [application](#application) +pub fn component( + init app_init: fn(Nil) -> #(model, Effect(message)), + update app_update: fn(model, message) -> #(model, Effect(message)), + subscriptions app_subscriptions: fn(model) -> Subscription(message), + view app_view: fn(model) -> Element(message), + options options: List(component.Option(message)), +) -> App(Nil, Model(model, message), Message(message)) { + let #(update, view) = wrap_runtime(app_update, app_subscriptions, app_view) + + // Components never dispatch Mounted — lifecycle is owned by ComponentConnected. + let init = fn(flags) { + let #(app_model, app_effect) = app_init(flags) + #(Dormant(model: app_model), effect.map(app_effect, ToApp)) + } + + let #(user_on_connect, user_on_disconnect) = + component.extract_lifecycle(options) + + // User options lifted to Message(message) via ToApp, followed by our runtime + // lifecycle options. Lustre's configure is last-wins, so appending last means + // our on_connect / on_disconnect always take effect. + let lustre_opts = + list.map(options, component.map(_, ToApp)) + |> list.append([ + component.on_connect(ComponentConnected(user_on_connect)), + component.on_disconnect(ComponentDisconnected(user_on_disconnect)), + ]) + |> component.to_lustre_options + + lustre.component(init:, update:, view:, options: lustre_opts) +} + +fn wrap_runtime(app_update, app_subscriptions, app_view) { + let view = view(_, app_view) + let update = fn(model, msg) { + update(model, msg, app_subscriptions, app_update) + } + #(update, view) +} + +/// Inline the server component runtime as a `