mono-jsx 0.3.2 β†’ 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@ mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in
9
9
  - πŸ”« Minimal state runtime
10
10
  - 🚨 Complete Web API TypeScript definitions
11
11
  - ⏳ Streaming rendering
12
+ - πŸ₯· [htmx](#using-htmx) integration
12
13
  - 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
13
14
 
14
15
  ## Installation
@@ -65,6 +66,7 @@ mono-jsx allows you to return an `<html>` JSX element as a `Response` object in
65
66
 
66
67
  ```tsx
67
68
  // app.tsx
69
+
68
70
  export default {
69
71
  fetch: (req) => (
70
72
  <html>
@@ -91,6 +93,7 @@ npx wrangler dev app.tsx
91
93
 
92
94
  ```tsx
93
95
  // app.tsx
96
+
94
97
  import { serve } from "srvx";
95
98
 
96
99
  serve({
@@ -157,7 +160,7 @@ mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/
157
160
  </a>;
158
161
  ```
159
162
 
160
- ### `<slot>` Element
163
+ ### Using `<slot>` Element
161
164
 
162
165
  mono-jsx uses [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) elements to render slotted content (equivalent to React's `children` property). You can also add the `name` attribute to define named slots:
163
166
 
@@ -185,7 +188,7 @@ function App() {
185
188
  }
186
189
  ```
187
190
 
188
- ### `html` Tag Function
191
+ ### Using `html` Tag Function
189
192
 
190
193
  mono-jsx provides an `html` tag function to render raw HTML in JSX instead of React's `dangerouslySetInnerHTML`:
191
194
 
@@ -270,12 +273,7 @@ mono-jsx also accepts functions for the `action` property on `form` elements, wh
270
273
  ```tsx
271
274
  function App() {
272
275
  return (
273
- <form
274
- action={(data: FormData, event: SubmitEvent) => {
275
- event.preventDefault(); // true
276
- console.log(data.get("name"));
277
- }}
278
- >
276
+ <form action={(data: FormData, event: SubmitEvent) => console.log(data.get("name"))}>
279
277
  <input type="text" name="name" />
280
278
  <button type="submit">Submit</button>
281
279
  </form>
@@ -285,11 +283,11 @@ function App() {
285
283
 
286
284
  ## Reactive
287
285
 
288
- mono-jsx provides a minimal state runtime for updating the view based on client-side state changes:
286
+ mono-jsx provides a minimal state runtime for updating the view based on client-side state changes.
289
287
 
290
- ### Using State
288
+ ### Using Component State
291
289
 
292
- You can use `this` to define state in your components. The view will automatically update when the state changes:
290
+ You can use the `this` keyword in your components to manage state. The state is bound to the component instance and can be updated directly, and will automatically re-render the view when the state changes:
293
291
 
294
292
  ```tsx
295
293
  function Counter(
@@ -377,9 +375,60 @@ function App(this: FC<{ input: string }>) {
377
375
  }
378
376
  ```
379
377
 
380
- ### Limitation of States
378
+ ### Using `<toggle>` Element with State
379
+
380
+ The `<toggle>` element conditionally renders content based on the value of a state.
381
+
382
+ ```tsx
383
+ function App(this: FC<{ show: boolean }>) {
384
+ this.show = false;
385
+
386
+ function toggle() {
387
+ this.show = !this.show;
388
+ }
389
+
390
+ return (
391
+ <div>
392
+ <toggle value={this.show}>
393
+ <h1>Welcome to mono-jsx!</h1>
394
+ </toggle>
395
+
396
+ <button onClick={toggle}>
397
+ {this.computed(() => this.show ? "Hide" : "Show")}
398
+ </button>
399
+ </div>
400
+ );
401
+ }
402
+ ```
403
+
404
+ ### Using `<switch>` Element with State
405
+
406
+ The `<switch>` element renders different content based on the value of a state. Elements with matching `slot` attributes are displayed when their value matches, otherwise default content is shown:
407
+
408
+ ```tsx
409
+ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
410
+ this.lang = "en";
411
+
412
+ return (
413
+ <div>
414
+ <switch value={this.lang}>
415
+ <h1 slot="en">Hello, world!</h1>
416
+ <h1 slot="zh">δ½ ε₯½οΌŒδΈ–η•ŒοΌ</h1>
417
+ <h1>βœ‹πŸŒŽβ—οΈ</h1>
418
+ </switch>
419
+ <p>
420
+ <button onClick={() => this.lang = "en"}>English</button>
421
+ <button onClick={() => this.lang = "zh"}>δΈ­ζ–‡</button>
422
+ <button onClick={() => this.lang = "emoji"}>Emoji</button>
423
+ </p>
424
+ </div>
425
+ );
426
+ }
427
+ ```
428
+
429
+ ### Limitation of State
381
430
 
382
- 1\. States cannot be used in arrow function components.
431
+ 1\. Component state cannot be used in arrow function components.
383
432
 
384
433
  ```tsx
385
434
  // ❌ Won't work - state updates won't refresh the view
@@ -405,7 +454,7 @@ function App(this: FC) {
405
454
  }
406
455
  ```
407
456
 
408
- 2\. States cannot be computed outside of the `this.computed` method.
457
+ 2\. Component state cannot be computed outside of the `this.computed` method.
409
458
 
410
459
  ```tsx
411
460
  // ❌ Won't work - state updates won't refresh the view
@@ -435,58 +484,82 @@ function App(this: FC) {
435
484
  }
436
485
  ```
437
486
 
438
- ## Built-in Elements
487
+ ## Using `this` in Components
439
488
 
440
- mono-jsx provides built-in elements to help you build reactive UIs.
489
+ mono-jsx binds a special `this` object to your components when they are rendered. This object contains properties and methods that you can use to manage state, context, and other features.
441
490
 
442
- ### `<toggle>` element
491
+ The `this` object contains the following properties:
443
492
 
444
- The `<toggle>` element conditionally renders content based on a boolean value:
493
+ - `app`: The app state defined on the root `<html>` element.
494
+ - `context`: The context defined on the root `<html>` element.
495
+ - `request`: The request object from the `fetch` handler.
496
+ - `computed`: A method to create computed properties based on state.
445
497
 
446
- ```tsx
447
- function App(this: FC<{ show: boolean }>) {
448
- this.show = false;
498
+ ```ts
499
+ type FC<State = {}, AppState = {}, Context = {}> = {
500
+ readonly app: AppState;
501
+ readonly context: Context;
502
+ readonly request: Request;
503
+ readonly computed: <V = unknown>(computeFn: () => V) => V;
504
+ } & Omit<State, "app" | "context" | "request" | "computed">;
505
+ ```
449
506
 
450
- function toggle() {
451
- this.show = !this.show;
452
- }
507
+ ### Using State
508
+
509
+ Check the [Using State](#using-state) section for more details on how to use state in your components.
510
+
511
+ ### Using Context
453
512
 
513
+ You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
514
+
515
+ ```tsx
516
+ function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
517
+ const { auth } = this.context;
454
518
  return (
455
519
  <div>
456
- <toggle value={this.show}>
457
- <h1>Welcome to mono-jsx!</h1>
458
- </toggle>
459
-
460
- <button onClick={toggle}>
461
- {this.computed(() => this.show ? "Hide" : "Show")}
462
- </button>
520
+ <h1>Welcome back, {auth.name}!</h1>
521
+ <p>Your UUID is {auth.uuid}</p>
463
522
  </div>
464
523
  );
465
524
  }
525
+
526
+ export default {
527
+ fetch: async (req) => {
528
+ const auth = await doAuth(req);
529
+ return (
530
+ <html context={{ auth }} request={req}>
531
+ {!auth && <p>Please Login</p>}
532
+ {auth && <Dash />}
533
+ </html>
534
+ );
535
+ },
536
+ };
466
537
  ```
467
538
 
468
- ### `<switch>` element
539
+ ### Accessing Request Info
469
540
 
470
- The `<switch>` element renders different content based on a value. Elements with matching `slot` attributes are displayed when their value matches, otherwise default content is shown:
541
+ You can access request information in components via the `request` property in `this` which is set on the root `<html>` element:
471
542
 
472
543
  ```tsx
473
- function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
474
- this.lang = "en";
475
-
544
+ function RequestInfo(this: FC) {
545
+ const { request } = this;
476
546
  return (
477
547
  <div>
478
- <switch value={this.lang}>
479
- <h1 slot="en">Hello, world!</h1>
480
- <h1 slot="zh">δ½ ε₯½οΌŒδΈ–η•ŒοΌ</h1>
481
- <h1>βœ‹πŸŒŽβ—οΈ</h1>
482
- </switch>
483
-
484
- <button onClick={() => this.lang = "en"}>English</button>
485
- <button onClick={() => this.lang = "zh"}>δΈ­ζ–‡</button>
486
- <button onClick={() => this.lang = "emoji"}>Emoji</button>
548
+ <h1>Request Info</h1>
549
+ <p>{request.method}</p>
550
+ <p>{request.url}</p>
551
+ <p>{request.headers.get("user-agent")}</p>
487
552
  </div>
488
553
  );
489
554
  }
555
+
556
+ export default {
557
+ fetch: (req) => (
558
+ <html request={req}>
559
+ <RequestInfo />
560
+ </html>
561
+ ),
562
+ };
490
563
  ```
491
564
 
492
565
  ## Streaming Rendering
@@ -533,61 +606,24 @@ export default {
533
606
  };
534
607
  ```
535
608
 
536
- ## Using Context
609
+ You can add the `catch` attribute to handle errors in async components. The `catch` attribute should be a function that returns a JSX element:
537
610
 
538
- You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
539
-
540
- ```tsx
541
- function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
542
- const { auth } = this.context;
543
- return (
544
- <div>
545
- <h1>Welcome back, {auth.name}!</h1>
546
- <p>Your UUID is {auth.uuid}</p>
547
- </div>
548
- );
549
- }
550
-
551
- export default {
552
- fetch: async (req) => {
553
- const auth = await doAuth(req);
554
- return (
555
- <html context={{ auth }} request={req}>
556
- {!auth && <p>Please Login</p>}
557
- {auth && <Dash />}
558
- </html>
559
- );
560
- },
561
- };
562
- ```
563
-
564
- ## Accessing Request Info
565
-
566
- You can access request information in components via the `request` property in `this` which is set on the root `<html>` element:
567
-
568
- ```tsx
569
- function RequestInfo(this: FC) {
570
- const { request } = this;
571
- return (
572
- <div>
573
- <h1>Request Info</h1>
574
- <p>{request.method}</p>
575
- <p>{request.url}</p>
576
- <p>{request.headers.get("user-agent")}</p>
577
- </div>
578
- );
611
+ ```jsx
612
+ async function Hello() {
613
+ throw new Error("Something went wrong!");
614
+ return <p>Hello world!</p>;
579
615
  }
580
616
 
581
617
  export default {
582
618
  fetch: (req) => (
583
- <html request={req}>
584
- <RequestInfo />
619
+ <html>
620
+ <Hello catch={err => <p>{err.messaage}</p>} />
585
621
  </html>
586
622
  ),
587
623
  };
588
624
  ```
589
625
 
590
- ## Customizing Response
626
+ ## Customizing html Response
591
627
 
592
628
  Add `status` or `headers` attributes to the root `<html>` element to customize the response:
593
629
 
@@ -607,7 +643,7 @@ export default {
607
643
  };
608
644
  ```
609
645
 
610
- ## Using htmx
646
+ ### Using htmx
611
647
 
612
648
  mono-jsx integrates with [htmx](https://htmx.org/) and [typed-htmx](https://github.com/Desdaemon/typed-htmx). To use htmx, add the `htmx` attribute to the root `<html>` element:
613
649
 
@@ -635,7 +671,7 @@ export default {
635
671
  };
636
672
  ```
637
673
 
638
- ### Adding htmx Extensions
674
+ #### Adding htmx Extensions
639
675
 
640
676
  You can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `<html>` element:
641
677
 
@@ -651,7 +687,7 @@ export default {
651
687
  };
652
688
  ```
653
689
 
654
- ### Specifying htmx Version
690
+ #### Specifying htmx Version
655
691
 
656
692
  You can specify the htmx version by setting the `htmx` attribute to a specific version:
657
693
 
@@ -667,7 +703,7 @@ export default {
667
703
  };
668
704
  ```
669
705
 
670
- ### Installing htmx Manually
706
+ #### Installing htmx Manually
671
707
 
672
708
  By default, mono-jsx installs htmx from [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also install htmx manually with your own CDN or local copy:
673
709
 
package/jsx-runtime.mjs CHANGED
@@ -5,61 +5,16 @@ var $html = Symbol.for("jsx.html");
5
5
  var $state = Symbol.for("mono.state");
6
6
  var $computed = Symbol.for("mono.computed");
7
7
 
8
- // state.ts
9
- var collectDeps;
10
- function createState(fc, appState, context, request) {
11
- const computed = (fn) => {
12
- const deps = /* @__PURE__ */ Object.create(null);
13
- collectDeps = (fc2, key, value2) => deps[fc2 + ":" + key] = value2;
14
- const value = fn.call(proxy);
15
- collectDeps = void 0;
16
- if (value instanceof Promise || deps.size === 0) return value;
17
- return [$computed, { value, deps, fn: fn.toString(), fc }, $vnode];
18
- };
19
- const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
20
- get(target, key, receiver) {
21
- switch (key) {
22
- case "app":
23
- return appState;
24
- case "context":
25
- return context ?? {};
26
- case "request":
27
- if (!request) {
28
- throw new Error("request is not defined");
29
- }
30
- return request;
31
- case "computed":
32
- return computed;
33
- default: {
34
- const value = Reflect.get(target, key, receiver);
35
- if (typeof key === "symbol") {
36
- return value;
37
- }
38
- if (collectDeps) {
39
- collectDeps(fc, key, value);
40
- return value;
41
- }
42
- return [$state, { key, value, fc }, $vnode];
43
- }
44
- }
45
- },
46
- set(target, key, value, receiver) {
47
- return Reflect.set(target, key, value, receiver);
48
- }
49
- });
50
- return proxy;
51
- }
52
-
53
8
  // runtime/index.ts
54
- var STATE_JS = `const p=new Map,f=e=>p.get(e)??p.set(e,E(e)).get(e),u=(e,t)=>e.getAttribute(t),d=(e,t)=>e.hasAttribute(t),E=e=>{const t=Object.create(null),l=new Map,s=(i,c)=>{let r=c;Object.defineProperty(t,i,{get:()=>r,set:o=>{if(o!==r){const a=l.get(i);a&&queueMicrotask(()=>a.forEach(m=>m())),r=o}}})},n=(i,c)=>{let r=l.get(i);r||(r=[],l.set(i,r)),r.push(c)};return e>0&&Object.defineProperty(t,"app",{get:()=>f(0).store,enumerable:!1,configurable:!1}),{store:t,define:s,watch:n}},g=(e,t,l)=>{if(t==="toggle"){let s;return()=>{if(!s){const n=e.firstElementChild;n&&n.tagName==="TEMPLATE"&&d(n,"m-slot")?(s=n.content.childNodes,e.innerHTML=""):s=e.childNodes}l()?e.append(...s):e.innerHTML=""}}if(t==="switch"){let s=u(e,"match"),n,i,c=o=>n.get(o)??n.set(o,[]).get(o),r;return()=>{if(!n){n=new Map,i=[];for(const o of e.childNodes)if(o.nodeType===1&&o.tagName==="TEMPLATE"&&d(o,"m-slot")){for(const a of o.content.childNodes)a.nodeType===1&&d(a,"slot")?c(u(a,"slot")).push(a):i.push(a);o.remove()}else s?c(s).push(o):i.push(o)}r=l(),e.innerHTML="",e.append(...n.has(r)?n.get(r):i)}}if(t&&t.length>2&&t.startsWith("[")&&t.endsWith("]")){let s=t.slice(1,-1),n=e.parentElement;return n.tagName==="M-GROUP"&&(n=n.previousElementSibling),()=>{const i=l();i===!1?n.removeAttribute(s):(s==="class"||s==="style")&&i&&typeof i=="object"?n.setAttribute(s,s==="class"?cx(i):styleToCSS(i)):n.setAttribute(s,i===!0?"":""+i)}}return()=>e.textContent=""+l()},h=e=>{const t=e.indexOf(":");if(t>0)return[Number(e.slice(0,t)),e.slice(t+1)];throw new Error("Invalid state key")};customElements.define("m-state",class extends HTMLElement{connectedCallback(){const e=this,t=f(Number(u(e,"fc"))),l=u(e,"mode"),s=u(e,"key");s?t.watch(s,g(e,l,()=>t.store[s])):d(e,"computed")&&setTimeout(()=>{const n=e.firstChild;if(n&&n.nodeType===1&&n.type==="computed"){const i=n.textContent;i&&new Function("$",i).call(t.store,(c,r)=>{const o=g(e,l,c);for(const a of r){const[m,T]=h(a);f(m).watch(T,o)}})}})}}),Object.assign(globalThis,{$state:e=>e!==void 0?f(e).store:void 0,$defineState:(e,t)=>{const[l,s]=h(e);f(l).define(s,t)}});`;
9
+ var STATE_JS = `const m=new Map,u=e=>m.get(e)??m.set(e,b(e)).get(e),f=(e,t)=>e.getAttribute(t),d=(e,t)=>e.hasAttribute(t),b=e=>{const t=Object.create(null),r=new Map,n=(i,c)=>{let a=c;Object.defineProperty(t,i,{get:()=>a,set:o=>{if(o!==a){const l=r.get(i);l&&queueMicrotask(()=>l.forEach(h=>h())),a=o}}})},s=(i,c)=>{let a=r.get(i);a||(a=[],r.set(i,a)),a.push(c)};return e>0&&Object.defineProperty(t,"app",{get:()=>u(0).store,enumerable:!1,configurable:!1}),{store:t,define:n,watch:s}},g=(e,t,r)=>{if(t==="toggle"){let n;return()=>{if(!n){const s=e.firstElementChild;s&&s.tagName==="TEMPLATE"&&d(s,"m-slot")?(n=s.content.childNodes,e.innerHTML=""):n=e.childNodes}r()?e.append(...n):e.innerHTML=""}}if(t==="switch"){let n=f(e,"match"),s,i,c=o=>s.get(o)??s.set(o,[]).get(o),a;return()=>{if(!s){s=new Map,i=[];for(const o of e.childNodes)if(o.nodeType===1&&o.tagName==="TEMPLATE"&&d(o,"m-slot")){for(const l of o.content.childNodes)l.nodeType===1&&d(l,"slot")?c(f(l,"slot")).push(l):i.push(l);o.remove()}else n?c(n).push(o):i.push(o)}a=r(),e.innerHTML="",e.append(...s.has(a)?s.get(a):i)}}if(t&&t.length>2&&t.startsWith("[")&&t.endsWith("]")){let n=t.slice(1,-1),s=e.parentElement;return s.tagName==="M-GROUP"&&(s=s.previousElementSibling),()=>{const i=r();i===!1?s.removeAttribute(n):(n==="class"||n==="style")&&i&&typeof i=="object"?s.setAttribute(n,n==="class"?$cx(i):$styleToCSS(i)):s.setAttribute(n,i===!0?"":""+i)}}return()=>e.textContent=""+r()},p=e=>{const t=e.indexOf(":");if(t>0)return[Number(e.slice(0,t)),e.slice(t+1)];throw new Error("Invalid state key")};customElements.define("m-state",class extends HTMLElement{connectedCallback(){const e=this,t=f(e,"key");if(t){const r=u(Number(f(e,"fc")));r.watch(t,g(e,f(e,"mode"),()=>r.store[t]));return}}}),Object.assign(window,{$state:e=>e!==void 0?u(e).store:void 0,$defineState:(e,t)=>{const[r,n]=p(e);u(r).define(n,t)},$defineComputed:(e,t,r)=>{const n=document.querySelector("m-state[computed='"+e+"']");if(n){const s=u(Number(f(n,"fc"))).store,i=g(n,f(n,"mode"),t.bind(s));for(const c of r){const[a,o]=p(c);u(a).watch(o,i)}}}});`;
55
10
  var SUSPENSE_JS = `const n={},o=e=>e.getAttribute("chunk-id");c("m-portal",e=>{n[o(e)]=e}),c("m-chunk",e=>{const t=o(e),s=n[t];s&&setTimeout(()=>{s.replaceWith(...e.firstChild.content.childNodes),delete n[t],e.remove()})});function c(e,t){customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}})}`;
56
11
  var UTILS_JS = {
57
- /** cx.js (239 bytes) */
58
- cx: `let cx=(()=>{var n=e=>typeof e=="string",o=e=>typeof e=="object"&&e!==null;function t(e){return n(e)?e:o(e)?Array.isArray(e)?e.map(t).filter(Boolean).join(" "):Object.entries(e).filter(([,r])=>!!r).map(([r])=>r).join(" "):""}return t;})();`,
59
- /** styleToCSS.js (1203 bytes) */
60
- styleToCSS: `let styleToCSS=(()=>{var c=new Set(["animation-iteration-count","aspect-ratio","border-image-outset","border-image-slice","border-image-width","box-flex-group","box-flex","box-ordinal-group","column-count","columns","fill-opacity","flex-grow","flex-negative","flex-order","flex-positive","flex-shrink","flex","flood-opacity","font-weight","grid-area","grid-column-end","grid-column-span","grid-column-start","grid-column","grid-row-end","grid-row-span","grid-row-start","grid-row","line-clamp","line-height","opacity","order","orphans","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width","tab-size","widows","z-index","zoom"]),s=e=>typeof e=="string",u=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,r=>r.charAt(0)+"-"+r.charAt(1).toLowerCase());function l(e){if(s(e))return e;if(u(e)){let r="";for(let[o,t]of Array.isArray(e)?e:Object.entries(e)){if(t==null||t===!1||Number.isNaN(t)||!s(o))return"";let n=f(o),i=typeof t=="number"?c.has(n)?""+t:t+"px":a(""+t);r+=(r!==""?";":"")+a(n)+":"+(n==="content"?JSON.stringify(i):i)}return r}return""}function a(e){return e.replace(/["<>]/g,r=>r==="<"?"&lt;":r===">"?"&gt;":"'")}return l;})();`,
61
- /** event.js (169 bytes) */
62
- event: `let w=window;w.$emit=(evt,el,fn,fc)=>fn.call(w.$state?.(fc)??el,evt);w.$onsubmit=(evt,el,fn,fc)=>{evt.preventDefault();fn.call(w.$state?.(fc)??el,new FormData(el),evt)};`
12
+ /** cx.js (225 bytes) */
13
+ cx: `var n=e=>typeof e=="string",o=e=>typeof e=="object"&&e!==null;function t(e){return n(e)?e:o(e)?Array.isArray(e)?e.map(t).filter(Boolean).join(" "):Object.entries(e).filter(([,r])=>!!r).map(([r])=>r).join(" "):""}window.$cx=t;`,
14
+ /** styleToCSS.js (1168 bytes) */
15
+ styleToCSS: `var a=new Set(["animation-iteration-count","aspect-ratio","border-image-outset","border-image-slice","border-image-width","box-flex-group","box-flex","box-ordinal-group","column-count","columns","fill-opacity","flex-grow","flex-negative","flex-order","flex-positive","flex-shrink","flex","flood-opacity","font-weight","grid-area","grid-column-end","grid-column-span","grid-column-start","grid-column","grid-row-end","grid-row-span","grid-row-start","grid-row","line-clamp","line-height","opacity","order","orphans","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width","tab-size","widows","z-index","zoom"]),o=e=>typeof e=="string",u=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,r=>r.charAt(0)+"-"+r.charAt(1).toLowerCase());function p(e){if(o(e))return e;if(u(e)){let r="";for(let[i,t]of Array.isArray(e)?e:Object.entries(e))if(o(i)&&(o(t)||typeof t=="number")){let n=f(i),c=typeof t=="number"?a.has(n)?""+t:t+"px":s(""+t);r+=(r?";":"")+s(n)+":"+(n==="content"?JSON.stringify(c):c)}return r}return""}function s(e){return e.replace(/["<>]/g,r=>r==="<"?"&lt;":r===">"?"&gt;":"'")}window.$styleToCSS=p;`,
16
+ /** event.js (347 bytes) */
17
+ event: `var w=window;w.$emit=(e,fn,fc)=>fn.call(w.$state?.(fc)??e.target,e);w.$onsubmit=(e,fn,fc)=>{e.preventDefault();fn.call(w.$state?.(fc)??e.target,new FormData(e.target),e)};w.$onstage=()=>document.querySelectorAll("[onmount]").forEach(t=>{const k="onmount",j=t.getAttribute(k);t.removeAttribute(k);new Function("event",j)({type:"mount",target:t})});`
63
18
  };
64
19
 
65
20
  // runtime/utils.ts
@@ -130,35 +85,36 @@ function styleToCSS(style) {
130
85
  if (isObject(style)) {
131
86
  let css = "";
132
87
  for (const [k, v] of Array.isArray(style) ? style : Object.entries(style)) {
133
- if (v === null || v === void 0 || v === false || Number.isNaN(v) || !isString(k)) return "";
134
- const cssKey = toHyphenCase(k);
135
- const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : escapeCSSText("" + v);
136
- css += (css !== "" ? ";" : "") + escapeCSSText(cssKey) + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
88
+ if (isString(k) && (isString(v) || typeof v === "number")) {
89
+ const cssKey = toHyphenCase(k);
90
+ const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : escapeCSSText("" + v);
91
+ css += (css ? ";" : "") + escapeCSSText(cssKey) + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
92
+ }
137
93
  }
138
94
  return css;
139
95
  }
140
96
  return "";
141
97
  }
142
- function escapeCSSText(str) {
143
- return str.replace(/["<>]/g, (m) => {
98
+ function escapeCSSText(str2) {
99
+ return str2.replace(/["<>]/g, (m) => {
144
100
  if (m === "<") return "&lt;";
145
101
  if (m === ">") return "&gt;";
146
102
  return "'";
147
103
  });
148
104
  }
149
105
  var regexpHtmlSafe = /["'&<>]/;
150
- function escapeHTML(str) {
151
- if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str);
152
- const match = regexpHtmlSafe.exec(str);
106
+ function escapeHTML(str2) {
107
+ if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str2);
108
+ const match = regexpHtmlSafe.exec(str2);
153
109
  if (!match) {
154
- return str;
110
+ return str2;
155
111
  }
156
112
  let escape;
157
113
  let index;
158
114
  let lastIndex = 0;
159
115
  let html2 = "";
160
- for (index = match.index; index < str.length; index++) {
161
- switch (str.charCodeAt(index)) {
116
+ for (index = match.index; index < str2.length; index++) {
117
+ switch (str2.charCodeAt(index)) {
162
118
  case 34:
163
119
  escape = "&quot;";
164
120
  break;
@@ -178,21 +134,66 @@ function escapeHTML(str) {
178
134
  continue;
179
135
  }
180
136
  if (lastIndex !== index) {
181
- html2 += str.slice(lastIndex, index);
137
+ html2 += str2.slice(lastIndex, index);
182
138
  }
183
139
  lastIndex = index + 1;
184
140
  html2 += escape;
185
141
  }
186
- return lastIndex !== index ? html2 + str.slice(lastIndex, index) : html2;
142
+ return lastIndex !== index ? html2 + str2.slice(lastIndex, index) : html2;
187
143
  }
188
144
 
189
145
  // render.ts
190
146
  var encoder = new TextEncoder();
191
147
  var regexpHtmlTag = /^[a-z][\w\-$]*$/;
192
148
  var selfClosingTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
149
+ var cdn = "https://raw.esm.sh";
193
150
  var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
194
151
  var hashCode = (s) => [...s].reduce((hash, c) => Math.imul(31, hash) + c.charCodeAt(0) | 0, 0);
195
- var toAttrStringLit = (str) => '"' + escapeHTML(str) + '"';
152
+ var toAttrStringLit = (str2) => '"' + escapeHTML(str2) + '"';
153
+ var str = (v, str2) => v !== void 0 ? str2(v) : "";
154
+ var collectDeps;
155
+ function createThis(fc, appState, context, request) {
156
+ const computed = (fn) => {
157
+ const deps = /* @__PURE__ */ Object.create(null);
158
+ collectDeps = (fc2, key, value2) => deps[fc2 + ":" + key] = value2;
159
+ const value = fn.call(thisProxy);
160
+ collectDeps = void 0;
161
+ if (value instanceof Promise || Object.keys(deps).length === 0) return value;
162
+ return [$computed, { value, deps, fc, fn }, $vnode];
163
+ };
164
+ const thisProxy = new Proxy(/* @__PURE__ */ Object.create(null), {
165
+ get(target, key, receiver) {
166
+ switch (key) {
167
+ case "app":
168
+ return appState;
169
+ case "context":
170
+ return context ?? {};
171
+ case "request":
172
+ if (!request) {
173
+ throw new Error("request is not defined");
174
+ }
175
+ return request;
176
+ case "computed":
177
+ return computed;
178
+ default: {
179
+ const value = Reflect.get(target, key, receiver);
180
+ if (typeof key === "symbol") {
181
+ return value;
182
+ }
183
+ if (collectDeps) {
184
+ collectDeps(fc, key, value);
185
+ return value;
186
+ }
187
+ return [$state, { key, value, fc }, $vnode];
188
+ }
189
+ }
190
+ },
191
+ set(target, key, value, receiver) {
192
+ return Reflect.set(target, key, value, receiver);
193
+ }
194
+ });
195
+ return thisProxy;
196
+ }
196
197
  async function renderNode(rc, node, stripSlotProp) {
197
198
  const { write } = rc;
198
199
  switch (typeof node) {
@@ -207,7 +208,7 @@ async function renderNode(rc, node, stripSlotProp) {
207
208
  if (isVNode(node)) {
208
209
  const [tag, props] = node;
209
210
  const { stateStore } = rc;
210
- if (tag === $fragment || tag === "htmx") {
211
+ if (tag === $fragment) {
211
212
  if (props.children !== void 0) {
212
213
  await renderChildren(rc, props.children);
213
214
  }
@@ -230,10 +231,8 @@ async function renderNode(rc, node, stripSlotProp) {
230
231
  break;
231
232
  }
232
233
  if (tag === $computed) {
233
- const { deps, value, fn, fc } = props;
234
- write(
235
- '<m-state fc="' + fc + '" computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>"
236
- );
234
+ const { deps, value, fc } = props;
235
+ write('<m-state fc="' + fc + '" computed="' + rc.mcs.gen(node) + '">');
237
236
  if (value !== void 0) {
238
237
  write(escapeHTML("" + value));
239
238
  }
@@ -264,14 +263,14 @@ async function renderNode(rc, node, stripSlotProp) {
264
263
  if (tag === "toggle") {
265
264
  const { value: valueProp, children } = props;
266
265
  if (children !== void 0) {
267
- if (isVNode(valueProp) && valueProp[0] === $state || valueProp[0] === $computed) {
268
- const { key, deps, value, fn, fc } = valueProp[1];
266
+ if (isVNode(valueProp) && (valueProp[0] === $state || valueProp[0] === $computed)) {
267
+ const { key, deps, value, fc } = valueProp[1];
269
268
  write('<m-state mode="toggle" fc="' + fc + '" ');
270
269
  if (key) {
271
270
  write("key=" + toAttrStringLit(key) + ">");
272
271
  stateStore.set(fc + ":" + key, !!value);
273
272
  } else {
274
- write('computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>");
273
+ write('computed="' + rc.mcs.gen(valueProp) + '">');
275
274
  for (const [key2, value2] of Object.entries(deps)) {
276
275
  if (!stateStore.has(key2)) {
277
276
  stateStore.set(key2, value2);
@@ -300,14 +299,14 @@ async function renderNode(rc, node, stripSlotProp) {
300
299
  let computed;
301
300
  let valueOrDefault;
302
301
  if (isVNode(valueProp) && (valueProp[0] === $state || valueProp[0] === $computed)) {
303
- const { key, deps, value, fn, fc } = valueProp[1];
302
+ const { key, deps, value, fc } = valueProp[1];
304
303
  valueOrDefault = value ?? defaultValue;
305
304
  stateful = '<m-state mode="switch" fc="' + fc + '" ';
306
305
  if (key) {
307
306
  stateful += "key=" + toAttrStringLit(key) + ">";
308
307
  stateStore.set(fc + ":" + key, valueOrDefault);
309
308
  } else {
310
- stateful += 'computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>";
309
+ stateful += 'computed="' + rc.mcs.gen(valueProp) + '">';
311
310
  for (const [key2, value2] of Object.entries(deps)) {
312
311
  if (!stateStore.has(key2)) {
313
312
  stateStore.set(key2, value2);
@@ -358,49 +357,49 @@ async function renderNode(rc, node, stripSlotProp) {
358
357
  break;
359
358
  }
360
359
  if (typeof tag === "function") {
361
- const fcIndex = ++rc.index.fc;
360
+ const fcIndex = ++rc.status.fcIndex;
362
361
  const { rendering, placeholder, catch: catchFC, ...fcProps } = props ?? /* @__PURE__ */ Object.create(null);
363
362
  try {
364
- const v = tag.call(createState(fcIndex, rc.appState, rc.context, rc.request), fcProps);
363
+ const v = tag.call(createThis(fcIndex, rc.appState, rc.context, rc.request), fcProps);
365
364
  const { children } = fcProps;
366
365
  const fcSlots = children !== void 0 ? Array.isArray(children) ? isVNode(children) ? [children] : children : [children] : void 0;
367
- const eager = (rendering ?? tag.rendering) === "eager" || rc.eager;
368
366
  if (v instanceof Promise) {
369
- if (eager) {
370
- await renderNode({ ...rc, fcIndex, eager: true, fcSlots }, await v);
367
+ if ((rendering ?? tag.rendering) === "eager") {
368
+ await renderNode({ ...rc, fcIndex, fcSlots }, await v);
371
369
  } else {
372
- const chunkId = (rc.suspenses.length + 1).toString(36);
373
- rc.suspenses.push(v.then(async (c) => {
374
- write('<m-chunk chunk-id="' + chunkId + '"><template>');
375
- await renderNode({ ...rc, fcIndex, eager, fcSlots }, c);
370
+ const chunkIdAttr = 'chunk-id="' + (rc.status.chunkIndex++).toString(36) + '"';
371
+ rc.suspenses.push(v.then(async (node2) => {
372
+ write("<m-chunk " + chunkIdAttr + "><template>");
373
+ await renderNode({ ...rc, fcIndex, fcSlots }, node2);
376
374
  write("</template></m-chunk>");
377
375
  }));
378
- write('<m-portal chunk-id="' + chunkId + '">');
376
+ write("<m-portal " + chunkIdAttr + ">");
379
377
  if (placeholder) {
380
- await renderNode({ ...rc, fcIndex, eager: true }, placeholder);
378
+ await renderNode({ ...rc, fcIndex }, placeholder);
381
379
  }
382
380
  write("</m-portal>");
383
381
  }
384
- } else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
385
- for (const c of v) {
386
- await renderNode({ ...rc, fcIndex, eager, fcSlots }, c);
387
- }
388
382
  } else if (isObject(v) && Symbol.asyncIterator in v) {
389
- if (eager) {
383
+ if ((rendering ?? tag.rendering) === "eager") {
390
384
  for await (const c of v) {
391
- await renderNode({ ...rc, fcIndex, eager: true, fcSlots }, c);
385
+ await renderNode({ ...rc, fcIndex, fcSlots }, c);
392
386
  }
393
387
  } else {
394
388
  }
389
+ } else if (isObject(v) && Symbol.iterator in v && !isVNode(v)) {
390
+ for (const node2 of v) {
391
+ await renderNode({ ...rc, fcIndex, fcSlots }, node2);
392
+ }
395
393
  } else {
396
- await renderNode({ ...rc, fcIndex, eager, fcSlots }, v);
394
+ await renderNode({ ...rc, fcIndex, fcSlots }, v);
397
395
  }
398
396
  } catch (err) {
399
397
  if (err instanceof Error) {
400
398
  if (typeof catchFC === "function") {
401
- await renderNode({ ...rc, fcIndex, eager: true }, catchFC(err));
399
+ await renderNode({ ...rc, fcIndex }, catchFC(err));
402
400
  } else {
403
- write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.stack ?? err.message) + "</code></pre>");
401
+ write('<pre style="color:red;font-size:1rem"><code>' + escapeHTML(err.message) + "</code></pre>");
402
+ console.error(err);
404
403
  }
405
404
  }
406
405
  }
@@ -408,32 +407,31 @@ async function renderNode(rc, node, stripSlotProp) {
408
407
  }
409
408
  if (isString(tag) && regexpHtmlTag.test(tag)) {
410
409
  let buffer = "<" + tag;
411
- let propEffects = [];
412
- let onMountHandler;
410
+ let stateTags = "";
413
411
  for (let [propName, propValue] of Object.entries(props)) {
414
412
  if (propName === "children") {
415
413
  continue;
416
414
  }
417
415
  if (isVNode(propValue) && (propValue[0] === $state || propValue[0] === $computed)) {
418
- const { key, value, deps, fn, fc } = propValue[1];
416
+ const { key, value, deps, fc } = propValue[1];
419
417
  if (propName === "class") {
420
- rc.runtimeUtils.cx = true;
418
+ rc.status.cx = true;
421
419
  } else if (propName === "style") {
422
- rc.runtimeUtils.styleToCSS = true;
420
+ rc.status.styleToCSS = true;
423
421
  }
424
- propEffects.push("<m-state mode=" + toAttrStringLit("[" + propName + "]") + ' fc="' + fc + '" ');
422
+ stateTags += "<m-state mode=" + toAttrStringLit("[" + propName + "]") + ' fc="' + fc + '" ';
425
423
  if (key) {
426
- propEffects.push("key=" + toAttrStringLit(key) + ">");
424
+ stateTags += "key=" + toAttrStringLit(key) + ">";
427
425
  stateStore.set(fc + ":" + key, value);
428
426
  } else {
429
- propEffects.push('computed><script type="computed">$(' + fn + ", " + JSON.stringify(Object.keys(deps)) + ")<\/script>");
427
+ stateTags += 'computed="' + rc.mcs.gen(propValue) + '">';
430
428
  for (const [key2, value2] of Object.entries(deps)) {
431
429
  if (!stateStore.has(key2)) {
432
430
  stateStore.set(key2, value2);
433
431
  }
434
432
  }
435
433
  }
436
- propEffects.push("</m-state>");
434
+ stateTags += "</m-state>";
437
435
  propValue = value;
438
436
  }
439
437
  switch (propName) {
@@ -469,19 +467,19 @@ async function renderNode(rc, node, stripSlotProp) {
469
467
  if (pseudoStyles.length > 0 || atRuleStyles.length > 0 || nestingStyles.length > 0) {
470
468
  let css = "";
471
469
  let raw = "";
472
- let styleIds;
473
470
  let id;
474
471
  let cssSelector;
472
+ let cssIds;
475
473
  if (style.length > 0) {
476
474
  css = styleToCSS(style);
477
475
  raw += css + "|";
478
476
  }
479
477
  raw += [pseudoStyles, atRuleStyles, nestingStyles].flat(1).map(([k, v]) => k + ">" + v).join("|");
480
- styleIds = rc.styleIds ?? (rc.styleIds = /* @__PURE__ */ new Set());
481
478
  id = hashCode(raw).toString(36);
482
479
  cssSelector = "[data-css-" + id + "]";
483
- if (!styleIds.has(id)) {
484
- styleIds.add(id);
480
+ cssIds = rc.cssIds ?? (rc.cssIds = /* @__PURE__ */ new Set());
481
+ if (!cssIds.has(id)) {
482
+ cssIds.add(id);
485
483
  if (css) {
486
484
  css = cssSelector + "{" + css + "}";
487
485
  }
@@ -504,14 +502,13 @@ async function renderNode(rc, node, stripSlotProp) {
504
502
  break;
505
503
  case "onMount":
506
504
  if (typeof propValue === "function") {
507
- onMountHandler = propValue;
505
+ rc.status.onmount++;
506
+ buffer += ' onmount="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
508
507
  }
509
508
  break;
510
509
  case "action":
511
510
  if (typeof propValue === "function" && tag === "form") {
512
- const fn = "$MF_" + (rc.index.mf++).toString(36);
513
- write("<script>function " + fn + "(fd){(" + propValue.toString() + ")(fd)}<\/script>");
514
- buffer += ' onsubmit="$onsubmit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
511
+ buffer += ' onsubmit="$onsubmit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
515
512
  } else if (isString(propValue)) {
516
513
  buffer += " action=" + toAttrStringLit(propValue);
517
514
  }
@@ -525,9 +522,7 @@ async function renderNode(rc, node, stripSlotProp) {
525
522
  if (regexpHtmlTag.test(propName) && propValue !== void 0) {
526
523
  if (propName.startsWith("on")) {
527
524
  if (typeof propValue === "function") {
528
- const fn = "$MF_" + (rc.index.mf++).toString(36);
529
- write("<script>function " + fn + "(e){(" + propValue.toString() + ")(e)}<\/script>");
530
- buffer += " " + propName.toLowerCase() + '="$emit(event,this,' + fn + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + ')"';
525
+ buffer += " " + propName.toLowerCase() + '="$emit(event,' + rc.mfs.gen(propValue) + str(rc.fcIndex, (i) => "," + i) + ')"';
531
526
  }
532
527
  } else if (typeof propValue === "boolean") {
533
528
  if (propValue) {
@@ -541,8 +536,8 @@ async function renderNode(rc, node, stripSlotProp) {
541
536
  }
542
537
  write(buffer + ">");
543
538
  if (!selfClosingTags.has(tag)) {
544
- if (propEffects.length > 0) {
545
- write(propEffects.join(""));
539
+ if (stateTags) {
540
+ write(stateTags);
546
541
  }
547
542
  if (props.innerHTML) {
548
543
  write(props.innerHTML);
@@ -550,16 +545,12 @@ async function renderNode(rc, node, stripSlotProp) {
550
545
  await renderChildren(rc, props.children);
551
546
  }
552
547
  write("</" + tag + ">");
553
- } else if (propEffects.length > 0) {
554
- write("<m-group>" + propEffects.join("") + "</m-group>");
555
- }
556
- if (onMountHandler) {
557
- rc.index.mf++;
558
- write(
559
- '<script>{const target=document.currentScript.previousElementSibling;addEventListener("load",()=>$emit({type:"mount",currentTarget:target,target},target,' + onMountHandler.toString() + (rc.fcIndex !== void 0 ? "," + rc.fcIndex : "") + "))}<\/script>"
560
- );
548
+ } else if (stateTags) {
549
+ write("<m-group>" + stateTags + "</m-group>");
561
550
  }
562
551
  }
552
+ } else if (Array.isArray(node) && node.length > 0) {
553
+ renderChildren(rc, node, stripSlotProp);
563
554
  }
564
555
  break;
565
556
  }
@@ -573,6 +564,21 @@ async function renderChildren(rc, children, stripSlotProp) {
573
564
  await renderNode(rc, children, stripSlotProp);
574
565
  }
575
566
  }
567
+ var IdGenImpl = class extends Map {
568
+ constructor(_prefix) {
569
+ super();
570
+ this._prefix = _prefix;
571
+ }
572
+ _id = 0;
573
+ gen(v) {
574
+ let id = this.get(v);
575
+ if (id === void 0) {
576
+ id = this._prefix + (this._id++).toString(36);
577
+ this.set(v, id);
578
+ }
579
+ return id;
580
+ }
581
+ };
576
582
  function render(node, renderOptions = {}) {
577
583
  const { request, status, headers: headersInit } = renderOptions;
578
584
  const headers = new Headers();
@@ -596,64 +602,99 @@ function render(node, renderOptions = {}) {
596
602
  return new Response(
597
603
  new ReadableStream({
598
604
  async start(controller) {
599
- const { appState: appStateInit, context, rendering, htmx } = renderOptions;
605
+ const { appState: appStateInit, context, htmx } = renderOptions;
600
606
  const write = (chunk) => controller.enqueue(encoder.encode(chunk));
601
- const appState = createState(0, null, context, request);
607
+ const appState = createThis(0, null, context, request);
602
608
  const stateStore = /* @__PURE__ */ new Map();
603
609
  const suspenses = [];
604
- const rtComponents = { cx: false, styleToCSS: false };
605
610
  const rc = {
606
611
  write,
612
+ suspenses,
607
613
  context,
608
614
  request,
609
615
  appState,
610
616
  stateStore,
611
- suspenses,
612
- runtimeUtils: rtComponents,
613
- index: { fc: 0, mf: 0 },
614
- eager: rendering === "eager"
617
+ mcs: new IdGenImpl("$MC_"),
618
+ mfs: new IdGenImpl("$MF_"),
619
+ status: { fcIndex: 0, chunkIndex: 0, onmount: 0 }
615
620
  };
616
- if (appStateInit) {
617
- for (const [key, value] of Object.entries(appStateInit)) {
618
- if (value !== void 0) {
619
- appState[key] = value;
620
- }
621
- }
622
- }
623
- try {
624
- write("<!DOCTYPE html>");
625
- await renderNode(rc, node);
621
+ const runtimeJS = {};
622
+ const finalize = async () => {
626
623
  let js = "";
627
- if (rc.index.mf > 0) {
624
+ if (rc.status.cx && !runtimeJS.cx) {
625
+ runtimeJS.cx = true;
626
+ js += UTILS_JS.cx;
627
+ }
628
+ if (rc.status.styleToCSS && !runtimeJS.styleToCSS) {
629
+ runtimeJS.styleToCSS = true;
630
+ js += UTILS_JS.styleToCSS;
631
+ }
632
+ if (rc.mfs.size > 0 && !runtimeJS.event) {
633
+ runtimeJS.event = true;
628
634
  js += UTILS_JS.event;
629
635
  }
636
+ if (suspenses.length > 0 && !runtimeJS.suspense) {
637
+ runtimeJS.suspense = true;
638
+ js += SUSPENSE_JS;
639
+ }
640
+ if (stateStore.size > 0 && !runtimeJS.state) {
641
+ runtimeJS.state = true;
642
+ js += STATE_JS;
643
+ }
644
+ if (js) {
645
+ write("<script>/* runtime.js (generated by mono-jsx) */(()=>{" + js + "})()<\/script>");
646
+ }
647
+ js = "";
648
+ if (rc.mfs.size > 0) {
649
+ for (const [fn, fname] of rc.mfs.entries()) {
650
+ js += "function " + fname + "(){(" + fn.toString() + ").apply(this,arguments)};";
651
+ }
652
+ rc.mfs.clear();
653
+ }
630
654
  if (stateStore.size > 0) {
631
- if (rtComponents.cx) {
632
- js += UTILS_JS.cx;
655
+ for (const [key, value] of stateStore.entries()) {
656
+ js += "$defineState(" + JSON.stringify(key) + (value !== void 0 ? "," + JSON.stringify(value) : "") + ");";
633
657
  }
634
- if (rtComponents.styleToCSS) {
635
- js += UTILS_JS.styleToCSS;
658
+ stateStore.clear();
659
+ }
660
+ if (rc.mcs.size > 0) {
661
+ for (const [vnode, fname] of rc.mcs.entries()) {
662
+ const { fn, deps } = vnode[1];
663
+ js += '$defineComputed("' + fname + '",function(){return(' + fn.toString() + ").call(this)}," + JSON.stringify(Object.keys(deps)) + ");";
636
664
  }
637
- js += STATE_JS;
638
- js += "for(let[k,v]of" + JSON.stringify(Array.from(stateStore.entries()).map((e) => e[1] === void 0 ? [e[0]] : e)) + ")$defineState(k,v);";
665
+ rc.mcs.clear();
639
666
  }
640
- if (suspenses.length > 0) {
641
- js += SUSPENSE_JS;
667
+ if (rc.status.onmount > 0) {
668
+ rc.status.onmount = 0;
669
+ js += "$onstage();";
642
670
  }
643
671
  if (js) {
644
- write("<script>(()=>{" + js + "})()<\/script>");
672
+ write("<script>/* app.js (generated by mono-jsx) */" + js + "<\/script>");
645
673
  }
674
+ if (suspenses.length > 0) {
675
+ await Promise.all(suspenses.splice(0, suspenses.length));
676
+ await finalize();
677
+ }
678
+ };
679
+ if (appStateInit) {
680
+ for (const [key, value] of Object.entries(appStateInit)) {
681
+ if (value !== void 0) {
682
+ appState[key] = value;
683
+ }
684
+ }
685
+ }
686
+ try {
687
+ write("<!DOCTYPE html>");
688
+ await renderNode(rc, node);
646
689
  if (htmx) {
647
- write(`<script src="https://raw.esm.sh/htmx.org${htmx === true ? "" : escapeHTML("@" + htmx)}/dist/htmx.min.js"><\/script>`);
690
+ write(`<script src="${cdn}/htmx.org${htmx === true ? "" : escapeHTML("@" + htmx)}/dist/htmx.min.js"><\/script>`);
648
691
  for (const [key, value] of Object.entries(renderOptions)) {
649
692
  if (key.startsWith("htmx-ext-") && value) {
650
- write(`<script src="https://raw.esm.sh/${key}${value === true ? "" : escapeHTML("@" + value)}"><\/script>`);
693
+ write(`<script src="${cdn}/${key}${value === true ? "" : escapeHTML("@" + value)}"><\/script>`);
651
694
  }
652
695
  }
653
696
  }
654
- if (suspenses.length > 0) {
655
- await Promise.all(suspenses);
656
- }
697
+ await finalize();
657
698
  } finally {
658
699
  controller.close();
659
700
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "`<html>` as a `Response`.",
5
5
  "type": "module",
6
6
  "module": "./index.mjs",
package/types/html.d.ts CHANGED
@@ -822,7 +822,7 @@ export namespace HTML {
822
822
 
823
823
  interface EventAttributes<T extends EventTarget> {
824
824
  // mono-jsx specific
825
- onMount?: (e: { type: "mount"; currentTarget: T; target: T }) => void | Promise<void>;
825
+ onMount?: (event: { type: "mount"; target: T }) => void | Promise<void>;
826
826
 
827
827
  // Input Events
828
828
  onBeforeInput?: EventHandler<Event, T>;
package/types/jsx.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import type * as Mono from "./mono.d.ts";
4
4
  import type { HTML } from "./html.d.ts";
5
5
 
6
- export type ChildType = VNode | string | number | bigint | boolean | null;
6
+ export type ChildType = VNode | VNode[] | string | number | bigint | boolean | null;
7
7
 
8
8
  export type VNode = readonly [
9
9
  tag: string | symbol | FC<any>,
@@ -13,14 +13,9 @@ export type VNode = readonly [
13
13
 
14
14
  export interface FC<P = {}> {
15
15
  (props: P): ChildType | Promise<ChildType> | Generator<ChildType> | AsyncGenerator<ChildType>;
16
- displayName?: string;
17
16
  rendering?: string;
18
17
  }
19
18
 
20
- export interface TC {
21
- (strings: TemplateStringsArray, ...values: unknown[]): VNode;
22
- }
23
-
24
19
  declare global {
25
20
  namespace JSX {
26
21
  type ElementType<P = any> =
@@ -28,9 +23,9 @@ declare global {
28
23
  [K in keyof IntrinsicElements]: P extends IntrinsicElements[K] ? K : never;
29
24
  }[keyof IntrinsicElements]
30
25
  | FC<P>;
26
+ type Raw = (strings: TemplateStringsArray, ...values: unknown[]) => JSX.Element;
31
27
  interface Element extends VNode, Response {}
32
28
  interface IntrinsicAttributes extends Mono.BaseAttributes, Mono.AsyncComponentAttributes {}
33
29
  interface IntrinsicElements extends HTML.Elements, HTML.SVGElements, Mono.Elements {}
34
30
  }
35
- var html: TC, css: TC, js: TC;
36
31
  }
package/types/mono.d.ts CHANGED
@@ -66,45 +66,73 @@ export interface CSSProperties extends BaseCSSProperties, AtRuleCSSProperties, P
66
66
  [key: `&${" " | "." | "["}${string}`]: CSSProperties;
67
67
  }
68
68
 
69
- export type ChildType = JSX.Element | string | number | bigint | boolean | null;
69
+ export type ChildType = JSX.Element | JSX.Element[] | string | number | bigint | boolean | null;
70
70
 
71
71
  export interface BaseAttributes {
72
- children?: ChildType | (ChildType)[];
72
+ children?: ChildType | ChildType[];
73
73
  key?: string | number;
74
74
  slot?: string;
75
75
  }
76
76
 
77
77
  export interface AsyncComponentAttributes {
78
- rendering?: "eager";
79
- placeholder?: JSX.Element | string;
78
+ /**
79
+ * Error handler.
80
+ */
80
81
  catch?: (err: any) => JSX.Element;
82
+ /**
83
+ * Loading spinner.
84
+ */
85
+ placeholder?: JSX.Element | string;
86
+ /**
87
+ * Rendering mode
88
+ * - `eager`: render immediately
89
+ */
90
+ rendering?: "eager";
81
91
  }
82
92
 
83
93
  export interface Elements {
84
- toggle: {
85
- value: boolean;
94
+ /**
95
+ * The `<toggle>` element is a custom element that represents a toggle switch.
96
+ */
97
+ toggle: BaseAttributes & {
98
+ value?: boolean | string | number | null;
86
99
  };
87
- switch: {
100
+ /**
101
+ * The `<switch>` element is a custom element that represents a switch.
102
+ */
103
+ switch: BaseAttributes & {
88
104
  value?: string;
89
105
  defaultValue?: string;
90
106
  };
91
- cache: {
92
- /** The cache key is used to identify the cache. */
93
- key: string;
94
- /** The `etag` (or **entity tag**) is an identifier for a specific version of a rendering cache. */
95
- etag?: string;
96
- /** The `max-age=N` prop indicates that the cache remains fresh until N seconds after the cache is generated. */
97
- maxAge?: number;
98
- /** The `stale-while-revalidate` prop indicates that the cache could reuse a stale rendering while it revalidates it to a cache. */
99
- swr?: number;
100
- };
101
107
  }
102
108
 
103
109
  declare global {
104
- type FC<S = Record<string, unknown>, AppState = Record<string, unknown>, Context = Record<string, unknown>> = {
110
+ /**
111
+ * The `html` function is used to create XSS-unsafe HTML elements.
112
+ */
113
+ var html: JSX.Raw, css: JSX.Raw, js: JSX.Raw;
114
+
115
+ /**
116
+ * mono-jsx `this` object that is bound to the function component.
117
+ */
118
+ type FC<State = {}, AppState = {}, Context = {}> = {
119
+ /**
120
+ * Application state.
121
+ * This is the state that is shared across the entire application.
122
+ */
105
123
  readonly app: AppState;
124
+ /**
125
+ * Context object.
126
+ */
106
127
  readonly context: Context;
128
+ /**
129
+ * Current request object.
130
+ */
107
131
  readonly request: Request;
132
+ /**
133
+ * The `computed` function is used to create a computed property.
134
+ * It takes a function that returns a value and returns the value.
135
+ */
108
136
  readonly computed: <V = unknown>(computeFn: () => V) => V;
109
- } & Omit<S, "app" | "context" | "request" | "computed">;
137
+ } & Omit<State, "app" | "context" | "request" | "computed">;
110
138
  }
package/types/render.d.ts CHANGED
@@ -41,11 +41,6 @@ export interface RenderOptions extends Partial<HtmxExts> {
41
41
  lastModified?: string;
42
42
  setCookie?: string;
43
43
  };
44
- /**
45
- * Rendering mode.
46
- * - **eager**: Render the component immediately.
47
- */
48
- rendering?: "eager";
49
44
  /**
50
45
  * Install htmx script with the given version.
51
46
  * @see https://htmx.org/