mono-jsx 0.10.0-beta.4 → 0.10.0-beta.6
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 +114 -5
- package/jsx-runtime.mjs +124 -74
- package/package.json +1 -1
- package/types/jsx-runtime.d.ts +2 -0
- package/types/jsx.d.ts +11 -2
- package/types/mono.d.ts +26 -8
- package/types/render.d.ts +5 -4
package/README.md
CHANGED
|
@@ -964,8 +964,6 @@ The `<component>` element also supports the `ref` prop, which allows you to cont
|
|
|
964
964
|
- `refresh`: A method to re-render the component with the current name and props.
|
|
965
965
|
|
|
966
966
|
```tsx
|
|
967
|
-
import type { ComponentElement } from "mono-jsx";
|
|
968
|
-
|
|
969
967
|
function App(this: WithRefs<FC, { component: ComponentElement }>) {
|
|
970
968
|
this.effect(() => {
|
|
971
969
|
// updating the component name and props will trigger a re-render of the component
|
|
@@ -1110,8 +1108,21 @@ You can access the `params` object in your route components to get the values of
|
|
|
1110
1108
|
```tsx
|
|
1111
1109
|
// router pattern: "/post/:id"
|
|
1112
1110
|
function Post(this: FC) {
|
|
1113
|
-
this.request.url // "http://localhost:3000/post/123"
|
|
1114
|
-
this.request.params?.id // "123"
|
|
1111
|
+
console.log(this.request.url) // "http://localhost:3000/post/123"
|
|
1112
|
+
console.log(this.request.params?.id) // "123"
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
You can use `this.app.url` signal to get route URL and parameters:
|
|
1117
|
+
|
|
1118
|
+
```tsx
|
|
1119
|
+
function Post(this: FC) {
|
|
1120
|
+
return (
|
|
1121
|
+
<div>
|
|
1122
|
+
<p>Current URL: {this.$(() => this.app.url.href)}</p>
|
|
1123
|
+
<p>Post id: {this.$(() => this.app.url.params?.id)}</p>
|
|
1124
|
+
</div>
|
|
1125
|
+
)
|
|
1115
1126
|
}
|
|
1116
1127
|
```
|
|
1117
1128
|
|
|
@@ -1227,7 +1238,7 @@ The `hidden` prop can be used to hide the formslot payload from the form handler
|
|
|
1227
1238
|
|
|
1228
1239
|
### Using `this.app.url` Signal
|
|
1229
1240
|
|
|
1230
|
-
`this.app.url` is an app-level signal that contains the current route URL and parameters.
|
|
1241
|
+
The `this.app.url` in a component is an app-level signal that contains the current route URL and parameters. It is automatically updated when the route changes, so you can use it to display the current URL in your components or control the view with `<show>`, `<hidden>` or `<switch>` elements:
|
|
1231
1242
|
|
|
1232
1243
|
```tsx
|
|
1233
1244
|
function App(this: FC) {
|
|
@@ -1258,6 +1269,23 @@ export default {
|
|
|
1258
1269
|
}
|
|
1259
1270
|
```
|
|
1260
1271
|
|
|
1272
|
+
You can also use the `navigate` method of the `<router>` element to navigate to a new route programmatically.
|
|
1273
|
+
|
|
1274
|
+
```tsx
|
|
1275
|
+
function App(this: FC<{}, { router: RouterElement }>) {
|
|
1276
|
+
return (
|
|
1277
|
+
<>
|
|
1278
|
+
<header>
|
|
1279
|
+
<button
|
|
1280
|
+
onClick={() => this.refs.router.navigate("/about", { replace: false, refresh: false })}
|
|
1281
|
+
>About</button>
|
|
1282
|
+
</header>
|
|
1283
|
+
<router ref={this.refs.router} />
|
|
1284
|
+
</>
|
|
1285
|
+
)
|
|
1286
|
+
}
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1261
1289
|
### Nav Links
|
|
1262
1290
|
|
|
1263
1291
|
Links under the `<nav>` element will be treated as navigation links by the router. When the `href` of a nav link matches a route, an active class will be added to the link element. By default, the active class is `active`, but you can customize it by setting the `data-active-class` prop on the `<nav>` element. You can add styles for the active link using nested CSS selectors in the `style` prop of the `<nav>` element.
|
|
@@ -1277,6 +1305,63 @@ export default {
|
|
|
1277
1305
|
}
|
|
1278
1306
|
```
|
|
1279
1307
|
|
|
1308
|
+
## Adding Page Metadata
|
|
1309
|
+
|
|
1310
|
+
You can add metadata to the route component by setting the `metadata` property on the route component.
|
|
1311
|
+
|
|
1312
|
+
```tsx
|
|
1313
|
+
function Home(this: FC) {
|
|
1314
|
+
return <p>Home</p>
|
|
1315
|
+
}
|
|
1316
|
+
Home.metadata = {
|
|
1317
|
+
title: "Home",
|
|
1318
|
+
description: "Home page",
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const routes = {
|
|
1322
|
+
"/": Home,
|
|
1323
|
+
}
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
Or use `getMetadata` property on the route component to dynamically generate the metadata.
|
|
1327
|
+
|
|
1328
|
+
```tsx
|
|
1329
|
+
async function Post(this: FC) {
|
|
1330
|
+
const post = await getPost(this.request.params.slug)
|
|
1331
|
+
return <div>
|
|
1332
|
+
<h1>{post.title}</h1>
|
|
1333
|
+
<h2>{post.description}</h2>
|
|
1334
|
+
<div>{post.content}</div>
|
|
1335
|
+
</div>
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
Post.getMetadata = async function(this: FC) {
|
|
1339
|
+
const post = await getPost(this.request.params.slug)
|
|
1340
|
+
return {
|
|
1341
|
+
title: post.title,
|
|
1342
|
+
description: post.description,
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const routes = {
|
|
1347
|
+
"/post/:slug": Post,
|
|
1348
|
+
}
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
You can also add metadata to the root `<html>` element by setting the `metadata` prop on the root `<html>` element. This will be added to all the pages in your app.
|
|
1352
|
+
|
|
1353
|
+
```tsx
|
|
1354
|
+
export default {
|
|
1355
|
+
fetch: (req) => (
|
|
1356
|
+
<html request={req} routes={routes} metadata={{ title: "My App" }}>
|
|
1357
|
+
<head>
|
|
1358
|
+
<metadata />
|
|
1359
|
+
</head>
|
|
1360
|
+
</html>
|
|
1361
|
+
)
|
|
1362
|
+
}
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1280
1365
|
### Fallback (404)
|
|
1281
1366
|
|
|
1282
1367
|
You can add fallback(404) content to the `<router>` element as children, which will be displayed when no route matches the current URL.
|
|
@@ -1294,6 +1379,30 @@ export default {
|
|
|
1294
1379
|
}
|
|
1295
1380
|
```
|
|
1296
1381
|
|
|
1382
|
+
### Route Caching
|
|
1383
|
+
|
|
1384
|
+
By default, the router client caches the html content from the server. To disable the caching, you can add the `dynamic` option to the route component.
|
|
1385
|
+
|
|
1386
|
+
```tsx
|
|
1387
|
+
// Home is a static route that can be cached on the client side
|
|
1388
|
+
function Home(this: FC) {
|
|
1389
|
+
return <p>Home</p>
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Dash is a dynamic route that will not be cached on the client side
|
|
1393
|
+
// it will be fetched from the server on every navigation
|
|
1394
|
+
function Dash(this: FC) {
|
|
1395
|
+
const user = this.session.get<{ name: string }>("user")
|
|
1396
|
+
return <p>Welcome back, {user?.name}!</p>
|
|
1397
|
+
}
|
|
1398
|
+
Dash.dynamic = true;
|
|
1399
|
+
|
|
1400
|
+
const routes = {
|
|
1401
|
+
"/": Home,
|
|
1402
|
+
"/dash": Dash,
|
|
1403
|
+
}
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1297
1406
|
## Using Session
|
|
1298
1407
|
|
|
1299
1408
|
mono-jsx provides a built-in session storage that allows you to manage sessions. To use session storage, you need to set the `session` prop on the root `<html>` element with the `cookie.secret` option.
|
package/jsx-runtime.mjs
CHANGED
|
@@ -8,6 +8,16 @@ var JSX = {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// symbols.ts
|
|
12
|
+
var $fragment = /* @__PURE__ */ Symbol.for("jsx.fragment");
|
|
13
|
+
var $html = /* @__PURE__ */ Symbol.for("jsx.html");
|
|
14
|
+
var $vnode = /* @__PURE__ */ Symbol.for("jsx.vnode");
|
|
15
|
+
var $setup = /* @__PURE__ */ Symbol.for("mono.setup");
|
|
16
|
+
var $rpc = /* @__PURE__ */ Symbol.for("mono.rpc");
|
|
17
|
+
|
|
18
|
+
// version.ts
|
|
19
|
+
var VERSION = "0.10.0-beta.6";
|
|
20
|
+
|
|
11
21
|
// runtime/index.ts
|
|
12
22
|
var EVENT = 1;
|
|
13
23
|
var CX = 2;
|
|
@@ -19,19 +29,21 @@ var SIGNALS = 64;
|
|
|
19
29
|
var SUSPENSE = 128;
|
|
20
30
|
var COMPONENT = 256;
|
|
21
31
|
var ROUTER = 512;
|
|
22
|
-
var
|
|
23
|
-
var
|
|
32
|
+
var REDIRECT = 1024;
|
|
33
|
+
var FORM = 2048;
|
|
34
|
+
var RPC = 4096;
|
|
24
35
|
var EVENT_JS = `{var w=window,m=new Map;w.$F=m.set.bind(m);w.$fmap=m;w.$emit=(e,i,s)=>m.get(i).call(w.$signals?.(s)??e.target,e.type==="mount"?e.target:e);w.$onsubmit=(e,i,s)=>{e.preventDefault();m.get(i).call(w.$signals?.(s)??e.target,new FormData(e.target),e)};}`;
|
|
25
36
|
var CX_JS = `{var n=t=>typeof t=="string"?t:typeof t=="object"&&t!==null?(Array.isArray(t)?t.map(n).filter(Boolean):Object.entries(t).filter(([,e])=>!!e).map(([e])=>e)).join(" "):"";window.$cx=n;}`;
|
|
26
|
-
var STYLE_JS = `{var f=/^(-|f[lo].*[^se]$|g.{5,}[^ps]$|z|o[pr]|(W.{5})?[lL]i.*(t|mp)$|an|(bo|s).{4}Im|sca|m.{6}[ds]|ta|c.*[st]$|wido|ini)/;var p=new Set;var
|
|
37
|
+
var STYLE_JS = `{var f=/^(-|f[lo].*[^se]$|g.{5,}[^ps]$|z|o[pr]|(W.{5})?[lL]i.*(t|mp)$|an|(bo|s).{4}Im|sca|m.{6}[ds]|ta|c.*[st]$|wido|ini)/;var p=new Set;var d=t=>typeof t=="object"&&t!==null,u=t=>d(t)&&(t.constructor===Object||t.constructor===void 0),g=t=>t.replace(/[a-z][A-Z]/g,n=>n.charAt(0)+"-"+n.charAt(1).toLowerCase()),y=t=>{let n=0;for(let e=0;e<t.length;e++)n=(n<<5)-n+t.charCodeAt(e)|0;return n>>>0};var b=(t,n)=>{let{inline:e,css:s}=h(n);if(s){let r="data-css-",o=y((e??"")+s.join("")),i=r+o.toString(36),l="["+i+"]";p.has(o)||(p.add(o),document.head.appendChild(document.createElement("style")).textContent=(e?l+"{"+e+"}":"")+s.map(a=>a===null?l:a).join("")),t.getAttributeNames().forEach(a=>a.startsWith(r)&&t.removeAttribute(a)),t.setAttribute(i,"")}else e&&t.setAttribute("style",e)},h=t=>{let n,e=[],s=new x;for(let[r,o]of Object.entries(t))switch(r.charCodeAt(0)){case 58:u(o)&&e.push(r.startsWith("::view-")?"":null,r+c(o));break;case 64:u(o)&&(r.startsWith("@keyframes ")?e.push(r+"{"+Object.entries(o).map(([i,l])=>u(l)?i+c(l):"").join("")+"}"):r.startsWith("@view-")?e.push(r+c(o)):e.push(r+"{",null,c(o)+"}"));break;case 38:u(o)&&e.push(null,r.slice(1)+c(o));break;default:n??={},n[r]=o}return n&&(s.inline=c(n).slice(1,-1)),e.length>0&&(s.css=e),s},c=t=>{let n="";for(let[e,s]of Object.entries(t)){let r=typeof s;if(r==="string"||r==="number"){let o=g(e),i=r==="number"?f.test(e)?""+s:s+"px":""+s;n+=(n?";":"")+o+":"+(o==="content"?JSON.stringify(i):i)}}return"{"+n+"}"},x=(()=>{function t(){}return t.prototype=Object.create(null),t})();window.$applyStyle=b;}`;
|
|
27
38
|
var RENDER_ATTR_JS = `{var s=(l,n,r)=>{let e=l.parentElement;return e.tagName==="M-GROUP"&&(e=e.previousElementSibling),()=>{let t=r();n==="value"?e.value=String(t):n==="checked"?e.checked=!!t:typeof t=="boolean"?e.toggleAttribute(n,t):t==null?e.removeAttribute(n):typeof t=="object"?n==="class"?e.setAttribute(n,$cx(t)):n==="style"?$applyStyle(e,t):e.setAttribute(n,JSON.stringify(t)):e.setAttribute(n,String(t))}};window.$renderAttr=s;}`;
|
|
28
39
|
var RENDER_TOGGLE_JS = `{var i=(e,s)=>{let t,l=()=>e.replaceChildren(...s()?t:[]);return()=>{if(!t){let n=e.firstElementChild;n&&n.tagName==="TEMPLATE"&&n.hasAttribute("m-slot")?t=[...n.content.childNodes]:t=[...e.childNodes]}e.hasAttribute("vt")&&document.startViewTransition?document.startViewTransition(l):l()}};window.$renderToggle=i;}`;
|
|
29
40
|
var RENDER_SWITCH_JS = `{var a=(l,u)=>{let s,r=l.getAttribute("value"),t,i,o=e=>t.get(e)??t.set(e,[]).get(e),d=()=>l.replaceChildren(...t.has(s)?t.get(s):i);return()=>{if(!t){t=new Map,i=[];for(let e of l.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):i.push(n);e.remove()}else r?o(r).push(e):i.push(e)}s=""+u(),l.hasAttribute("vt")&&document.startViewTransition?document.startViewTransition(d):d()}};window.$renderSwitch=a;}`;
|
|
30
41
|
var SIGNALS_JS = `{let h;const a=window,b=document,y=new Map,E=new Map,k=new Map,v=new Map,$=new Map,m=new Map,l=n=>y.get(n)??y.set(n,V(n)).get(n),F=()=>Object.create(null),p=(n,e)=>n.getAttribute(e),V=n=>{const e=F(),t=(o,c)=>{e[o]=c},s=new Map,r=(o,c)=>{let u=s.get(o);return u||(u=new Set,s.set(o,u)),u.add(c),()=>{u.delete(c),u.size===0&&s.delete(o)}},i=new Proxy(F(),{get:(o,c)=>b.querySelector("[data-ref='"+n+":"+c+"']")});return new Proxy(e,{get:(o,c,u)=>{switch(c){case"$init":return t;case"$watch":return r;case"app":return l(0);case"refs":return i;default:return h?.(n,c),Reflect.get(o,c,u)}},set:(o,c,u,g)=>{if(u!==Reflect.get(o,c,g)){const f=s.get(c);return f&&queueMicrotask(()=>f.forEach(d=>d())),Reflect.set(o,c,u,g)}return!1}})},M=(n,e,t)=>{switch(e){case"toggle":return $renderToggle(n,t);case"switch":return $renderSwitch(n,t);case"html":return()=>n.innerHTML=""+t()}if(e&&e.length>2&&e.startsWith("[")&&e.endsWith("]"))return $renderAttr(n,e.slice(1,-1),t);const s=n.parentElement,r=()=>n.textContent=""+t();if(b.startViewTransition&&s.hasAttribute("data-vt")){const i=s.getAttribute("data-vt");return i&&(s.style.viewTransitionName=i),()=>b.startViewTransition(r)}return r},S=n=>{const e=n.indexOf(":");return e>0?[Number(n.slice(0,e)),n.slice(e+1)]:null},q=(n,e,t,s)=>{const r=n.get(t);if(r!==void 0){s(r);return}const i=e.get(t);i?i.push(s):e.set(t,[s])},N=(n,e)=>{const t=a.$fmap?.get(n);if(t){e(t);return}const s=m.get(n);s?s.push(e):m.set(n,[e])};if(typeof a.$F=="function"){const n=a.$F;a.$F=(e,t)=>{n(e,t);const s=m.get(e);s&&(m.delete(e),s.forEach(r=>r(t)))}}const T=n=>typeof n=="function"&&n(),A=(n,e)=>customElements.define(n,class extends HTMLElement{disposes=[];connectedCallback(){e(this)}disconnectedCallback(){this.disposes.forEach(t=>t()),this.disposes.length=0}});A("m-signal",n=>{const e=Number(p(n,"scope")),t=l(e),s=p(n,"key");if(s)n.disposes.push(t.$watch(s,M(n,p(n,"mode"),()=>t[s])));else{const r=Number(p(n,"computed")),i=e+":"+r;q(E,v,i,o=>{N(r,c=>{const u=M(n,p(n,"mode"),c.bind(t));o.forEach(g=>{const[f,d]=S(g);n.disposes.push(l(f).$watch(d,u))})})})}}),A("m-effect",n=>{const{disposes:e}=n,t=Number(p(n,"scope"));q(k,$,t,s=>{const r=s.length,i=new Array(r);e.push(()=>{i.forEach(T),i.length=0});for(let o=0;o<r;o++)N(s[o],c=>{const u=[],g=l(t),f=()=>{T(i[o]),i[o]=c.call(g)};h=(d,w)=>u.push([d,w]),f(),h=void 0;for(const[d,w]of u)e.push(l(d).$watch(w,f))})})}),a.$S=(n,e)=>{const[t,s]=S(n);l(t).$init(s,e)},a.$C=(n,e,t)=>{const s=n+":"+e;E.set(s,t);const r=v.get(s);r&&(v.delete(s),r.forEach(i=>i(t)))},a.$E=(n,...e)=>{k.set(n,e);const t=$.get(n);t&&($.delete(n),t.forEach(s=>s(e)))},a.$patch=(n,...e)=>{for(const[t,...s]of e){let r=s.pop(),i=n;for(const o of s)i=i[o];i[r]=t}return n},a.$signals=n=>n!==void 0?l(n):void 0;}`;
|
|
31
42
|
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())})});}`;
|
|
32
43
|
var COMPONENT_JS = `{const e=document,a=(t,s)=>t.getAttribute(s);customElements.define("m-component",class extends HTMLElement{static observedAttributes=["name","props"];#t;#s;#r;#h;#i;#e=new Map;#a=!0;async#l(){if(!this.#t){this.#n("");return}const t=this.#s||"{}",s=this.#t+t,i={"x-component":this.#t,"x-props":t,"x-flags":$FLAGS},n=new AbortController;if(this.#h?.abort(),this.#h=n,this.#e.has(s)){this.#n(this.#e.get(s));return}this.#r?.length&&this.#n(this.#r);const r=await fetch(location.href,{headers:i,signal:n.signal});if(!r.ok)throw this.#n(""),new Error("Failed to fetch component '"+this.#t+"'");const[h,o]=await r.json();this.#e.set(s,h),this.#n(h),o&&(e.body.appendChild(e.createElement("script")).textContent=o)}#n(t){const s=()=>typeof t=="string"?this.innerHTML=t:this.replaceChildren(...t);this.hasAttribute("vt")&&e.startViewTransition&&!this.#a?e.startViewTransition(s):s(),this.#a=!1}get name(){return this.#t??null}set name(t){t&&t!==this.#t&&(this.#t=t,this.#o())}get props(){return this.#s?JSON.parse(this.#s):void 0}set props(t){const s=typeof t=="string"?t:JSON.stringify(t);s&&s!==this.#s&&(this.#s=s,this.#o())}attributeChangedCallback(t,s,i){this.#t&&i&&(t==="name"?this.name=i:t==="props"&&(this.props=i))}connectedCallback(){setTimeout(()=>{if(!this.#r){const t=a(this,"props");this.#t=a(this,"name"),this.#s=t?.startsWith("base64,")?atob(t.slice(7)):void 0,this.#r=[...this.childNodes]}this.#l()})}disconnectedCallback(){this.#e.clear(),this.#h?.abort(),this.#h=void 0,this.#i&&clearTimeout(this.#i),this.#i=void 0}#o(){this.#i&&clearTimeout(this.#i),this.#i=setTimeout(()=>{this.#i=void 0,this.#l()},50)}refresh(){this.#t&&this.#e.delete(this.#t+(this.#s||"{}")),this.#o()}});}`;
|
|
33
|
-
var ROUTER_JS = `{const
|
|
34
|
-
var
|
|
44
|
+
var ROUTER_JS = `{const o=window,r=document,s=location,d=e=>e.origin===s.origin&&l(e)===l(s),l=({pathname:e,search:t})=>e+t;customElements.define("m-router",class extends HTMLElement{#e;#i=new Map;#s=l(s);#t;#a=!0;#n;#r;async#c(e){this.#e?.abort(),this.#e=new AbortController;const t=await fetch(e,{headers:{"x-route":"true","x-flags":$FLAGS},signal:this.#e.signal});if(t.status===404)return null;if(!t.ok)throw this.replaceChildren(),new Error("Failed to fetch route: "+t.status+" "+t.statusText);const i=await t.json();if(!Array.isArray(i))throw new Error(i?.error?i.error:"Invalid response from server");return i}#o(e){const t=()=>typeof e=="string"?this.innerHTML=e:this.replaceChildren(...e);this.hasAttribute("vt")&&r.startViewTransition&&!this.#a?r.startViewTransition(t):t(),this.#a=!1}#l(){r.querySelectorAll("nav a").forEach(e=>{const{href:t,classList:i}=e,n=e.closest("nav")?.getAttribute("data-active-class")??"active";d(new URL(t))?i.add(n):i.remove(n)})}async#h(e,t){this.#s=l(e);let i,n=this.#i.get(this.#s);if(n!==void 0&&!t?.refresh&&!this.hasAttribute("no-cache"))this.#o(n);else{const h=await this.#c(e);typeof $signals<"u"&&($signals(0).url=e);let a,c;h?[a,i,c]=h:a=this.#t??[],c||this.#i.set(this.#s,a),this.#o(a)}history[t?.replace?"replaceState":"pushState"]({},"",e),this.#l(),window.scrollTo(0,0),i&&(r.body.appendChild(r.createElement("script")).textContent=i)}navigate(e,t){const i=new URL(e,s.href);if(i.origin!==s.origin||e.startsWith("#")){s.href=e;return}d(i)||this.#h(i,t)}connectedCallback(){if(o.$router)throw new Error("Only one <m-router> element is allowed on the page");o.$router=this,setTimeout(()=>{if(!this.#t)if(this.hasAttribute("fallback"))this.removeAttribute("fallback"),this.#t=[...this.childNodes];else{this.#t=[];for(const e of this.childNodes)if(e.nodeType===1&&e.tagName==="TEMPLATE"&&e.hasAttribute("m-fallback")){this.#t.push(...e.content.childNodes),e.remove();break}}this.#i.set(s.href,[...this.childNodes])}),this.#n=e=>{if(e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey||!(e.target instanceof HTMLAnchorElement))return;const t=e.target.getAttribute("href");if(!t||t.startsWith("#"))return;const{download:i,href:n,rel:h,target:a}=e.target;i||h==="external"||a==="_blank"||!n.startsWith(s.origin)||(e.preventDefault(),this.navigate(n))},this.#r=()=>{l(s)!==this.#s&&this.#h(new URL(s.href))},o.addEventListener("popstate",this.#r),r.addEventListener("click",this.#n),setTimeout(()=>this.#l())}disconnectedCallback(){o.removeEventListener("popstate",this.#r),r.removeEventListener("click",this.#n),delete o.$router,this.#e?.abort(),this.#e=void 0,this.#i.clear(),this.#n=void 0,this.#r=void 0}});}`;
|
|
45
|
+
var REDIRECT_JS = `{customElements.define("m-redirect",class extends HTMLElement{connectedCallback(){const e=this.getAttribute("to"),t=this.hasAttribute("replace");e&&($router?$router.navigate(e,{replace:t}):location.href=e)}});}`;
|
|
46
|
+
var FORM_JS = `{const d=window.document,c=(n,e)=>n.getAttribute(e),u=(n,e)=>n.appendChild(e);customElements.define("m-invalid",class extends HTMLElement{connectedCallback(){const n=c(this,"for"),e=this.closest("form"),m=this.textContent;if(n&&e&&m)for(const i of n.split(",")){const s=e.elements.namedItem(i.trim());if(s){const l=()=>{s.removeEventListener("input",l),s.setCustomValidity("")};s.addEventListener("input",l),s.setCustomValidity(m),s.focus()}}this.remove()}}),window.$onRFS=async n=>{n.preventDefault();const e=n.target;if(!e.checkValidity())return;const m=new FormData(e),i=[...e.elements];for(const l of i)l._disabled=l.disabled,l.disabled=!0;const s=await fetch(location.href,{method:"POST",headers:{"x-route-form":"true","x-flags":$FLAGS},body:m});if(s.ok){const[l,p]=await s.json(),E=d.createElement("template"),b=new Map;e.querySelectorAll("m-formslot").forEach(t=>{t.innerHTML=""}),E.innerHTML=l;for(const t of i)t.disabled=t._disabled,delete t._disabled;for(const t of E.content.childNodes){if(t.nodeType===1){const o=t,r=c(o,"formslot"),a=r?'m-formslot[name="'+r+'"]':"m-formslot",f=r?e.querySelector(a)??d.querySelector(a):e.querySelector(a);if(f){f.innerHTML="",b.set(o,f);continue}}u(e,t)}for(const[t,o]of b){const r=c(o,"onupdate"),a=c(o,"scope");switch(c(o,"mode")){case"insertbefore":o.before(t);break;case"insertafter":o.after(t);break;default:u(o,t)}r&&$fmap.get(Number(r))?.call($signals?.(Number(a))??o,{type:"update",target:o})}setTimeout(()=>{i.some(t=>!t.validity.valid)||e.reset()},0),p&&(u(d.body,d.createElement("script")).textContent=p+";document.currentScript.remove();")}};}`;
|
|
35
47
|
var RPC_JS = `{window.$RPC=(e,t)=>new Proxy(Object.create(null),{get(s,r){if(t.includes(r))return(...i)=>fetch(location.href,{method:"POST",body:JSON.stringify({fn:r,args:i}),headers:{"x-rpc":"true","x-rpc-id":e.toString()}}).then(async o=>{const{error:n,result:u}=await o.json();if(n)throw new Error(n);return u})}});}`;
|
|
36
48
|
|
|
37
49
|
// runtime/utils.ts
|
|
@@ -42,19 +54,6 @@ var isFunction = (v) => typeof v === "function";
|
|
|
42
54
|
var isObject = (v) => typeof v === "object" && v !== null;
|
|
43
55
|
var isPlainObject = (v) => isObject(v) && (v.constructor === Object || v.constructor === void 0);
|
|
44
56
|
var toHyphenCase = (k) => k.replace(/[a-z][A-Z]/g, (m) => m.charAt(0) + "-" + m.charAt(1).toLowerCase());
|
|
45
|
-
var IdGen = class extends Map {
|
|
46
|
-
#seq = 0;
|
|
47
|
-
gen(v) {
|
|
48
|
-
return this.get(v) ?? this.set(v, this.#seq++).get(v);
|
|
49
|
-
}
|
|
50
|
-
getById(id) {
|
|
51
|
-
for (const [v, i] of this.entries()) {
|
|
52
|
-
if (i === id) {
|
|
53
|
-
return v;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
57
|
var hashCode = (str) => {
|
|
59
58
|
let hash = 0;
|
|
60
59
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -171,16 +170,6 @@ var escapeHTML = (str) => {
|
|
|
171
170
|
return lastIndex !== index ? html2 + str.slice(lastIndex, index) : html2;
|
|
172
171
|
};
|
|
173
172
|
|
|
174
|
-
// symbols.ts
|
|
175
|
-
var $fragment = /* @__PURE__ */ Symbol.for("jsx.fragment");
|
|
176
|
-
var $html = /* @__PURE__ */ Symbol.for("jsx.html");
|
|
177
|
-
var $vnode = /* @__PURE__ */ Symbol.for("jsx.vnode");
|
|
178
|
-
var $setup = /* @__PURE__ */ Symbol.for("mono.setup");
|
|
179
|
-
var $rpc = /* @__PURE__ */ Symbol.for("mono.rpc");
|
|
180
|
-
|
|
181
|
-
// version.ts
|
|
182
|
-
var VERSION = "0.10.0-beta.4";
|
|
183
|
-
|
|
184
173
|
// render.ts
|
|
185
174
|
var FunctionIdGenerator = class extends Map {
|
|
186
175
|
constructor(seq = 0) {
|
|
@@ -218,6 +207,19 @@ var Ref = class {
|
|
|
218
207
|
this.name = name;
|
|
219
208
|
}
|
|
220
209
|
};
|
|
210
|
+
var IdGen = class extends Map {
|
|
211
|
+
#seq = 0;
|
|
212
|
+
gen(v) {
|
|
213
|
+
return this.get(v) ?? this.set(v, this.#seq++).get(v);
|
|
214
|
+
}
|
|
215
|
+
getById(id) {
|
|
216
|
+
for (const [v, i] of this.entries()) {
|
|
217
|
+
if (i === id) {
|
|
218
|
+
return v;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
221
223
|
var cdn = "https://raw.esm.sh";
|
|
222
224
|
var encoder = new TextEncoder();
|
|
223
225
|
var voidTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
|
|
@@ -227,8 +229,6 @@ var subtle = crypto.subtle;
|
|
|
227
229
|
var stringify = JSON.stringify;
|
|
228
230
|
var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
|
|
229
231
|
var isReactive = (v) => v instanceof Signal || v instanceof Compute;
|
|
230
|
-
var isFC = (v) => isFunction(v) && v.name.charCodeAt(0) <= /*Z*/
|
|
231
|
-
90;
|
|
232
232
|
var identifierRegex = /^[A-Za-z_$][0-9A-Za-z_$]*$/;
|
|
233
233
|
var escapeCSSText = (str) => str.replace(/[><]/g, (m) => m.charCodeAt(0) === 60 ? "<" : ">");
|
|
234
234
|
var toAttrStringLit = (str) => '"' + escapeHTML(str) + '"';
|
|
@@ -240,8 +240,7 @@ function renderToWebStream(root, options) {
|
|
|
240
240
|
const reqHeaders = request?.headers;
|
|
241
241
|
const componentName = reqHeaders?.get("x-component");
|
|
242
242
|
const routeForm = reqHeaders?.has("x-route-form");
|
|
243
|
-
|
|
244
|
-
if (rpc) {
|
|
243
|
+
if (reqHeaders?.has("x-rpc")) {
|
|
245
244
|
if (!request || request.method !== "POST") {
|
|
246
245
|
return new Response(null, { status: 405 });
|
|
247
246
|
}
|
|
@@ -277,7 +276,7 @@ function renderToWebStream(root, options) {
|
|
|
277
276
|
}
|
|
278
277
|
try {
|
|
279
278
|
const rpcSession = session ? await createSession(request, session) : void 0;
|
|
280
|
-
const result = await rpcFunction.apply(
|
|
279
|
+
const result = await rpcFunction.apply(createInvokeScope(request, context, rpcSession), args);
|
|
281
280
|
return Response.json({ result });
|
|
282
281
|
} catch (err) {
|
|
283
282
|
return Response.json({ error: errorStringify(err) }, { status: 500 });
|
|
@@ -333,7 +332,7 @@ function renderToWebStream(root, options) {
|
|
|
333
332
|
}
|
|
334
333
|
if (reqHeaders?.has("x-route") || routeForm) {
|
|
335
334
|
if (!routeFC) {
|
|
336
|
-
return Response.json({ error:
|
|
335
|
+
return Response.json({ error: "Route not found", status }, { headers, status });
|
|
337
336
|
}
|
|
338
337
|
component = routeFC;
|
|
339
338
|
}
|
|
@@ -344,23 +343,24 @@ function renderToWebStream(root, options) {
|
|
|
344
343
|
async start(controller) {
|
|
345
344
|
try {
|
|
346
345
|
if (component instanceof Promise) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
component
|
|
346
|
+
const { default: defaultExport, ...rest } = await component;
|
|
347
|
+
if (isFunction(defaultExport)) {
|
|
348
|
+
component = defaultExport;
|
|
349
|
+
Object.assign(component, rest);
|
|
351
350
|
}
|
|
352
351
|
}
|
|
353
352
|
let propsHeader = reqHeaders?.get("x-props");
|
|
354
353
|
let props = propsHeader ? JSON.parse(propsHeader) : {};
|
|
355
354
|
let html2 = "";
|
|
356
355
|
let js = "";
|
|
357
|
-
let
|
|
356
|
+
let buf = "";
|
|
358
357
|
let vnode = [component, props, $vnode];
|
|
359
358
|
if (routeForm && request?.method === "POST") {
|
|
360
|
-
|
|
359
|
+
const FormHandler = component.FormHandler;
|
|
360
|
+
if (!FormHandler || !isFunction(FormHandler)) {
|
|
361
361
|
throw new Error(component.name + ".FormHandler is undefined or not a function");
|
|
362
362
|
}
|
|
363
|
-
vnode = [
|
|
363
|
+
vnode = [FormHandler, await request.formData(), $vnode];
|
|
364
364
|
}
|
|
365
365
|
await render(
|
|
366
366
|
vnode,
|
|
@@ -370,15 +370,16 @@ function renderToWebStream(root, options) {
|
|
|
370
370
|
true,
|
|
371
371
|
routeForm
|
|
372
372
|
);
|
|
373
|
-
|
|
373
|
+
buf = "[" + stringify(html2);
|
|
374
374
|
if (js) {
|
|
375
|
-
|
|
375
|
+
buf += "," + stringify(js);
|
|
376
376
|
}
|
|
377
|
-
|
|
377
|
+
if (component.dynamic) {
|
|
378
|
+
buf += ",true";
|
|
379
|
+
}
|
|
380
|
+
controller.enqueue(encoder.encode(buf + "]"));
|
|
378
381
|
} catch (err) {
|
|
379
|
-
controller.enqueue(encoder.encode(
|
|
380
|
-
'["",' + stringify('console.log("[mono-jsx]",' + stringify(err.stack)) + "]"
|
|
381
|
-
));
|
|
382
|
+
controller.enqueue(encoder.encode(stringify({ error: errorStringify(err) })));
|
|
382
383
|
console.error(err);
|
|
383
384
|
} finally {
|
|
384
385
|
controller.close();
|
|
@@ -417,7 +418,7 @@ function renderToWebStream(root, options) {
|
|
|
417
418
|
);
|
|
418
419
|
}
|
|
419
420
|
async function render(node, options, write, writeJS, componentMode, routeForm) {
|
|
420
|
-
const { app, context, request, routeFC } = options;
|
|
421
|
+
const { app, context, request, routeFC, metadata } = options;
|
|
421
422
|
const suspenses = [];
|
|
422
423
|
const signals = {
|
|
423
424
|
app: {},
|
|
@@ -432,14 +433,14 @@ async function render(node, options, write, writeJS, componentMode, routeForm) {
|
|
|
432
433
|
request,
|
|
433
434
|
signals,
|
|
434
435
|
routeFC,
|
|
436
|
+
metadata,
|
|
435
437
|
flags: { scope: 0, chunk: 0, runtime: 0 },
|
|
436
|
-
fidGenerator: new FunctionIdGenerator()
|
|
437
|
-
extraJS: []
|
|
438
|
+
fidGenerator: new FunctionIdGenerator()
|
|
438
439
|
};
|
|
439
440
|
signals.app = Object.assign(createThisProxy(rc, 0), app);
|
|
440
441
|
let runtimeFlag = 0;
|
|
441
442
|
const finalize = async () => {
|
|
442
|
-
const {
|
|
443
|
+
const { fidGenerator, session, flags } = rc;
|
|
443
444
|
const computes = signals.computes;
|
|
444
445
|
const hasEffect = signals.effects.length > 0;
|
|
445
446
|
const treeshake = (flag, code, force) => {
|
|
@@ -461,16 +462,14 @@ async function render(node, options, write, writeJS, componentMode, routeForm) {
|
|
|
461
462
|
treeshake(SUSPENSE, SUSPENSE_JS, suspenses.length > 0);
|
|
462
463
|
treeshake(COMPONENT, COMPONENT_JS);
|
|
463
464
|
treeshake(ROUTER, ROUTER_JS);
|
|
465
|
+
treeshake(REDIRECT, REDIRECT_JS);
|
|
464
466
|
treeshake(FORM, FORM_JS);
|
|
465
|
-
if (js.length > 0) {
|
|
466
|
-
js = "(()=>{" + js + "})();/*!*/";
|
|
467
|
-
}
|
|
468
467
|
if (runtimeFlag & ROUTER && runtimeFlag & SIGNALS && request) {
|
|
469
468
|
const { params } = request;
|
|
470
469
|
const url = "new URL(" + stringify(request.url) + ")";
|
|
471
470
|
const urlWithParams = params ? "Object.assign(" + url + "," + stringify(params) + ")" : url;
|
|
472
471
|
if (componentMode) {
|
|
473
|
-
if (!routeForm) {
|
|
472
|
+
if (!routeForm && params) {
|
|
474
473
|
js += "$signals(0).url=" + urlWithParams + ";";
|
|
475
474
|
}
|
|
476
475
|
} else {
|
|
@@ -562,12 +561,9 @@ async function render(node, options, write, writeJS, componentMode, routeForm) {
|
|
|
562
561
|
if (sameSite) {
|
|
563
562
|
cookie += "; SameSite=" + sameSite;
|
|
564
563
|
}
|
|
565
|
-
js
|
|
564
|
+
js = "document.cookie=" + toAttrStringLit(cookie) + ";" + js;
|
|
566
565
|
}
|
|
567
566
|
}
|
|
568
|
-
if (extraJS.length > 0) {
|
|
569
|
-
js += extraJS.splice(0, extraJS.length).join("");
|
|
570
|
-
}
|
|
571
567
|
if (js.length > 0) {
|
|
572
568
|
writeJS(js);
|
|
573
569
|
}
|
|
@@ -695,7 +691,11 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
695
691
|
// `<switch>` element
|
|
696
692
|
case "switch": {
|
|
697
693
|
const { value: valueProp, children } = props;
|
|
698
|
-
if (
|
|
694
|
+
if (rc.svg && valueProp === void 0) {
|
|
695
|
+
write("<switch>");
|
|
696
|
+
await renderChildren(rc, children);
|
|
697
|
+
write("</switch>");
|
|
698
|
+
} else if (children !== void 0) {
|
|
699
699
|
let slots = Array.isArray(children) ? isVNode(children) ? [children] : children : [children];
|
|
700
700
|
let matchedSlot;
|
|
701
701
|
let namedSlots = [];
|
|
@@ -761,11 +761,11 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
761
761
|
};
|
|
762
762
|
if (isVNode(as)) {
|
|
763
763
|
const [fc, props2] = as;
|
|
764
|
-
if (
|
|
764
|
+
if (isFunction(fc)) {
|
|
765
765
|
attrs += ' name="@comp_' + componentsMap.gen(fc) + '"';
|
|
766
766
|
writeAttr("props", props2);
|
|
767
767
|
}
|
|
768
|
-
} else if (
|
|
768
|
+
} else if (isFunction(is)) {
|
|
769
769
|
attrs += ' name="@comp_' + componentsMap.gen(is) + '"';
|
|
770
770
|
writeAttr("props");
|
|
771
771
|
} else if (props.name) {
|
|
@@ -803,7 +803,14 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
803
803
|
}
|
|
804
804
|
write("<m-router" + attrs + ">");
|
|
805
805
|
if (routeFC) {
|
|
806
|
-
|
|
806
|
+
if (routeFC instanceof Promise) {
|
|
807
|
+
routeFC = (await routeFC).default;
|
|
808
|
+
if (!routeFC || !isFunction(routeFC)) {
|
|
809
|
+
console.warn("[mono-jsx] <router> The `default` export is not a function component.");
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
await renderFC(rc, routeFC, {}, true);
|
|
807
814
|
}
|
|
808
815
|
if (children) {
|
|
809
816
|
if (routeFC) {
|
|
@@ -822,6 +829,32 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
822
829
|
rc.flags.runtime |= ROUTER;
|
|
823
830
|
break;
|
|
824
831
|
}
|
|
832
|
+
case "metadata": {
|
|
833
|
+
if (rc.svg) {
|
|
834
|
+
write("<metadata>");
|
|
835
|
+
await renderChildren(rc, props.children);
|
|
836
|
+
write("</metadata>");
|
|
837
|
+
} else if (rc.routeFC) {
|
|
838
|
+
let { metadata, getMetadata } = await rc.routeFC;
|
|
839
|
+
if (isFunction(getMetadata)) {
|
|
840
|
+
const { request, context, session } = rc;
|
|
841
|
+
if (!request) {
|
|
842
|
+
throw new TypeError("[mono-jsx] The `request` prop in the `<html>` element is required.");
|
|
843
|
+
}
|
|
844
|
+
metadata = await getMetadata.call(createInvokeScope(request, context, session));
|
|
845
|
+
}
|
|
846
|
+
let buf = "";
|
|
847
|
+
for (const [key, value] of Object.entries(Object.assign({}, rc.metadata, metadata))) {
|
|
848
|
+
if (key === "title") {
|
|
849
|
+
buf += "<title>" + value + "</title>";
|
|
850
|
+
} else {
|
|
851
|
+
buf += "<meta name=" + toAttrStringLit(key) + " content=" + toAttrStringLit(value) + ">";
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
write(buf);
|
|
855
|
+
}
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
825
858
|
case "cache":
|
|
826
859
|
case "static": {
|
|
827
860
|
const { $stack, key = $stack, maxAge, children } = props;
|
|
@@ -857,10 +890,15 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
857
890
|
}
|
|
858
891
|
case "redirect": {
|
|
859
892
|
const { to, replace } = props;
|
|
860
|
-
if (to) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
)
|
|
893
|
+
if (isString(to) || to instanceof URL) {
|
|
894
|
+
let buf = "<m-redirect";
|
|
895
|
+
buf += " to=" + toAttrStringLit(String(to));
|
|
896
|
+
if (replace) {
|
|
897
|
+
buf += " replace";
|
|
898
|
+
}
|
|
899
|
+
buf += "></m-redirect>";
|
|
900
|
+
write(buf);
|
|
901
|
+
rc.flags.runtime |= REDIRECT;
|
|
864
902
|
}
|
|
865
903
|
break;
|
|
866
904
|
}
|
|
@@ -915,8 +953,8 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
915
953
|
}
|
|
916
954
|
let buffer = "<" + tag;
|
|
917
955
|
let attrModifiers = "";
|
|
918
|
-
let
|
|
919
|
-
let isSvgSelfClosingElement = rc.svg &&
|
|
956
|
+
let noChildren = props.children === void 0;
|
|
957
|
+
let isSvgSelfClosingElement = rc.svg && noChildren;
|
|
920
958
|
for (let [propName, propValue] of Object.entries(props)) {
|
|
921
959
|
switch (propName) {
|
|
922
960
|
case "children":
|
|
@@ -924,7 +962,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
924
962
|
break;
|
|
925
963
|
case "route":
|
|
926
964
|
if (tag === "form") {
|
|
927
|
-
buffer += ' onsubmit="$
|
|
965
|
+
buffer += ' onsubmit="$onRFS(event)"';
|
|
928
966
|
rc.flags.runtime |= FORM;
|
|
929
967
|
break;
|
|
930
968
|
}
|
|
@@ -947,7 +985,7 @@ async function renderNode(rc, node, stripSlotProp) {
|
|
|
947
985
|
if (attrModifiers) {
|
|
948
986
|
write(attrModifiers);
|
|
949
987
|
}
|
|
950
|
-
if (!
|
|
988
|
+
if (!noChildren) {
|
|
951
989
|
await renderChildren(tag === "svg" ? { ...rc, svg: true } : rc, props.children);
|
|
952
990
|
}
|
|
953
991
|
if (!isSvgSelfClosingElement) {
|
|
@@ -1061,7 +1099,7 @@ async function renderFC(rc, fcFn, props, eager) {
|
|
|
1061
1099
|
});
|
|
1062
1100
|
} else {
|
|
1063
1101
|
console.error(err);
|
|
1064
|
-
write("<script>console.error(" + stringify(err
|
|
1102
|
+
write("<script>console.error(" + stringify(err) + ")<\/script>");
|
|
1065
1103
|
}
|
|
1066
1104
|
}
|
|
1067
1105
|
}
|
|
@@ -1357,7 +1395,7 @@ function createThisProxy(rc, scopeId) {
|
|
|
1357
1395
|
});
|
|
1358
1396
|
return thisProxy;
|
|
1359
1397
|
}
|
|
1360
|
-
function
|
|
1398
|
+
function createInvokeScope(request, context, session) {
|
|
1361
1399
|
const scope = new NullPrototypeObject();
|
|
1362
1400
|
Object.assign(scope, { request, context: context ?? {} });
|
|
1363
1401
|
Object.defineProperty(scope, "session", {
|
|
@@ -1469,7 +1507,19 @@ var jsx = (tag, props = new NullPrototypeObject(), key) => {
|
|
|
1469
1507
|
return props;
|
|
1470
1508
|
}
|
|
1471
1509
|
const renderOptions = new NullPrototypeObject();
|
|
1472
|
-
const optionsKeys = /* @__PURE__ */ new Set([
|
|
1510
|
+
const optionsKeys = /* @__PURE__ */ new Set([
|
|
1511
|
+
"app",
|
|
1512
|
+
"context",
|
|
1513
|
+
"components",
|
|
1514
|
+
"expose",
|
|
1515
|
+
"routes",
|
|
1516
|
+
"request",
|
|
1517
|
+
"session",
|
|
1518
|
+
"status",
|
|
1519
|
+
"headers",
|
|
1520
|
+
"htmx",
|
|
1521
|
+
"metadata"
|
|
1522
|
+
]);
|
|
1473
1523
|
for (const [key2, value] of Object.entries(props)) {
|
|
1474
1524
|
if (optionsKeys.has(key2) || key2.startsWith("htmx-ext-")) {
|
|
1475
1525
|
renderOptions[key2] = value;
|
package/package.json
CHANGED
package/types/jsx-runtime.d.ts
CHANGED
package/types/jsx.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type ChildPrimitiveType = JSX.Element | string | number | bigint | boolea
|
|
|
4
4
|
export type ChildType = MaybeArray<MaybeGetter<ChildPrimitiveType>>;
|
|
5
5
|
export type MaybeArray<T> = T | T[];
|
|
6
6
|
export type MaybeGetter<T> = T | { get: () => T };
|
|
7
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
8
|
+
export type MaybeModule<T> = T | Promise<{ default: T } & ComponentConfig>;
|
|
7
9
|
export type MaybePromiseOrGenerator<T> = T | Promise<T> | Generator<T> | AsyncGenerator<T>;
|
|
8
10
|
|
|
9
11
|
export interface BaseAttributes {
|
|
@@ -54,11 +56,18 @@ export type VNode = readonly [
|
|
|
54
56
|
$vnode: symbol,
|
|
55
57
|
];
|
|
56
58
|
|
|
57
|
-
export interface
|
|
58
|
-
|
|
59
|
+
export interface ComponentConfig {
|
|
60
|
+
FormHandler?: ComponentType<FormData>;
|
|
61
|
+
getMetadata?: () => MaybePromise<Record<string, string>>;
|
|
62
|
+
metadata?: Record<string, string>;
|
|
63
|
+
dynamic?: boolean;
|
|
59
64
|
rendering?: string;
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
export interface ComponentType<P = {}> extends ComponentConfig {
|
|
68
|
+
(props: P): MaybePromiseOrGenerator<VNode | string | null>;
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
export interface MonoBuiltinElements {
|
|
63
72
|
/**
|
|
64
73
|
* A built-in element of mono-jsx that toggles the visibility of its children.
|
package/types/mono.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type WithParams<T> = T & { params?: Record<string, string> };
|
|
|
4
4
|
|
|
5
5
|
export interface Elements {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* The `<component>` element is used to load components lazily,
|
|
8
8
|
* which can improve performance by reducing the initial load time of the application.
|
|
9
9
|
* @mono-jsx
|
|
10
10
|
*/
|
|
@@ -37,7 +37,7 @@ export interface Elements {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* The `<router>` element provides SPA routing.
|
|
41
41
|
* @mono-jsx
|
|
42
42
|
*/
|
|
43
43
|
router: BaseAttributes & AsyncComponentAttributes & {
|
|
@@ -57,7 +57,7 @@ export interface Elements {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
60
|
+
* The `<cache>` element caches the rendered content of the child nodes
|
|
61
61
|
* with the given key and TTL.
|
|
62
62
|
* @mono-jsx
|
|
63
63
|
*/
|
|
@@ -73,14 +73,14 @@ export interface Elements {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* The `<static>` element treats the child nodes as static content,
|
|
77
77
|
* When the child nodes are rendered once, they will be cached in memory and reused on subsequent renders.
|
|
78
78
|
* @mono-jsx
|
|
79
79
|
*/
|
|
80
80
|
static: BaseAttributes;
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* The `<redirect>` element redirects to the given URL in the client side.
|
|
84
84
|
* @mono-jsx
|
|
85
85
|
*/
|
|
86
86
|
redirect: {
|
|
@@ -96,7 +96,7 @@ export interface Elements {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
99
|
+
* The `<invalid>` element sets custom validation
|
|
100
100
|
* state for the form elements.
|
|
101
101
|
* @mono-jsx
|
|
102
102
|
*/
|
|
@@ -108,7 +108,7 @@ export interface Elements {
|
|
|
108
108
|
};
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
111
|
+
* The `<formslot>` element is used to display the content of the route form
|
|
112
112
|
* in the `form` element.
|
|
113
113
|
* @mono-jsx
|
|
114
114
|
*/
|
|
@@ -147,11 +147,29 @@ export type ComponentElement = {
|
|
|
147
147
|
refresh: () => Promise<void>;
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The options for the `navigate` method of the `<router>` element.
|
|
152
|
+
*/
|
|
153
|
+
export type RouterNavigateOptions = {
|
|
154
|
+
/**
|
|
155
|
+
* If true, `history.replaceState` will be called instead of `history.pushState`.
|
|
156
|
+
* This is useful when you want to replace the current navigation without adding a new entry to the history.
|
|
157
|
+
*/
|
|
158
|
+
replace?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* If true, the router will ignore the cache and fetch the page HTML from the server.
|
|
161
|
+
*/
|
|
162
|
+
refresh?: boolean;
|
|
163
|
+
};
|
|
164
|
+
|
|
150
165
|
/**
|
|
151
166
|
* The `<router>` element.
|
|
152
167
|
*/
|
|
153
168
|
export type RouterElement = {
|
|
154
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Navigates to the given URL.
|
|
171
|
+
*/
|
|
172
|
+
navigate: (url: string | URL, options?: RouterNavigateOptions) => Promise<void>;
|
|
155
173
|
};
|
|
156
174
|
|
|
157
175
|
/**
|
package/types/render.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/// <reference path="./htmx.d.ts" />
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import type { ComponentType } from "./jsx.d.ts";
|
|
5
|
-
|
|
6
|
-
export type MaybeModule<T> = T | Promise<{ default: T; FormHandler?: Function }>;
|
|
3
|
+
import type { ComponentType, MaybeModule } from "./jsx.d.ts";
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* Htmx extensions.
|
|
@@ -89,6 +86,10 @@ export interface RenderOptions extends Partial<HtmxExts> {
|
|
|
89
86
|
* Routes to be used by the `<router>` element.
|
|
90
87
|
*/
|
|
91
88
|
routes?: Record<string, MaybeModule<ComponentType<any>>>;
|
|
89
|
+
/**
|
|
90
|
+
* Global metadata.
|
|
91
|
+
*/
|
|
92
|
+
metadata?: Record<string, string>;
|
|
92
93
|
/**
|
|
93
94
|
* Current `Request` object to be passed to components.
|
|
94
95
|
*/
|