mono-jsx 0.6.5 → 0.6.7
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 +91 -23
- package/index.mjs +1 -1
- package/jsx-runtime.mjs +257 -185
- package/package.json +1 -1
- package/setup.mjs +2 -0
- package/types/html.d.ts +4 -5
- package/types/mono.d.ts +63 -19
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in
|
|
|
14
14
|
- 🥷 [htmx](#using-htmx) integration
|
|
15
15
|
- 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
16
16
|
|
|
17
|
+
Playground: https://val.town/x/ije/mono-jsx
|
|
18
|
+
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
19
21
|
mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, and Cloudflare Workers.
|
|
@@ -30,7 +32,7 @@ deno add npm:mono-jsx
|
|
|
30
32
|
bun add mono-jsx
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
### Setup JSX Runtime
|
|
34
36
|
|
|
35
37
|
To use mono-jsx as your JSX runtime, add the following configuration to your `tsconfig.json` (or `deno.json` for Deno):
|
|
36
38
|
|
|
@@ -43,12 +45,6 @@ To use mono-jsx as your JSX runtime, add the following configuration to your `ts
|
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
Alternatively, you can use a pragma directive in your JSX file:
|
|
47
|
-
|
|
48
|
-
```js
|
|
49
|
-
/** @jsxImportSource mono-jsx */
|
|
50
|
-
```
|
|
51
|
-
|
|
52
48
|
You can also run `mono-jsx setup` to automatically add the configuration to your project:
|
|
53
49
|
|
|
54
50
|
```bash
|
|
@@ -62,6 +58,18 @@ deno run -A npm:mono-jsx setup
|
|
|
62
58
|
bunx mono-jsx setup
|
|
63
59
|
```
|
|
64
60
|
|
|
61
|
+
### Zero Configuration
|
|
62
|
+
|
|
63
|
+
Alternatively, you can use `@jsxImportSource` pragma directive without installation mono-jsx(no package.json/tsconfig/node_modules), the runtime(deno/bun) automatically installs mono-jsx to your computer:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// Deno, Valtown
|
|
67
|
+
/** @jsxImportSource https://esm.sh/mono-jsx */
|
|
68
|
+
|
|
69
|
+
// Bun
|
|
70
|
+
/** @jsxImportSource mono-jsx */
|
|
71
|
+
```
|
|
72
|
+
|
|
65
73
|
## Usage
|
|
66
74
|
|
|
67
75
|
mono-jsx allows you to return an `<html>` JSX element as a `Response` object in the `fetch` handler:
|
|
@@ -82,7 +90,7 @@ For Deno/Bun users, you can run the `app.tsx` directly:
|
|
|
82
90
|
|
|
83
91
|
```bash
|
|
84
92
|
deno serve app.tsx
|
|
85
|
-
bun
|
|
93
|
+
bun app.tsx
|
|
86
94
|
```
|
|
87
95
|
|
|
88
96
|
If you're building a web app with [Cloudflare Workers](https://developers.cloudflare.com/workers/wrangler/commands/#dev), use `wrangler dev` to start your app in development mode:
|
|
@@ -194,7 +202,7 @@ function App() {
|
|
|
194
202
|
|
|
195
203
|
### Using `html` Tag Function
|
|
196
204
|
|
|
197
|
-
mono-jsx provides an `html` tag function to render raw HTML in JSX instead of React's `dangerouslySetInnerHTML
|
|
205
|
+
mono-jsx provides an `html` tag function to render raw HTML in JSX instead of React's `dangerouslySetInnerHTML` property.
|
|
198
206
|
|
|
199
207
|
```tsx
|
|
200
208
|
function App() {
|
|
@@ -202,7 +210,7 @@ function App() {
|
|
|
202
210
|
}
|
|
203
211
|
```
|
|
204
212
|
|
|
205
|
-
The `html`
|
|
213
|
+
The `html` function is globally available without importing. You can also use `css` and `js` functions for CSS and JavaScript:
|
|
206
214
|
|
|
207
215
|
```tsx
|
|
208
216
|
function App() {
|
|
@@ -232,8 +240,7 @@ function Button() {
|
|
|
232
240
|
}
|
|
233
241
|
```
|
|
234
242
|
|
|
235
|
-
|
|
236
|
-
> Event handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**
|
|
243
|
+
Event handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**
|
|
237
244
|
|
|
238
245
|
```tsx
|
|
239
246
|
import { doSomething } from "some-library";
|
|
@@ -418,6 +425,9 @@ function App(this: FC<{ input: string }>) {
|
|
|
418
425
|
}
|
|
419
426
|
```
|
|
420
427
|
|
|
428
|
+
> [!TIP]
|
|
429
|
+
> You can use `this.$` as a shorthand for `this.computed` to create computed signals.
|
|
430
|
+
|
|
421
431
|
### Using Effects
|
|
422
432
|
|
|
423
433
|
You can use `this.effect` to create side effects based on signals. The effect will run whenever the signal changes:
|
|
@@ -475,7 +485,7 @@ function App(this: FC<{ show: boolean }>) {
|
|
|
475
485
|
|
|
476
486
|
### Using `<toggle>` Element with Signals
|
|
477
487
|
|
|
478
|
-
The `<toggle>` element conditionally renders content based on the `show` prop
|
|
488
|
+
The `<toggle>` element conditionally renders content based on the `show` prop. You can use signals to control the visibility of the content on the client side.
|
|
479
489
|
|
|
480
490
|
```tsx
|
|
481
491
|
function App(this: FC<{ show: boolean }>) {
|
|
@@ -501,7 +511,7 @@ function App(this: FC<{ show: boolean }>) {
|
|
|
501
511
|
|
|
502
512
|
### Using `<switch>` Element with Signals
|
|
503
513
|
|
|
504
|
-
The `<switch>` element renders different content based on the value
|
|
514
|
+
The `<switch>` element renders different content based on the `value` prop. Elements with matching `slot` attributes are displayed when their value matches, otherwise default slots are shown. Like `<toggle>`, you can use signals to control the value on the client side.
|
|
505
515
|
|
|
506
516
|
```tsx
|
|
507
517
|
function App(this: FC<{ lang: "en" | "zh" | "🙂" }>) {
|
|
@@ -619,7 +629,7 @@ mono-jsx binds a scoped signals object to `this` of your component functions. Th
|
|
|
619
629
|
|
|
620
630
|
The `this` object has the following built-in properties:
|
|
621
631
|
|
|
622
|
-
- `app`: The app signals
|
|
632
|
+
- `app`: The app global signals..
|
|
623
633
|
- `context`: The context defined on the root `<html>` element.
|
|
624
634
|
- `request`: The request object from the `fetch` handler.
|
|
625
635
|
- `refs`: A map of refs defined in the component.
|
|
@@ -627,12 +637,13 @@ The `this` object has the following built-in properties:
|
|
|
627
637
|
- `effect`: A method to create side effects.
|
|
628
638
|
|
|
629
639
|
```ts
|
|
630
|
-
type FC<Signals = {}, AppSignals = {}, Context = {}> = {
|
|
631
|
-
readonly app: AppSignals;
|
|
640
|
+
type FC<Signals = {}, AppSignals = {}, Context = {}, Refs = {}, AppRefs = {}> = {
|
|
641
|
+
readonly app: AppSignals & { refs: AppRefs; url: WithParams<URL> }
|
|
632
642
|
readonly context: Context;
|
|
633
|
-
readonly request: Request
|
|
634
|
-
readonly refs:
|
|
643
|
+
readonly request: WithParams<Request>;
|
|
644
|
+
readonly refs: Refs;
|
|
635
645
|
readonly computed: <T = unknown>(fn: () => T) => T;
|
|
646
|
+
readonly $: FC["computed"];
|
|
636
647
|
readonly effect: (fn: () => void | (() => void)) => void;
|
|
637
648
|
} & Omit<Signals, "app" | "context" | "request" | "computed" | "effect">;
|
|
638
649
|
```
|
|
@@ -643,10 +654,10 @@ See the [Using Signals](#using-signals) section for more details on how to use s
|
|
|
643
654
|
|
|
644
655
|
### Using Refs
|
|
645
656
|
|
|
646
|
-
You can use `this.refs` to access refs in your components.
|
|
657
|
+
You can use `this.refs` to access refs in your components. Refs are defined using the `ref` attribute in JSX, and they allow you to access DOM elements directly. The `refs` object is a map of ref names to DOM elements.
|
|
647
658
|
|
|
648
659
|
```tsx
|
|
649
|
-
function App(this: FC) {
|
|
660
|
+
function App(this: Refs<FC, { input: HTMLInputElement }>) {
|
|
650
661
|
this.effect(() => {
|
|
651
662
|
this.refs.input?.addEventListener("input", (evt) => {
|
|
652
663
|
console.log("Input changed:", evt.target.value);
|
|
@@ -662,12 +673,55 @@ function App(this: FC) {
|
|
|
662
673
|
}
|
|
663
674
|
```
|
|
664
675
|
|
|
676
|
+
You can also use `this.app.refs` to access app-level refs:
|
|
677
|
+
|
|
678
|
+
```tsx
|
|
679
|
+
function Layout(this: Refs<FC, {}, { h1: HTMLH1Element }>) {
|
|
680
|
+
return (
|
|
681
|
+
<>
|
|
682
|
+
<header>
|
|
683
|
+
<h1 ref={this.refs.h1}>Welcome to mono-jsx!</h1>
|
|
684
|
+
</header>
|
|
685
|
+
<main>
|
|
686
|
+
<slot />
|
|
687
|
+
</main>
|
|
688
|
+
</>
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Element `<componet>` also support `ref` attribute, which allows you to control the component rendering manually. The `ref` will be a `ComponentElement` that has the `name`, `props`, and `refresh` properties:
|
|
694
|
+
|
|
695
|
+
- `name`: The name of the component to render.
|
|
696
|
+
- `props`: The props to pass to the component.
|
|
697
|
+
- `refresh`: A method to re-render the component with the current name and props.
|
|
698
|
+
|
|
699
|
+
```tsx
|
|
700
|
+
function App(this: Refs<FC, { component: ComponentElement }>) {
|
|
701
|
+
this.effect(() => {
|
|
702
|
+
// updating the component name and props will trigger a re-render of the component
|
|
703
|
+
this.refs.component.name = "Foo";
|
|
704
|
+
this.refs.component.props = {};
|
|
705
|
+
|
|
706
|
+
const timer = setInterval(() => {
|
|
707
|
+
// re-render the component
|
|
708
|
+
this.refs.component.refresh();
|
|
709
|
+
}, 1000);
|
|
710
|
+
return () => clearInterval(timer); // cleanup
|
|
711
|
+
});
|
|
712
|
+
return (
|
|
713
|
+
<div>
|
|
714
|
+
<component ref={this.refs.component} />
|
|
715
|
+
</div>
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
|
|
665
719
|
### Using Context
|
|
666
720
|
|
|
667
721
|
You can use the `context` property in `this` to access context values in your components. The context is defined on the root `<html>` element:
|
|
668
722
|
|
|
669
723
|
```tsx
|
|
670
|
-
function Dash(this: FC
|
|
724
|
+
function Dash(this: Context<FC, { auth: { uuid: string; name: string } }>) {
|
|
671
725
|
const { auth } = this.context;
|
|
672
726
|
return (
|
|
673
727
|
<div>
|
|
@@ -790,7 +844,7 @@ async function Lazy(this: FC<{ show: boolean }>, props: { url: string }) {
|
|
|
790
844
|
this.show = false;
|
|
791
845
|
return (
|
|
792
846
|
<div>
|
|
793
|
-
<toggle
|
|
847
|
+
<toggle show={this.show}>
|
|
794
848
|
<component name="Foo" props={{ /* props for the component */ }} placeholder={<p>Loading...</p>} />
|
|
795
849
|
</toggle>
|
|
796
850
|
<button onClick={() => this.show = true }>Load `Foo` Component</button>
|
|
@@ -912,6 +966,20 @@ function Post(this: FC) {
|
|
|
912
966
|
}
|
|
913
967
|
```
|
|
914
968
|
|
|
969
|
+
### Using `this.app.url` Signal
|
|
970
|
+
|
|
971
|
+
`this.app.url` is an app-level signal that contains the current route url and parameters. The `this.app.url` signal is automatically updated when the route changes, so you can use it to display the current URL in your components, or control view with `<toggle>` or `<switch>` elements:
|
|
972
|
+
|
|
973
|
+
```tsx
|
|
974
|
+
function App(this: FC) {
|
|
975
|
+
return (
|
|
976
|
+
<div>
|
|
977
|
+
<h1>Current Pathname: {this.$(() => this.app.url.pathname)}</h1>
|
|
978
|
+
</div>
|
|
979
|
+
)
|
|
980
|
+
}
|
|
981
|
+
```
|
|
982
|
+
|
|
915
983
|
### Navigation between Pages
|
|
916
984
|
|
|
917
985
|
To navigate between pages, you can use `<a>` elements with `href` attributes that match the defined routes. The router will intercept the click events of these links and fetch the corresponding route component without reloading the page:
|
package/index.mjs
CHANGED
package/jsx-runtime.mjs
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// runtime/index.ts
|
|
2
|
+
var EVENT = 1;
|
|
3
|
+
var CX = 2;
|
|
4
|
+
var STYLE = 4;
|
|
5
|
+
var RENDER_ATTR = 8;
|
|
6
|
+
var RENDER_TOGGLE = 16;
|
|
7
|
+
var RENDER_SWITCH = 32;
|
|
8
|
+
var SIGNALS = 64;
|
|
9
|
+
var SUSPENSE = 128;
|
|
10
|
+
var LAZY = 256;
|
|
11
|
+
var ROUTER = 512;
|
|
12
|
+
var EVENT_JS = `{var w=window;w.$emit=(e,f,s)=>f.call(w.$signals?.(s)??e.target,e.type==="mount"?e.target:e);w.$onsubmit=(e,f,s)=>{e.preventDefault();f.call(w.$signals?.(s)??e.target,new FormData(e.target),e)};}`;
|
|
13
|
+
var CX_JS = `{var n=e=>typeof e=="string"?e:typeof e=="object"&&e!==null?Array.isArray(e)?e.map(n).filter(Boolean).join(" "):Object.entries(e).filter(([,t])=>!!t).map(([t])=>t).join(" "):"";window.$cx=n;}`;
|
|
14
|
+
var STYLE_JS = `{var a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;var l=e=>typeof e=="string";var u=e=>e.replace(/[a-z][A-Z]/g,r=>r.charAt(0)+"-"+r.charAt(1).toLowerCase());var p=e=>{let r=[],t=[],n=new g;for(let[o,s]of Object.entries(e))switch(o.charCodeAt(0)){case 58:t.push(null,o+"{"+c(s)+"}");break;case 64:t.push(o+"{",null,"{"+c(s)+"}}");break;case 38:t.push(null,o.slice(1)+"{"+c(s)+"}");break;default:r.push([o,s])}return r.length>0&&(n.inline=c(r)),t.length>0&&(n.css=t),n},f=(e,r)=>{let{inline:t,css:n}=p(r);if(n){let o="data-css-",s="["+o+(Date.now()+Math.random()).toString(36).replace(".","")+"]";document.head.appendChild(document.createElement("style")).textContent=(t?s+"{"+t+"}":"")+n.map(i=>i===null?s:i).join(""),e.getAttributeNames().forEach(i=>i.startsWith(o)&&e.removeAttribute(i)),e.setAttribute(s.slice(1,-1),"")}else t&&e.setAttribute("style",t)},c=e=>{if(typeof e=="object"&&e!==null){let r="";for(let[t,n]of Array.isArray(e)?e:Object.entries(e))if(l(n)||typeof n=="number"){let o=u(t),s=typeof n=="number"?a.test(t)?""+n:n+"px":""+n;r+=(r?";":"")+o+":"+(o==="content"?JSON.stringify(s):s)}return r}return""},g=(()=>{function e(){}return e.prototype=Object.freeze(Object.create(null)),e})();window.$applyStyle=f;}`;
|
|
15
|
+
var RENDER_ATTR_JS = `{var s=(l,t,r)=>{let n=l.parentElement;return n.tagName==="M-GROUP"&&(n=n.previousElementSibling),()=>{let e=r();e===!1||e===null||e===void 0?n.removeAttribute(t):typeof e=="object"&&e!==null&&(t==="class"||t==="style"||t==="props")?t==="class"?n.setAttribute(t,$cx(e)):t==="style"?$applyStyle(n,e):n.setAttribute(t,JSON.stringify(e)):n.setAttribute(t,e===!0?"":e)}};window.$renderAttr=s;}`;
|
|
16
|
+
var RENDER_TOGGLE_JS = `{var s=(n,l)=>{let e;return()=>{if(!e){let t=n.firstElementChild;t&&t.tagName==="TEMPLATE"&&t.hasAttribute("m-slot")?e=[...t.content.childNodes]:e=[...n.childNodes]}n.replaceChildren(...l()?e:[])}};window.$renderToggle=s;}`;
|
|
17
|
+
var RENDER_SWITCH_JS = `{var u=(s,d)=>{let r,i=s.getAttribute("value"),t,l,o=e=>t.get(e)??t.set(e,[]).get(e);return()=>{if(!t){t=new Map,l=[];for(let e of s.childNodes)if(e.nodeType===1&&e.tagName==="TEMPLATE"&&e.hasAttribute("m-slot")){for(let n of e.content.childNodes)n.nodeType===1&&n.hasAttribute("slot")?o(n.getAttribute("slot")).push(n):l.push(n);e.remove()}else i?o(i).push(e):l.push(e)}r=""+d(),s.replaceChildren(...t.has(r)?t.get(r):l)}};window.$renderSwitch=u;}`;
|
|
18
|
+
var SIGNALS_JS = `{let m;const h=window,b=new Map,y=new Map,f=n=>y.get(n)??y.set(n,S(n)).get(n),k=()=>Object.create(null),d=(n,e)=>n.getAttribute(e),S=n=>{const e=k(),t=(c,r)=>{e[c]=r},s=new Map,a=(c,r)=>{let i=s.get(c);return i||(i=new Set,s.set(c,i)),i.add(r),()=>{i.delete(r),i.size===0&&s.delete(c)}},o=new Proxy(k(),{get:(c,r)=>document.querySelector("[data-ref='"+n+":"+r+"']")});return new Proxy(e,{get:(c,r,i)=>{switch(r){case"$init":return t;case"$watch":return a;case"app":return f(0);case"refs":return o;default:return m?.(n,r),Reflect.get(c,r,i)}},set:(c,r,i,l)=>{if(i!==Reflect.get(c,r,l)){const u=s.get(r);return u&&queueMicrotask(()=>u.forEach(g=>g())),Reflect.set(c,r,i,l)}return!1}})},$=(n,e,t)=>{switch(e){case"toggle":return $renderToggle(n,t);case"switch":return $renderSwitch(n,t);case"html":return()=>n.innerHTML=""+t()}return e&&e.length>2&&e.startsWith("[")&&e.endsWith("]")?$renderAttr(n,e.slice(1,-1),t):()=>n.textContent=""+t()},v=n=>{const e=n.indexOf(":");return e>0?[Number(n.slice(0,e)),n.slice(e+1)]:null},p=async n=>{const e=n();return e!==void 0?e:(await new Promise(t=>setTimeout(t,0)),p(n))},E=(n,e)=>customElements.define(n,class extends HTMLElement{disposes=[];connectedCallback(){e(this)}disconnectedCallback(){this.disposes.forEach(t=>t()),this.disposes.length=0}});E("m-signal",n=>{const e=Number(d(n,"scope")),t=f(e),s=d(n,"key");if(s)n.disposes.push(t.$watch(s,$(n,d(n,"mode"),()=>t[s])));else{const a=Number(d(n,"computed"));p(()=>b.get(e*1e9+a)).then(([o,c])=>{const r=$(n,d(n,"mode"),o.bind(t));c.forEach(i=>{const[l,u]=v(i);n.disposes.push(f(l).$watch(u,r))})})}}),E("m-effect",n=>{const{disposes:e}=n,t=Number(d(n,"scope")),s=Number(d(n,"n")),a=new Array(s);e.push(()=>{a.forEach(o=>typeof o=="function"&&o()),a.length=0});for(let o=0;o<s;o++){const c="$ME_"+t+"_"+o;p(()=>h[c]).then(r=>{const i=[],l=f(t),u=()=>{a[o]=r.call(l)};m=(g,w)=>i.push([g,w]),u(),m=void 0;for(const[g,w]of i)e.push(f(g).$watch(w,u))},()=>{})}}),h.$MS=(n,e)=>{const[t,s]=v(n);f(t).$init(s,e)},h.$MC=(n,e,t,s)=>{b.set(n*1e9+e,[t,s])},h.$patch=(n,...e)=>{for(const[t,...s]of e){const a=s.pop();let o=n;for(const c of s)o=o[c];o[a]=t}return n},h.$signals=n=>n!==void 0?f(n):void 0;}`;
|
|
19
|
+
var SUSPENSE_JS = `{const i=new Map,o=e=>e.getAttribute("chunk-id"),l=(e,t)=>customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}});l("m-portal",e=>{i.set(o(e),e)}),l("m-chunk",e=>{setTimeout(()=>{const t=o(e),n=i.get(t),s=e.firstChild?.content.childNodes;n&&(e.hasAttribute("next")?s&&n.before(...s):(e.hasAttribute("done")?n.remove():s&&n.replaceWith(...s),i.delete(t)),e.remove())})});}`;
|
|
20
|
+
var LAZY_JS = `{const r=document,o=(t,s)=>t.getAttribute(s),i=(t,s=[])=>t.replaceChildren(...s);customElements.define("m-component",class extends HTMLElement{static observedAttributes=["name","props"];#t;#e;#i;#n;#s;async#r(){if(!this.#t){i(this);return}const t={"x-component":this.#t,"x-props":this.#e||"{}","x-flags":$FLAGS},s=new AbortController;this.#n?.abort(),this.#n=s,i(this,this.#i);const e=await fetch(location.href,{headers:t,signal:s.signal});if(!e.ok)throw i(this),new Error("Failed to fetch component '"+this.#t+"'");const[h,n]=await e.json();this.innerHTML=h,n&&(r.body.appendChild(r.createElement("script")).textContent=n)}get name(){return this.#t??null}set name(t){t&&t!==this.#t&&(this.#t=t,this.refresh())}get props(){return this.#e?JSON.parse(this.#e):void 0}set props(t){const s=typeof t=="string"?t:JSON.stringify(t);s&&s!==this.#e&&(this.#e=s,this.refresh())}connectedCallback(){setTimeout(()=>{if(!this.#i){const t=o(this,"props");this.#t=o(this,"name"),this.#e=t?.startsWith("base64,")?atob(t.slice(7)):void 0,this.#i=[...this.childNodes]}this.#r()})}disconnectedCallback(){i(this,this.#i),this.#n?.abort(),this.#n=void 0,this.#s&&clearTimeout(this.#s),this.#s=void 0}attributeChangedCallback(t,s,e){this.#t&&e&&(t==="name"?this.name=e:t==="props"&&(this.props=e))}refresh(){this.#s&&clearTimeout(this.#s),this.#s=setTimeout(()=>{this.#s=void 0,this.#r()},50)}});}`;
|
|
21
|
+
var ROUTER_JS = `{const a=document,n=location,l=t=>t.split("#",1)[0],c=t=>l(t)===l(n.href);customElements.define("m-router",class extends HTMLElement{#t;#e;#s;#i;async#o(t){const s=new AbortController,e={"x-route":"true","x-flags":$FLAGS};this.#i?.abort(),this.#i=s;const i=await fetch(t,{headers:e,signal:s.signal});if(i.status===404)return this.replaceChildren(...this.#t),!0;if(!i.ok)throw this.replaceChildren(),new Error("Failed to fetch route: "+i.status+" "+i.statusText);const[o,r]=await i.json();this.innerHTML=o,r&&(a.body.appendChild(a.createElement("script")).textContent=r)}#n(){a.querySelectorAll("nav a").forEach(t=>{const{href:s,classList:e}=t,i=t.closest("nav")?.getAttribute("data-active-class")??"active";c(s)?e.add(i):e.remove(i)})}navigate(t,s){const e=new URL(t,n.href);if(e.origin!==n.origin){n.href=t;return}c(e.href)||this.#a(t,s)}async#a(t,s){const e=await this.#o(t);s?.replace?history.replaceState({},"",t):history.pushState({},"",t),e&&typeof $signals<"u"&&($signals(0).url=new URL(t)),this.#n(),window.scrollTo(0,0)}connectedCallback(){setTimeout(()=>{if(!this.#t)if(this.getAttribute("status")==="404")this.#t=[...this.childNodes];else{this.#t=[];for(const t of this.childNodes)if(t.nodeType===1&&t.tagName==="TEMPLATE"&&t.hasAttribute("m-slot")){this.#t.push(...t.content.childNodes),t.remove();break}}}),this.#e=t=>{if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey||!(t.target instanceof HTMLAnchorElement))return;const{download:s,href:e,rel:i,target:o}=t.target;s||i==="external"||o==="_blank"||!e.startsWith(n.origin)||(t.preventDefault(),this.navigate(e))},this.#s=()=>this.#a(n.href),addEventListener("popstate",this.#s),a.addEventListener("click",this.#e),setTimeout(()=>this.#n())}disconnectedCallback(){removeEventListener("popstate",this.#s),a.removeEventListener("click",this.#e),this.#i?.abort(),this.#i=void 0,this.#e=void 0,this.#s=void 0}});}`;
|
|
4
22
|
|
|
5
23
|
// runtime/utils.ts
|
|
24
|
+
var regexpCssBareUnitProps = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
|
|
6
25
|
var regexpHtmlSafe = /["'&<>]/;
|
|
7
|
-
var cssBareUnitProps = new Set(
|
|
8
|
-
"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".split(",")
|
|
9
|
-
);
|
|
10
26
|
var isString = (v) => typeof v === "string";
|
|
11
27
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
12
28
|
var toHyphenCase = (k) => k.replace(/[a-z][A-Z]/g, (m) => m.charAt(0) + "-" + m.charAt(1).toLowerCase());
|
|
13
29
|
var cx = (className) => {
|
|
14
|
-
if (
|
|
30
|
+
if (typeof className === "string") {
|
|
15
31
|
return className;
|
|
16
32
|
}
|
|
17
|
-
if (
|
|
33
|
+
if (typeof className === "object" && className !== null) {
|
|
18
34
|
if (Array.isArray(className)) {
|
|
19
35
|
return className.map(cx).filter(Boolean).join(" ");
|
|
20
36
|
}
|
|
@@ -53,12 +69,12 @@ var styleToCSS = (style) => {
|
|
|
53
69
|
return ret;
|
|
54
70
|
};
|
|
55
71
|
var renderStyle = (style) => {
|
|
56
|
-
if (
|
|
72
|
+
if (typeof style === "object" && style !== null) {
|
|
57
73
|
let css = "";
|
|
58
74
|
for (const [k, v] of Array.isArray(style) ? style : Object.entries(style)) {
|
|
59
75
|
if (isString(v) || typeof v === "number") {
|
|
60
76
|
const cssKey = toHyphenCase(k);
|
|
61
|
-
const cssValue = typeof v === "number" ?
|
|
77
|
+
const cssValue = typeof v === "number" ? regexpCssBareUnitProps.test(k) ? "" + v : v + "px" : "" + v;
|
|
62
78
|
css += (css ? ";" : "") + cssKey + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
|
|
63
79
|
}
|
|
64
80
|
}
|
|
@@ -112,14 +128,14 @@ var escapeHTML = (str) => {
|
|
|
112
128
|
};
|
|
113
129
|
|
|
114
130
|
// symbols.ts
|
|
115
|
-
var $vnode = Symbol.for("jsx.vnode");
|
|
116
131
|
var $fragment = Symbol.for("jsx.fragment");
|
|
117
132
|
var $html = Symbol.for("jsx.html");
|
|
133
|
+
var $setup = Symbol.for("mono.setup");
|
|
118
134
|
var $signal = Symbol.for("mono.signal");
|
|
119
|
-
var $
|
|
135
|
+
var $vnode = Symbol.for("jsx.vnode");
|
|
120
136
|
|
|
121
137
|
// version.ts
|
|
122
|
-
var VERSION = "0.6.
|
|
138
|
+
var VERSION = "0.6.6";
|
|
123
139
|
|
|
124
140
|
// render.ts
|
|
125
141
|
var cdn = "https://raw.esm.sh";
|
|
@@ -132,16 +148,43 @@ var hashCode = (s) => [...s].reduce((hash, c) => Math.imul(31, hash) + c.charCod
|
|
|
132
148
|
var escapeCSSText = (str) => str.replace(/[<>]/g, (m) => m.charCodeAt(0) === 60 ? "<" : ">");
|
|
133
149
|
var toAttrStringLit = (str) => '"' + escapeHTML(str) + '"';
|
|
134
150
|
var toStr = (v, str) => v !== void 0 ? str(v) : "";
|
|
151
|
+
var stringify = JSON.stringify;
|
|
135
152
|
var Ref = class {
|
|
136
153
|
constructor(scope, name) {
|
|
137
154
|
this.scope = scope;
|
|
138
155
|
this.name = name;
|
|
139
156
|
}
|
|
140
157
|
};
|
|
141
|
-
var
|
|
142
|
-
|
|
158
|
+
var IdGen = class extends Map {
|
|
159
|
+
#seq = 0;
|
|
143
160
|
gen(v) {
|
|
144
|
-
return this.get(v) ?? this.set(v, this
|
|
161
|
+
return this.get(v) ?? this.set(v, this.#seq++).get(v);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var IdGenManagerImpl = class {
|
|
165
|
+
#scopes = /* @__PURE__ */ new Map();
|
|
166
|
+
size = 0;
|
|
167
|
+
gen(key, scope = 0) {
|
|
168
|
+
let idGen = this.#scopes.get(scope);
|
|
169
|
+
if (!idGen) {
|
|
170
|
+
idGen = new IdGen();
|
|
171
|
+
this.#scopes.set(scope, idGen);
|
|
172
|
+
}
|
|
173
|
+
this.size++;
|
|
174
|
+
return idGen.gen(key);
|
|
175
|
+
}
|
|
176
|
+
toJS(callback) {
|
|
177
|
+
let js = "";
|
|
178
|
+
for (const [scope, gens] of this.#scopes) {
|
|
179
|
+
for (const [v, id] of gens.entries()) {
|
|
180
|
+
js += callback(scope, id, v);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return js;
|
|
184
|
+
}
|
|
185
|
+
clear() {
|
|
186
|
+
this.#scopes.clear();
|
|
187
|
+
this.size = 0;
|
|
145
188
|
}
|
|
146
189
|
};
|
|
147
190
|
var JSX = {
|
|
@@ -152,13 +195,17 @@ var JSX = {
|
|
|
152
195
|
}
|
|
153
196
|
};
|
|
154
197
|
function renderHtml(node, options) {
|
|
155
|
-
const {
|
|
198
|
+
const { routes, components } = options;
|
|
199
|
+
const request = options.request;
|
|
156
200
|
const headers = new Headers();
|
|
157
201
|
const reqHeaders = request?.headers;
|
|
158
202
|
const componentHeader = reqHeaders?.get("x-component");
|
|
159
203
|
let routeFC = request ? Reflect.get(request, "x-route") : void 0;
|
|
160
204
|
let component = componentHeader ? components?.[componentHeader] : null;
|
|
161
205
|
let status = options.status;
|
|
206
|
+
if (request) {
|
|
207
|
+
request.URL = new URL(request.url);
|
|
208
|
+
}
|
|
162
209
|
if (routes && !routeFC) {
|
|
163
210
|
if (request) {
|
|
164
211
|
const patterns = Object.keys(routes);
|
|
@@ -166,7 +213,7 @@ function renderHtml(node, options) {
|
|
|
166
213
|
for (const pattern of patterns) {
|
|
167
214
|
if (pattern.includes(":") || pattern.includes("*")) {
|
|
168
215
|
dynamicPatterns.push(pattern);
|
|
169
|
-
} else if (
|
|
216
|
+
} else if (request.URL.pathname === pattern) {
|
|
170
217
|
routeFC = routes[pattern];
|
|
171
218
|
break;
|
|
172
219
|
}
|
|
@@ -176,23 +223,23 @@ function renderHtml(node, options) {
|
|
|
176
223
|
const match = new URLPattern({ pathname: path }).exec(request.url);
|
|
177
224
|
if (match) {
|
|
178
225
|
routeFC = routes[path];
|
|
179
|
-
|
|
226
|
+
request.params = match.pathname.groups;
|
|
180
227
|
break;
|
|
181
228
|
}
|
|
182
229
|
}
|
|
183
230
|
}
|
|
184
231
|
} else {
|
|
185
|
-
console.error("The `request` prop in the `<html>` element is required for routing.");
|
|
232
|
+
console.error("[mono-jsx] The `request` prop in the `<html>` element is required for routing.");
|
|
186
233
|
}
|
|
187
234
|
if (!routeFC) {
|
|
188
235
|
status = 404;
|
|
189
236
|
}
|
|
190
237
|
}
|
|
191
238
|
if (components && !request) {
|
|
192
|
-
console.warn("The `components` prop in the `<html>` element is ignored when `request` is not provided.");
|
|
239
|
+
console.warn("[mono-jsx] The `components` prop in the `<html>` element is ignored when `request` is not provided.");
|
|
193
240
|
}
|
|
194
|
-
if (
|
|
195
|
-
for (const [key, value] of Object.entries(
|
|
241
|
+
if (options.headers) {
|
|
242
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
196
243
|
if (value) {
|
|
197
244
|
headers.set(toHyphenCase(key), value);
|
|
198
245
|
}
|
|
@@ -221,9 +268,9 @@ function renderHtml(node, options) {
|
|
|
221
268
|
(chunk) => js += chunk,
|
|
222
269
|
true
|
|
223
270
|
);
|
|
224
|
-
let json = "[" +
|
|
271
|
+
let json = "[" + stringify(html2);
|
|
225
272
|
if (js) {
|
|
226
|
-
json += "," +
|
|
273
|
+
json += "," + stringify(js);
|
|
227
274
|
}
|
|
228
275
|
controller.enqueue(encoder.encode(json + "]"));
|
|
229
276
|
} finally {
|
|
@@ -279,66 +326,66 @@ async function render(node, options, write, writeJS, componentMode) {
|
|
|
279
326
|
signals,
|
|
280
327
|
routeFC,
|
|
281
328
|
eager: componentMode,
|
|
282
|
-
flags: { scope: 0, chunk: 0,
|
|
283
|
-
mcs: new
|
|
284
|
-
mfs: new
|
|
329
|
+
flags: { scope: 0, chunk: 0, runtime: 0 },
|
|
330
|
+
mcs: new IdGenManagerImpl(),
|
|
331
|
+
mfs: new IdGenManagerImpl()
|
|
285
332
|
};
|
|
286
333
|
const finalize = async () => {
|
|
334
|
+
const hasEffect = signals.effects.length > 0;
|
|
335
|
+
const treeshake = (flag, code, force) => {
|
|
336
|
+
if ((force || rc.flags.runtime & flag) && !(runtimeFlag & flag)) {
|
|
337
|
+
runtimeFlag |= flag;
|
|
338
|
+
js += code;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
287
341
|
let js = "";
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (rc.mfs.size > 0 && !(runtimeFlag & F_EVENT)) {
|
|
297
|
-
runtimeFlag |= F_EVENT;
|
|
298
|
-
js += EVENT_JS;
|
|
299
|
-
}
|
|
300
|
-
if ((signals.store.size > 0 || rc.mcs.size > 0 || signals.effects.length > 0) && !(runtimeFlag & F_SIGNALS)) {
|
|
301
|
-
runtimeFlag |= F_SIGNALS;
|
|
302
|
-
js += SIGNALS_JS;
|
|
303
|
-
}
|
|
304
|
-
if (suspenses.length > 0 && !(runtimeFlag & F_SUSPENSE)) {
|
|
305
|
-
runtimeFlag |= F_SUSPENSE;
|
|
306
|
-
js += SUSPENSE_JS;
|
|
307
|
-
}
|
|
308
|
-
if (rc.flags.runtime & F_LAZY && !(runtimeFlag & F_LAZY)) {
|
|
309
|
-
runtimeFlag |= F_LAZY;
|
|
310
|
-
js += LAZY_JS;
|
|
311
|
-
}
|
|
312
|
-
if (rc.flags.runtime & F_ROUTER && !(runtimeFlag & F_ROUTER)) {
|
|
313
|
-
runtimeFlag |= F_ROUTER;
|
|
314
|
-
js += ROUTER_JS;
|
|
342
|
+
treeshake(CX, CX_JS);
|
|
343
|
+
treeshake(STYLE, STYLE_JS);
|
|
344
|
+
treeshake(EVENT, EVENT_JS, rc.mfs.size > 0);
|
|
345
|
+
if (signals.store.size > 0 || rc.mcs.size > 0 || hasEffect) {
|
|
346
|
+
treeshake(RENDER_ATTR, RENDER_ATTR_JS);
|
|
347
|
+
treeshake(RENDER_TOGGLE, RENDER_TOGGLE_JS);
|
|
348
|
+
treeshake(RENDER_SWITCH, RENDER_SWITCH_JS);
|
|
349
|
+
treeshake(SIGNALS, SIGNALS_JS, true);
|
|
315
350
|
}
|
|
351
|
+
treeshake(SUSPENSE, SUSPENSE_JS, suspenses.length > 0);
|
|
352
|
+
treeshake(LAZY, LAZY_JS);
|
|
353
|
+
treeshake(ROUTER, ROUTER_JS);
|
|
316
354
|
if (js.length > 0) {
|
|
317
|
-
js = "(()=>{" + js + "})();/* --- */
|
|
355
|
+
js = "(()=>{" + js + "})();/* --- */";
|
|
318
356
|
}
|
|
319
|
-
if (runtimeFlag &
|
|
320
|
-
|
|
357
|
+
if (runtimeFlag & LAZY || runtimeFlag & ROUTER) {
|
|
358
|
+
const { scope, chunk } = rc.flags;
|
|
359
|
+
js += 'window.$FLAGS="' + scope + "|" + chunk + "|" + runtimeFlag + '";';
|
|
321
360
|
}
|
|
322
361
|
if (rc.mfs.size > 0) {
|
|
323
|
-
|
|
324
|
-
js += "function $MF_" + i + "(){(" + fn.toString() + ").apply(this,arguments)};";
|
|
325
|
-
}
|
|
362
|
+
js += rc.mfs.toJS((scope, seq, fn) => "function $MF_" + scope + "_" + seq + "(){(" + fn.toString() + ").apply(this,arguments)};");
|
|
326
363
|
rc.mfs.clear();
|
|
327
364
|
}
|
|
328
|
-
if (
|
|
365
|
+
if (hasEffect) {
|
|
329
366
|
js += signals.effects.splice(0, signals.effects.length).join("");
|
|
330
367
|
}
|
|
368
|
+
if (runtimeFlag & ROUTER && runtimeFlag & SIGNALS && request) {
|
|
369
|
+
const { params } = request;
|
|
370
|
+
const url = "new URL(" + stringify(request.url) + ")";
|
|
371
|
+
const urlWithParams = params ? "Object.assign(" + url + "," + stringify(params) + ")" : url;
|
|
372
|
+
if (componentMode) {
|
|
373
|
+
js += "$signals(0).url=" + urlWithParams + ";";
|
|
374
|
+
} else {
|
|
375
|
+
js += '$MS("0:url",' + urlWithParams + ");";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
331
378
|
if (signals.store.size > 0) {
|
|
332
379
|
for (const [key, value] of signals.store.entries()) {
|
|
333
|
-
js += "$MS(" +
|
|
380
|
+
js += "$MS(" + stringify(key) + (value !== void 0 ? "," + stringify(value) : "") + ");";
|
|
334
381
|
}
|
|
335
382
|
signals.store.clear();
|
|
336
383
|
}
|
|
337
384
|
if (rc.mcs.size > 0) {
|
|
338
|
-
|
|
339
|
-
const { compute, deps } =
|
|
340
|
-
|
|
341
|
-
}
|
|
385
|
+
js += rc.mcs.toJS((scope, seq, signal) => {
|
|
386
|
+
const { compute, deps } = signal[$signal].key;
|
|
387
|
+
return "$MC(" + scope + "," + seq + ",function(){return(" + compute.toString() + ").call(this)}," + stringify([...deps.values()]) + ");";
|
|
388
|
+
});
|
|
342
389
|
rc.mcs.clear();
|
|
343
390
|
}
|
|
344
391
|
if (js.length > 0) {
|
|
@@ -351,8 +398,13 @@ async function render(node, options, write, writeJS, componentMode) {
|
|
|
351
398
|
};
|
|
352
399
|
let runtimeFlag = 0;
|
|
353
400
|
if (componentMode && request) {
|
|
354
|
-
|
|
355
|
-
|
|
401
|
+
const headers = request.headers;
|
|
402
|
+
const flagsHeader = headers.get("x-flags")?.split("|");
|
|
403
|
+
if (flagsHeader?.length === 3) {
|
|
404
|
+
const [scope, chunk, runtime] = flagsHeader.map(Number);
|
|
405
|
+
Object.assign(rc.flags, { scope, chunk });
|
|
406
|
+
runtimeFlag = runtime;
|
|
407
|
+
}
|
|
356
408
|
}
|
|
357
409
|
await renderNode(rc, node);
|
|
358
410
|
if (rc.flags.scope > 0 && !componentMode) {
|
|
@@ -373,22 +425,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
373
425
|
case "object":
|
|
374
426
|
if (node === null) {
|
|
375
427
|
} else if (isSignal(node)) {
|
|
376
|
-
|
|
377
|
-
if (isString(key)) {
|
|
378
|
-
let buf = '<m-signal scope="' + scope + '" key=' + toAttrStringLit(key) + ">";
|
|
379
|
-
if (value !== void 0 && value !== null) {
|
|
380
|
-
buf += escapeHTML(String(value));
|
|
381
|
-
}
|
|
382
|
-
buf += "</m-signal>";
|
|
383
|
-
write(buf);
|
|
384
|
-
} else {
|
|
385
|
-
let buf = '<m-signal scope="' + scope + '" computed="' + rc.mcs.gen(node) + '">';
|
|
386
|
-
if (value !== void 0) {
|
|
387
|
-
buf += escapeHTML(String(value));
|
|
388
|
-
}
|
|
389
|
-
buf += "</m-signal>";
|
|
390
|
-
write(buf);
|
|
391
|
-
}
|
|
428
|
+
renderSignal(rc, node, void 0, node[$signal].value);
|
|
392
429
|
} else if (isVNode(node)) {
|
|
393
430
|
const [tag, props] = node;
|
|
394
431
|
switch (tag) {
|
|
@@ -401,8 +438,13 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
401
438
|
}
|
|
402
439
|
// XSS!
|
|
403
440
|
case $html: {
|
|
404
|
-
|
|
405
|
-
|
|
441
|
+
const { innerHTML } = props;
|
|
442
|
+
if (innerHTML) {
|
|
443
|
+
if (isSignal(innerHTML)) {
|
|
444
|
+
renderSignal(rc, innerHTML, "html", String(innerHTML[$signal].value), true);
|
|
445
|
+
} else {
|
|
446
|
+
write(innerHTML);
|
|
447
|
+
}
|
|
406
448
|
}
|
|
407
449
|
break;
|
|
408
450
|
}
|
|
@@ -432,7 +474,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
432
474
|
let { scope, key, value } = hidden[$signal];
|
|
433
475
|
if (typeof key === "string") {
|
|
434
476
|
key = {
|
|
435
|
-
compute: "()=>!this[" +
|
|
477
|
+
compute: "()=>!this[" + stringify(key) + "]",
|
|
436
478
|
deps: /* @__PURE__ */ new Set([scope + ":" + key])
|
|
437
479
|
};
|
|
438
480
|
} else {
|
|
@@ -452,11 +494,12 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
452
494
|
if (isString(key)) {
|
|
453
495
|
buf += "key=" + toAttrStringLit(key) + ">";
|
|
454
496
|
} else {
|
|
455
|
-
buf += 'computed="' + rc.mcs.gen(show) + '">';
|
|
497
|
+
buf += 'computed="' + rc.mcs.gen(show, rc.fcCtx?.scopeId) + '">';
|
|
456
498
|
}
|
|
457
499
|
if (!value) {
|
|
458
500
|
buf += "<template m-slot>";
|
|
459
501
|
}
|
|
502
|
+
rc.flags.runtime |= RENDER_TOGGLE;
|
|
460
503
|
write(buf);
|
|
461
504
|
await renderChildren(rc, children);
|
|
462
505
|
write((!value ? "</template>" : "") + "</m-signal>");
|
|
@@ -479,8 +522,9 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
479
522
|
if (isString(key)) {
|
|
480
523
|
stateful += "key=" + toAttrStringLit(key) + ">";
|
|
481
524
|
} else {
|
|
482
|
-
stateful += 'computed="' + rc.mcs.gen(valueProp) + '">';
|
|
525
|
+
stateful += 'computed="' + rc.mcs.gen(valueProp, rc.fcCtx?.scopeId) + '">';
|
|
483
526
|
}
|
|
527
|
+
rc.flags.runtime |= RENDER_SWITCH;
|
|
484
528
|
toSlotName = String(value);
|
|
485
529
|
} else {
|
|
486
530
|
toSlotName = String(valueProp);
|
|
@@ -524,22 +568,19 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
524
568
|
}
|
|
525
569
|
// `<component>` element
|
|
526
570
|
case "component": {
|
|
527
|
-
|
|
571
|
+
let { placeholder } = props;
|
|
528
572
|
let attrs = "";
|
|
529
573
|
let attrModifiers = "";
|
|
530
|
-
for (const p of ["name", "props"]) {
|
|
574
|
+
for (const p of ["name", "props", "ref"]) {
|
|
531
575
|
let propValue = props[p];
|
|
532
|
-
|
|
576
|
+
let [attr, , attrSignal] = renderAttr(rc, p, propValue);
|
|
533
577
|
if (attrSignal) {
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
attrModifiers += "></m-signal>";
|
|
542
|
-
propValue = value;
|
|
578
|
+
const write2 = (chunk) => {
|
|
579
|
+
attrModifiers += chunk;
|
|
580
|
+
};
|
|
581
|
+
renderSignal({ ...rc, write: write2 }, attrSignal, [p]);
|
|
582
|
+
rc.flags.runtime |= RENDER_ATTR;
|
|
583
|
+
propValue = attrSignal[$signal].value;
|
|
543
584
|
}
|
|
544
585
|
attrs += attr;
|
|
545
586
|
}
|
|
@@ -555,7 +596,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
555
596
|
buf += "<m-group>" + attrModifiers + "</m-group>";
|
|
556
597
|
}
|
|
557
598
|
write(buf);
|
|
558
|
-
rc.flags.runtime |=
|
|
599
|
+
rc.flags.runtime |= LAZY;
|
|
559
600
|
break;
|
|
560
601
|
}
|
|
561
602
|
// `<router>` element
|
|
@@ -582,7 +623,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
582
623
|
}
|
|
583
624
|
buf += "</m-router>";
|
|
584
625
|
write(buf);
|
|
585
|
-
rc.flags.runtime |=
|
|
626
|
+
rc.flags.runtime |= ROUTER;
|
|
586
627
|
break;
|
|
587
628
|
}
|
|
588
629
|
default: {
|
|
@@ -606,14 +647,11 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
606
647
|
write(addonHtml);
|
|
607
648
|
}
|
|
608
649
|
if (signalValue) {
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
attrModifiers += 'computed="' + rc.mcs.gen(signalValue) + '"';
|
|
615
|
-
}
|
|
616
|
-
attrModifiers += "></m-signal>";
|
|
650
|
+
const write2 = (chunk) => {
|
|
651
|
+
attrModifiers += chunk;
|
|
652
|
+
};
|
|
653
|
+
renderSignal({ ...rc, write: write2 }, signalValue, [propName]);
|
|
654
|
+
rc.flags.runtime |= RENDER_ATTR;
|
|
617
655
|
}
|
|
618
656
|
buffer += attr;
|
|
619
657
|
}
|
|
@@ -642,6 +680,15 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
642
680
|
break;
|
|
643
681
|
}
|
|
644
682
|
}
|
|
683
|
+
async function renderChildren(rc, children, stripSlotProp) {
|
|
684
|
+
if (Array.isArray(children) && !isVNode(children)) {
|
|
685
|
+
for (const child of children) {
|
|
686
|
+
await renderNode(rc, child, stripSlotProp);
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
await renderNode(rc, children, stripSlotProp);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
645
692
|
function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
646
693
|
let attr = "";
|
|
647
694
|
let addonHtml = "";
|
|
@@ -651,13 +698,37 @@ function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
|
651
698
|
if (isSignal(attrValue)) {
|
|
652
699
|
signal = attrValue;
|
|
653
700
|
} else {
|
|
654
|
-
|
|
701
|
+
const { fcCtx } = rc;
|
|
702
|
+
if (fcCtx) {
|
|
703
|
+
const deps = /* @__PURE__ */ new Set();
|
|
704
|
+
const patches = [];
|
|
705
|
+
const staticProps = traverseProps(attrValue, (path, value) => {
|
|
706
|
+
const { scope, key } = value[$signal];
|
|
707
|
+
if (isString(key)) {
|
|
708
|
+
patches.push([
|
|
709
|
+
(scope !== fcCtx.scopeId ? "$signals(" + scope + ")" : "this") + "[" + stringify(key) + "]",
|
|
710
|
+
...path
|
|
711
|
+
].join(","));
|
|
712
|
+
deps.add(scope + ":" + key);
|
|
713
|
+
} else {
|
|
714
|
+
patches.push(["(" + key.compute.toString() + ")(),", ...path].join(","));
|
|
715
|
+
for (const dep of key.deps) {
|
|
716
|
+
deps.add(dep);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
if (patches.length > 0) {
|
|
721
|
+
const { scopeId } = fcCtx;
|
|
722
|
+
const compute = "()=>$patch(" + stringify(staticProps) + ",[" + patches.join("],[") + "])";
|
|
723
|
+
signal = Signal(scopeId, { compute, deps }, staticProps);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
655
726
|
}
|
|
656
727
|
if (signal) {
|
|
657
728
|
if (attrName === "class") {
|
|
658
|
-
rc.flags.runtime |=
|
|
729
|
+
rc.flags.runtime |= CX;
|
|
659
730
|
} else if (attrName === "style") {
|
|
660
|
-
rc.flags.runtime |=
|
|
731
|
+
rc.flags.runtime |= STYLE;
|
|
661
732
|
}
|
|
662
733
|
signalValue = signal;
|
|
663
734
|
attrValue = signal[$signal].value;
|
|
@@ -683,16 +754,16 @@ function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
|
683
754
|
break;
|
|
684
755
|
case "props":
|
|
685
756
|
if (isObject(attrValue) && !Array.isArray(attrValue)) {
|
|
686
|
-
attr = ' props="base64,' + btoa(
|
|
757
|
+
attr = ' props="base64,' + btoa(stringify(attrValue)) + '"';
|
|
687
758
|
}
|
|
688
759
|
break;
|
|
689
760
|
case "ref":
|
|
690
761
|
if (typeof attrValue === "function") {
|
|
691
762
|
const signals = rc.fcCtx?.signals;
|
|
692
763
|
if (!signals) {
|
|
693
|
-
console.error("Use `ref` outside of a component function");
|
|
764
|
+
console.error("[mono-jsx] Use `ref` outside of a component function");
|
|
694
765
|
} else {
|
|
695
|
-
const refId = rc.
|
|
766
|
+
const refId = rc.fcCtx.refs++;
|
|
696
767
|
const effects = signals[Symbol.for("effects")];
|
|
697
768
|
effects.push("()=>(" + attrValue.toString() + ')(this.refs["' + refId + '"])');
|
|
698
769
|
attr = " data-ref=" + toAttrStringLit(rc.fcCtx.scopeId + ":" + refId);
|
|
@@ -703,7 +774,8 @@ function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
|
703
774
|
break;
|
|
704
775
|
case "action":
|
|
705
776
|
if (typeof attrValue === "function") {
|
|
706
|
-
|
|
777
|
+
const scopeId = rc.fcCtx?.scopeId;
|
|
778
|
+
attr = ' onsubmit="$onsubmit(event,$MF_' + (scopeId ?? 0) + "_" + rc.mfs.gen(attrValue, scopeId) + toStr(scopeId, (i) => "," + i) + ')"';
|
|
707
779
|
} else if (isString(attrValue)) {
|
|
708
780
|
attr = " action=" + toAttrStringLit(attrValue);
|
|
709
781
|
}
|
|
@@ -715,7 +787,8 @@ function renderAttr(rc, attrName, attrValue, stripSlotProp) {
|
|
|
715
787
|
break;
|
|
716
788
|
default:
|
|
717
789
|
if (attrName.startsWith("on") && typeof attrValue === "function") {
|
|
718
|
-
|
|
790
|
+
const scopeId = rc.fcCtx?.scopeId;
|
|
791
|
+
attr = " " + escapeHTML(attrName.toLowerCase()) + '="$emit(event,$MF_' + (scopeId ?? 0) + "_" + rc.mfs.gen(attrValue, scopeId) + toStr(scopeId, (i) => "," + i) + ')"';
|
|
719
792
|
} else if (attrValue === false || attrValue === null || attrValue === void 0) {
|
|
720
793
|
} else {
|
|
721
794
|
attr = " " + escapeHTML(attrName);
|
|
@@ -732,7 +805,7 @@ async function renderFC(rc, fc, props) {
|
|
|
732
805
|
const scopeId = ++rc.flags.scope;
|
|
733
806
|
const signals = createSignals(scopeId, rc.signals.app, rc.context, rc.request);
|
|
734
807
|
const slots = children !== void 0 ? Array.isArray(children) ? isVNode(children) ? [children] : children : [children] : void 0;
|
|
735
|
-
const fcCtx = { scopeId, signals, slots };
|
|
808
|
+
const fcCtx = { scopeId, signals, slots, refs: 0 };
|
|
736
809
|
try {
|
|
737
810
|
const v = fc.call(signals, props);
|
|
738
811
|
if (isObject(v) && !isVNode(v)) {
|
|
@@ -803,22 +876,34 @@ async function renderFC(rc, fc, props) {
|
|
|
803
876
|
} catch (err) {
|
|
804
877
|
if (err instanceof Error) {
|
|
805
878
|
if (props.catch) {
|
|
806
|
-
await renderNode(rc, props.catch(err))
|
|
879
|
+
await renderNode(rc, props.catch(err)).catch(() => {
|
|
880
|
+
});
|
|
807
881
|
} else {
|
|
808
882
|
console.error(err);
|
|
809
|
-
write(
|
|
883
|
+
write("<script>console.error(" + stringify(err.stack ?? err.message) + ")<\/script>");
|
|
810
884
|
}
|
|
811
885
|
}
|
|
812
886
|
}
|
|
813
887
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
888
|
+
function renderSignal(rc, signal, mode, content, html2) {
|
|
889
|
+
const { scope, key } = signal[$signal];
|
|
890
|
+
let buffer = "<m-signal";
|
|
891
|
+
if (mode) {
|
|
892
|
+
buffer += ' mode="';
|
|
893
|
+
if (isString(mode)) {
|
|
894
|
+
buffer += mode;
|
|
895
|
+
} else {
|
|
896
|
+
buffer += "[" + mode[0] + "]";
|
|
818
897
|
}
|
|
898
|
+
buffer += '"';
|
|
899
|
+
}
|
|
900
|
+
buffer += ' scope="' + scope + '"';
|
|
901
|
+
if (isString(key)) {
|
|
902
|
+
buffer += " key=" + toAttrStringLit(key);
|
|
819
903
|
} else {
|
|
820
|
-
|
|
904
|
+
buffer += ' computed="' + rc.mcs.gen(signal, rc.fcCtx?.scopeId) + '"';
|
|
821
905
|
}
|
|
906
|
+
rc.write(buffer + ">" + (html2 ? content : escapeHTML(String(content ?? ""))) + "</m-signal>");
|
|
822
907
|
}
|
|
823
908
|
var collectDeps;
|
|
824
909
|
function Signal(scope, key, value) {
|
|
@@ -849,6 +934,14 @@ function createSignals(scopeId, appSignals, context = new NullProtoObj(), reques
|
|
|
849
934
|
const store = new NullProtoObj();
|
|
850
935
|
const signals = /* @__PURE__ */ new Map();
|
|
851
936
|
const effects = [];
|
|
937
|
+
const refs = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
938
|
+
get(_, key) {
|
|
939
|
+
return new Ref(scopeId, key);
|
|
940
|
+
},
|
|
941
|
+
set() {
|
|
942
|
+
throw new Error("[mono-jsx] The `refs` object is read-only at SSR time.");
|
|
943
|
+
}
|
|
944
|
+
});
|
|
852
945
|
const computed = (compute) => {
|
|
853
946
|
const deps = /* @__PURE__ */ new Set();
|
|
854
947
|
collectDeps = (scopeId2, key) => deps.add(scopeId2 + ":" + key);
|
|
@@ -856,11 +949,9 @@ function createSignals(scopeId, appSignals, context = new NullProtoObj(), reques
|
|
|
856
949
|
collectDeps = void 0;
|
|
857
950
|
return Signal(scopeId, { compute, deps }, value);
|
|
858
951
|
};
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
});
|
|
952
|
+
const markEffect = (effect) => {
|
|
953
|
+
effects.push(effect.toString());
|
|
954
|
+
};
|
|
864
955
|
const mark = ({ signals: signals2, write }) => {
|
|
865
956
|
if (effects.length > 0) {
|
|
866
957
|
const n = effects.length;
|
|
@@ -892,71 +983,55 @@ function createSignals(scopeId, appSignals, context = new NullProtoObj(), reques
|
|
|
892
983
|
case "$":
|
|
893
984
|
return computed;
|
|
894
985
|
case "effect":
|
|
895
|
-
return
|
|
896
|
-
effects.push(effect.toString());
|
|
897
|
-
};
|
|
986
|
+
return markEffect;
|
|
898
987
|
case Symbol.for("effects"):
|
|
899
988
|
return effects;
|
|
900
989
|
case Symbol.for("mark"):
|
|
901
990
|
return mark;
|
|
902
|
-
|
|
903
|
-
if (
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
}
|
|
920
|
-
return signal;
|
|
991
|
+
case "url":
|
|
992
|
+
if (scopeId === 0) {
|
|
993
|
+
collectDeps?.(0, key);
|
|
994
|
+
return request ? request.URL ?? (request.URL = new URL(request.url)) : void 0;
|
|
995
|
+
}
|
|
996
|
+
// fallthrough
|
|
997
|
+
default: {
|
|
998
|
+
const value = Reflect.get(target, key, receiver);
|
|
999
|
+
if (typeof key === "symbol" || isSignal(value)) {
|
|
1000
|
+
return value;
|
|
1001
|
+
}
|
|
1002
|
+
if (value === void 0 && !Reflect.has(target, key)) {
|
|
1003
|
+
Reflect.set(target, key, void 0, receiver);
|
|
1004
|
+
}
|
|
1005
|
+
if (collectDeps) {
|
|
1006
|
+
collectDeps(scopeId, key);
|
|
1007
|
+
return value;
|
|
921
1008
|
}
|
|
1009
|
+
let signal = signals.get(key);
|
|
1010
|
+
if (!signal) {
|
|
1011
|
+
signal = Signal(scopeId, key, value);
|
|
1012
|
+
signals.set(key, signal);
|
|
1013
|
+
}
|
|
1014
|
+
return signal;
|
|
1015
|
+
}
|
|
922
1016
|
}
|
|
923
1017
|
},
|
|
924
1018
|
set(target, key, value, receiver) {
|
|
925
|
-
|
|
1019
|
+
if (isString(key)) {
|
|
1020
|
+
signals.delete(key);
|
|
1021
|
+
}
|
|
926
1022
|
return Reflect.set(target, key, value, receiver);
|
|
927
1023
|
}
|
|
928
1024
|
});
|
|
929
1025
|
return thisProxy;
|
|
930
1026
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
const deps = /* @__PURE__ */ new Set();
|
|
934
|
-
const patches = [];
|
|
935
|
-
const staticProps = traverseProps(props, (path, value) => {
|
|
936
|
-
const { scope, key } = value[$signal];
|
|
937
|
-
if (isString(key)) {
|
|
938
|
-
patches.push([(scope !== fcCtx.scopeId ? "$signals(" + scope + ")" : "this") + "[" + JSON.stringify(key) + "]", ...path].join(","));
|
|
939
|
-
deps.add(scope + ":" + key);
|
|
940
|
-
} else {
|
|
941
|
-
patches.push(["(" + key.compute.toString() + ")(),", ...path].join(","));
|
|
942
|
-
for (const dep of key.deps) {
|
|
943
|
-
deps.add(dep);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
if (patches.length > 0) {
|
|
948
|
-
const { scopeId } = fcCtx;
|
|
949
|
-
const compute = "()=>$patch(" + JSON.stringify(staticProps) + ",[" + patches.join("],[") + "])";
|
|
950
|
-
return Signal(scopeId, { compute, deps }, staticProps);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
return void 0;
|
|
1027
|
+
function markSignals(rc, signals) {
|
|
1028
|
+
signals[Symbol.for("mark")](rc);
|
|
954
1029
|
}
|
|
955
1030
|
function traverseProps(obj, callback, path = []) {
|
|
956
1031
|
const isArray = Array.isArray(obj);
|
|
957
1032
|
const copy = isArray ? new Array(obj.length) : new NullProtoObj();
|
|
958
1033
|
for (const [k, value] of Object.entries(obj)) {
|
|
959
|
-
const newPath = path.concat(isArray ? k :
|
|
1034
|
+
const newPath = path.concat(isArray ? k : stringify(k));
|
|
960
1035
|
const key = isArray ? Number(k) : k;
|
|
961
1036
|
if (isObject(value)) {
|
|
962
1037
|
if (isSignal(value)) {
|
|
@@ -971,9 +1046,6 @@ function traverseProps(obj, callback, path = []) {
|
|
|
971
1046
|
}
|
|
972
1047
|
return copy;
|
|
973
1048
|
}
|
|
974
|
-
function markSignals(rc, signals) {
|
|
975
|
-
signals[Symbol.for("mark")](rc);
|
|
976
|
-
}
|
|
977
1049
|
|
|
978
1050
|
// jsx-runtime.ts
|
|
979
1051
|
var Fragment = $fragment;
|
|
@@ -986,7 +1058,7 @@ var jsx = (tag, props = new NullProtoObj(), key) => {
|
|
|
986
1058
|
props.key = key;
|
|
987
1059
|
}
|
|
988
1060
|
if (tag === "html") {
|
|
989
|
-
if (props.request === $
|
|
1061
|
+
if (props.request === $setup) {
|
|
990
1062
|
return props;
|
|
991
1063
|
}
|
|
992
1064
|
const renderOptions = new NullProtoObj();
|
|
@@ -1012,7 +1084,7 @@ var jsxEscape = (value) => {
|
|
|
1012
1084
|
};
|
|
1013
1085
|
var html = (template, ...values) => [
|
|
1014
1086
|
$html,
|
|
1015
|
-
{ innerHTML: isString(template) ? template : String.raw(template, ...values.map(jsxEscape)) },
|
|
1087
|
+
{ innerHTML: isString(template) || isSignal(template) ? template : String.raw(template, ...values.map(jsxEscape)) },
|
|
1016
1088
|
$vnode
|
|
1017
1089
|
];
|
|
1018
1090
|
Object.assign(globalThis, {
|
package/package.json
CHANGED
package/setup.mjs
CHANGED
|
@@ -34,6 +34,8 @@ async function setup() {
|
|
|
34
34
|
compilerOptions.lib ??= ["dom", "es2022"];
|
|
35
35
|
compilerOptions.module ??= "es2022";
|
|
36
36
|
compilerOptions.moduleResolution ??= "bundler";
|
|
37
|
+
compilerOptions.allowImportingTsExtensions ??= true;
|
|
38
|
+
compilerOptions.noEmit ??= true;
|
|
37
39
|
}
|
|
38
40
|
compilerOptions.jsx = "react-jsx";
|
|
39
41
|
compilerOptions.jsxImportSource = "mono-jsx";
|
package/types/html.d.ts
CHANGED
|
@@ -44,11 +44,13 @@ export namespace HTML {
|
|
|
44
44
|
|
|
45
45
|
/** Global HTML attributes from https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes */
|
|
46
46
|
interface GlobalAttributes<T extends EventTarget> extends EventAttributes<T>, Aria.Attributes, Mono.BaseAttributes, JSX.HtmlTag {
|
|
47
|
+
// @mono-jsx
|
|
48
|
+
ref?: HTMLElement | ((el: T) => unknown);
|
|
47
49
|
/** Defines a unique identifier (ID) which must be unique in the whole document. Its purpose is to identify the element when linking (using a fragment identifier), scripting, or styling (with CSS). */
|
|
48
50
|
id?: string;
|
|
49
|
-
/**
|
|
51
|
+
/** Contains a space-separated list of the classes of the element. Classes are used by CSS and JavaScript to select and access specific elements. */
|
|
50
52
|
class?: HTMLClass | HTMLClass[];
|
|
51
|
-
/** Contains [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) styling declarations to be applied to the element.
|
|
53
|
+
/** Contains [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) styling declarations to be applied to the element. */
|
|
52
54
|
style?: string | Mono.CSSProperties;
|
|
53
55
|
/** An enumerated attribute indicating that the element is not yet, or is no longer, relevant. For example, it can be used to hide elements of the page that can't be used until the login process has been completed. The browser won't render such elements. This attribute must not be used to hide content that could legitimately be shown. */
|
|
54
56
|
hidden?: boolean | "hidden" | "until-found";
|
|
@@ -828,9 +830,6 @@ export namespace HTML {
|
|
|
828
830
|
}
|
|
829
831
|
|
|
830
832
|
interface EventAttributes<T extends EventTarget> {
|
|
831
|
-
// @mono-jsx
|
|
832
|
-
ref?: ((el: T) => unknown) | HTMLElement | null;
|
|
833
|
-
|
|
834
833
|
// Input Events
|
|
835
834
|
onBeforeInput?: EventHandler<Event, T>;
|
|
836
835
|
onInput?: EventHandler<Event, T>;
|
package/types/mono.d.ts
CHANGED
|
@@ -104,42 +104,62 @@ export interface Elements {
|
|
|
104
104
|
switch: BaseAttributes & {
|
|
105
105
|
value?: string | number | boolean | null;
|
|
106
106
|
};
|
|
107
|
-
/**
|
|
108
|
-
* The `for` element is a built-in element for list rendering with signals.
|
|
109
|
-
*/
|
|
110
|
-
for: BaseAttributes & {
|
|
111
|
-
items?: unknown[];
|
|
112
|
-
};
|
|
113
107
|
/**
|
|
114
108
|
* The `component` element is a built-in element that is used to load components lazily,
|
|
115
109
|
* which can improve performance by reducing the initial load time of the application.
|
|
116
110
|
*/
|
|
117
111
|
component: BaseAttributes & AsyncComponentAttributes & {
|
|
118
|
-
name
|
|
112
|
+
name?: string;
|
|
119
113
|
props?: Record<string, unknown>;
|
|
114
|
+
ref?: ComponentElement | ((el: ComponentElement) => void);
|
|
120
115
|
};
|
|
121
116
|
/**
|
|
122
117
|
* The `router` element is a built-in element that implements client-side routing.
|
|
123
118
|
*/
|
|
124
|
-
router: BaseAttributes & AsyncComponentAttributes & {
|
|
119
|
+
router: BaseAttributes & AsyncComponentAttributes & {
|
|
120
|
+
/**
|
|
121
|
+
* The `base` attribute is used to set the base URL for the router.
|
|
122
|
+
*/
|
|
123
|
+
base?: string;
|
|
124
|
+
ref?: RouterElement | ((el: RouterElement) => void);
|
|
125
|
+
};
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
export type WithParams<T> = T & { params?: Record<string, string> };
|
|
129
|
+
|
|
127
130
|
declare global {
|
|
128
131
|
/**
|
|
129
|
-
* The `html` function is used to create XSS-
|
|
132
|
+
* The `html` function is used to create XSS-unsafed HTML content.
|
|
130
133
|
*/
|
|
131
134
|
var html: JSX.Raw;
|
|
135
|
+
/**
|
|
136
|
+
* The `css` function is an alias to `html`.
|
|
137
|
+
*/
|
|
132
138
|
var css: JSX.Raw;
|
|
139
|
+
/**
|
|
140
|
+
* The `js` function is an alias to `html`.
|
|
141
|
+
*/
|
|
133
142
|
var js: JSX.Raw;
|
|
134
|
-
|
|
135
143
|
/**
|
|
136
|
-
*
|
|
144
|
+
* The `FC` type defines Signals/Context/Refs API.
|
|
137
145
|
*/
|
|
138
|
-
type FC<Signals = {}, AppSignals = {}, Context = {}> = {
|
|
146
|
+
type FC<Signals = {}, AppSignals = {}, Context = {}, Refs = {}, AppRefs = {}> = {
|
|
139
147
|
/**
|
|
140
148
|
* The global signals shared across the application.
|
|
141
149
|
*/
|
|
142
|
-
readonly app:
|
|
150
|
+
readonly app: {
|
|
151
|
+
/**
|
|
152
|
+
* The `app.refs` object is used to store variables in the application scope.
|
|
153
|
+
* It is similar to `refs`, but it is shared across all components in the application.
|
|
154
|
+
*
|
|
155
|
+
* **⚠ This is a client-side only API.**
|
|
156
|
+
*/
|
|
157
|
+
readonly refs: AppRefs;
|
|
158
|
+
/**
|
|
159
|
+
* The `app.url` object contains the current URL information.
|
|
160
|
+
*/
|
|
161
|
+
readonly url: WithParams<URL>;
|
|
162
|
+
} & Omit<AppSignals, "refs" | "url">;
|
|
143
163
|
/**
|
|
144
164
|
* The rendering context.
|
|
145
165
|
*
|
|
@@ -151,23 +171,47 @@ declare global {
|
|
|
151
171
|
*
|
|
152
172
|
* **⚠ This is a server-side only API.**
|
|
153
173
|
*/
|
|
154
|
-
readonly request: Request & {
|
|
174
|
+
readonly request: WithParams<Request & { URL: URL }>;
|
|
155
175
|
/**
|
|
156
|
-
* The `refs` object is used to store
|
|
176
|
+
* The `refs` object is used to store variables in clide side.
|
|
177
|
+
*
|
|
178
|
+
* **⚠ This is a client-side only API.**
|
|
157
179
|
*/
|
|
158
|
-
readonly refs:
|
|
180
|
+
readonly refs: Refs;
|
|
159
181
|
/**
|
|
160
182
|
* The `computed` method is used to create a computed signal.
|
|
161
183
|
*/
|
|
162
184
|
readonly computed: <T = unknown>(fn: () => T) => T;
|
|
163
185
|
/**
|
|
164
|
-
* `this.$(fn)` is
|
|
186
|
+
* `this.$(fn)` is a shortcut for `this.computed(fn)`.
|
|
165
187
|
*/
|
|
166
|
-
readonly $:
|
|
188
|
+
readonly $: FC["computed"];
|
|
167
189
|
/**
|
|
168
190
|
* The `effect` method is used to create a side effect.
|
|
169
191
|
* **The effect function is only called on client side.**
|
|
170
192
|
*/
|
|
171
193
|
readonly effect: (fn: () => void | (() => void)) => void;
|
|
172
|
-
} & Omit<Signals, "app" | "context" | "request" | "refs" | "computed" | "$" | "effect">;
|
|
194
|
+
} & Omit<Signals, "app" | "context" | "request" | "refs" | "forIndex" | "forItem" | "computed" | "$" | "effect">;
|
|
195
|
+
/**
|
|
196
|
+
* The `Refs` defines the `refs` types.
|
|
197
|
+
*/
|
|
198
|
+
type Refs<T, R = {}, RR = {}> = T extends FC<infer S, infer A, infer C> ? FC<S, A, C, R, RR> : never;
|
|
199
|
+
/**
|
|
200
|
+
* The `Context` defines the `context` types.
|
|
201
|
+
*/
|
|
202
|
+
type Context<T, C = {}> = T extends FC<infer S, infer A, infer _, infer R, infer RR> ? FC<S, A, C, R, RR> : never;
|
|
203
|
+
/**
|
|
204
|
+
* The `ComponentElement` type defines the component element.
|
|
205
|
+
*/
|
|
206
|
+
type ComponentElement = {
|
|
207
|
+
name: string;
|
|
208
|
+
props: Record<string, unknown> | undefined;
|
|
209
|
+
refresh: () => Promise<void>;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* The `RouterElement` type defines the router element.
|
|
213
|
+
*/
|
|
214
|
+
type RouterElement = {
|
|
215
|
+
navigate: (url: string | URL, options?: { replace?: boolean }) => Promise<void>;
|
|
216
|
+
};
|
|
173
217
|
}
|