what-router 0.11.1 → 0.11.2
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/dist/index.min.js +1 -1
- package/package.json +2 -2
- package/src/index.js +15 -9
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{signal as
|
|
1
|
+
import{signal as x,effect as U,computed as S,batch as I,h as a,ErrorBoundary as V}from"what-core";function L(e){let t=e.replace(/\([\w-]+\)\//g,"").replace(/\[\.\.\.(\w+)\]/g,(r,c)=>`*:${c}`).replace(/\[(\w+)\]/g,":$1"),n=[],s=null,o=t.split("/").map(r=>r.startsWith("*:")?(s=r.slice(2),n.push(s),"(.+)"):r==="*"?(s="rest",n.push("rest"),"(.+)"):r.startsWith(":")?(n.push(r.slice(1)),"([^/]+)"):r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("/");return{regex:new RegExp(`^${o}$`),paramNames:n,catchAll:s}}function A(e,t){let s=t.filter(o=>o.path).sort((o,i)=>{let r=(o.path.match(/:/g)||[]).length+(o.path.includes("*")?100:0),c=(i.path.match(/:/g)||[]).length+(i.path.includes("*")?100:0);return r-c});for(let o of s){let{regex:i,paramNames:r}=L(o.path),c=e.match(i);if(c){let d={};return r.forEach((u,l)=>{d[u]=decodeURIComponent(c[l+1])}),{route:o,params:d}}}return null}function q(e){let t={};if(!e)return t;let n=e.startsWith("?")?e.slice(1):e;for(let s of n.split("&")){let[o,i]=s.split("=");if(!o)continue;let r=decodeURIComponent(o),c=i?decodeURIComponent(i):"";r in t?Array.isArray(t[r])?t[r].push(c):t[r]=[t[r],c]:t[r]=c}return t}function C(e){if(typeof e!="string")return!1;let n=e.trim().replace(/[\s\x00-\x1f]/g,"").toLowerCase();return!(n.startsWith("javascript:")||n.startsWith("data:")||n.startsWith("vbscript:"))}var p=x(typeof location<"u"?location.pathname+location.search+location.hash:"/"),T=x({}),$=x({}),w=x(!1),W=x(null),h={get url(){return p()},get path(){return p().split("?")[0].split("#")[0]},get params(){return T()},get query(){return $()},get hash(){let e=p().split("#")[1];return e?"#"+e:""},get isNavigating(){return w()},get error(){return W()}};async function v(e,t={}){let{replace:n=!1,state:s=null,transition:o=!0,_fromPopstate:i=!1}=t;if(!C(e)){typeof console<"u"&&console.warn(`[what-router] Blocked navigation to unsafe URL: ${e}`);return}if(typeof window<"u"&&e.startsWith("#")){let u=p().split("#")[0]+e;history.replaceState(s,"",u),p.set(u);let l=document.querySelector(e);l&&l.scrollIntoView({behavior:"smooth"});return}if(e===p()||w.peek())return;w.set(!0),W.set(null);let r=()=>{i||(typeof window<"u"&&k.set(p(),{x:scrollX,y:scrollY}),n?history.replaceState(s,"",e):history.pushState(s,"",e)),p.set(e),w.set(!1)};if(o&&typeof document<"u"&&document.startViewTransition)try{await document.startViewTransition(r).finished}catch{}else r()}typeof window<"u"&&window.addEventListener("popstate",()=>{k.set(p(),{x:scrollX,y:scrollY});let e=location.pathname+location.search+location.hash;v(e,{replace:!0,_fromPopstate:!0,transition:!1}).then(()=>{let t=k.get(e);t&&requestAnimationFrame(()=>window.scrollTo(t.x,t.y))})});function K(e,t){let n=[];if(!e.path)return n;let s=e.path.split("/").filter(Boolean),o="";for(let i of s){o+="/"+i;let r=t.find(c=>c.layout&&c.path===o+"/_layout");r&&n.push(r.layout)}return e.layout&&n.push(e.layout),n}var g=[],B=10;function G({routes:e,fallback:t,globalLayout:n}){let s=()=>{let o=p(),i=o.split("?")[0].split("#")[0],r=o.split("?")[1]?.split("#")[0]||"",c=w(),d=A(i,e);if(d){I(()=>{T.set(d.params),$.set(q(r))});let{route:u,params:l}=d,m=q(r);if(u.middleware&&u.middleware.length>0)for(let b of u.middleware){let P=b({path:i,params:l,query:m,route:u});if(P===!1)return t?a(t,{}):a("div",{class:"what-403"},a("h1",null,"403"),a("p",null,"Access denied"));if(typeof P=="string"){if(g.push(P),g.length>B){let R=g.slice(-5).join(" \u2192 ");return g.length=0,console.error(`[what-router] Redirect loop detected: ${R}`),w.set(!1),a("div",{class:"what-redirect-loop"},a("h1",null,"Redirect Loop"),a("p",null,"Too many redirects. Check your middleware configuration."))}let E=new Set,_=!1;for(let R of g){if(E.has(R)){_=!0;break}E.add(R)}if(_){let R=g.join(" \u2192 ");return g.length=0,console.error(`[what-router] Redirect cycle detected: ${R}`),w.set(!1),a("div",{class:"what-redirect-loop"},a("h1",null,"Redirect Loop"),a("p",null,"Circular redirect detected. Check your middleware configuration."))}return v(P,{replace:!0}),null}}g.length=0;let y;u.loading&&c?y=a(u.loading,{}):y=a(u.component,{params:l,query:m,route:u}),u.error&&(y=a(V,{fallback:u.error},y));let f=K(u,e);for(let b of f.reverse())y=a(b,{params:l,query:m},y);return y}return t?a(t,{}):a("div",{class:"what-404"},a("h1",null,"404"),a("p",null,"Page not found"))};return n?a(n,{},s):s}function z({href:e,class:t,className:n,children:s,replace:o,prefetch:i=!0,activeClass:r="active",exactActiveClass:c="exact-active",transition:d=!0,...u}){let l=C(e)?e:"about:blank";!C(e)&&typeof console<"u"&&console.warn(`[what-router] Link blocked unsafe href: ${e}`);let m=l.split("?")[0].split("#")[0];return a("a",{href:l,class:()=>{let f=h.path,b=m==="/"?f==="/":f===m||f.startsWith(m+"/");return[t||n,b&&r,f===m&&c].filter(Boolean).join(" ")||void 0},onclick:f=>{f.ctrlKey||f.metaKey||f.shiftKey||f.altKey||f.button!==0||(f.preventDefault(),v(l,{replace:o,transition:d}))},onmouseenter:i?()=>j(l):void 0,...u},...Array.isArray(s)?s:[s])}function Y(e){return z(e)}function H(e){return Object.entries(e).map(([t,n])=>typeof n=="function"?{path:t,component:n}:{path:t,...n})}function M(e,t,n={}){let{layout:s,loading:o,error:i}=n;return t.map(r=>({...r,path:e+r.path,layout:r.layout||s,loading:r.loading||o,error:r.error||i}))}function J(e,t,n={}){let{layout:s,middleware:o}=n;return t.map(i=>({...i,_group:e,layout:i.layout||s,middleware:[...i.middleware||[],...o||[]]}))}function Z({to:e}){return v(e,{replace:!0}),null}function ee(e,t){return n=>function(o){let i=e(o);return i instanceof Promise?a("div",{class:"what-guard-loading"},"Loading..."):i?a(n,o):typeof t=="string"?(v(t,{replace:!0}),null):a(t,o)}}function te(e,t={}){let{fallback:n="/login",loading:s=null}=t;return o=>function(r){let c=x("pending"),d=x(null),u=!1;return U(()=>(u=!1,Promise.resolve(e(r)).then(l=>{u||(d.set(l),c.set(l?"allowed":"denied"))}).catch(()=>{u||c.set("denied")}),()=>{u=!0})),()=>{let l=c();return l==="pending"?s?a(s,{}):null:l==="allowed"?a(o,r):typeof n=="string"?(v(n,{replace:!0}),null):a(n,r)}}}var N=new Set;function j(e){if(typeof document>"u"||N.has(e))return;N.add(e);let t=document.createElement("link");t.rel="prefetch",t.href=e,document.head.appendChild(t)}var k=new Map;function ne(){typeof window>"u"||(window.addEventListener("beforeunload",()=>{k.set(location.pathname,window.scrollY)}),U(()=>{let e=h.path,t=k.get(e);requestAnimationFrame(()=>{t!==void 0?window.scrollTo(0,t):h.hash?document.querySelector(h.hash)?.scrollIntoView():window.scrollTo(0,0)})}))}function re(e){return{style:{viewTransitionName:e}}}function oe(e){typeof document>"u"||(document.documentElement.dataset.transition=e)}function ie(){return{path:S(()=>h.path),params:S(()=>h.params),query:S(()=>h.query),hash:S(()=>h.hash),isNavigating:S(()=>h.isNavigating),navigate:v,prefetch:j}}function se({children:e}){return e||null}function ae({routes:e,layout:t,fallback:n,error:s}){let o=e.map(i=>({path:i.path,component:i.component,layout:i.layout||void 0,_mode:i.mode||"client"}));return G({routes:o,globalLayout:t,fallback:n||D})}function D(){return a("div",{style:"text-align:center;padding:60px 20px"},a("h1",{style:"font-size:48px;margin-bottom:8px"},"404"),a("p",{style:"color:#64748b"},"Page not found"))}export{ae as FileRouter,z as Link,Y as NavLink,se as Outlet,Z as Redirect,G as Router,te as asyncGuard,L as compilePath,H as defineRoutes,ne as enableScrollRestoration,ee as guard,C as isSafeUrl,A as matchRoute,v as navigate,M as nestedRoutes,q as parseQuery,j as prefetch,h as route,J as routeGroup,oe as setViewTransition,ie as useRoute,re as viewTransitionName};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-router",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "What Framework - File-based & programmatic router with View Transitions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"author": "ZVN DEV (https://zvndev.com)",
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"what-core": "^0.11.
|
|
35
|
+
"what-core": "^0.11.2"
|
|
36
36
|
},
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -167,10 +167,11 @@ const MAX_REDIRECTS = 10;
|
|
|
167
167
|
// --- Router Component ---
|
|
168
168
|
|
|
169
169
|
export function Router({ routes, fallback, globalLayout }) {
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
170
|
+
// The Router component runs ONCE. `content` is a reactive function child that
|
|
171
|
+
// re-evaluates whenever _url changes; the fine-grained runtime reconciles only
|
|
172
|
+
// the matched page in place. The globalLayout is rendered ONCE around it (below)
|
|
173
|
+
// so the app shell persists across navigations instead of re-instantiating.
|
|
174
|
+
const content = () => {
|
|
174
175
|
const currentUrl = _url();
|
|
175
176
|
const path = currentUrl.split('?')[0].split('#')[0];
|
|
176
177
|
const search = currentUrl.split('?')[1]?.split('#')[0] || '';
|
|
@@ -259,11 +260,6 @@ export function Router({ routes, fallback, globalLayout }) {
|
|
|
259
260
|
element = h(Layout, { params, query: queryObj }, element);
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
// Global layout wrapper
|
|
263
|
-
if (globalLayout) {
|
|
264
|
-
element = h(globalLayout, {}, element);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
263
|
return element;
|
|
268
264
|
}
|
|
269
265
|
|
|
@@ -274,6 +270,16 @@ export function Router({ routes, fallback, globalLayout }) {
|
|
|
274
270
|
h('p', null, 'Page not found')
|
|
275
271
|
);
|
|
276
272
|
};
|
|
273
|
+
|
|
274
|
+
// Render the global layout ONCE so it — and everything it mounts (sidebars,
|
|
275
|
+
// toasters, command palettes, global key listeners) — PERSISTS across
|
|
276
|
+
// navigations; the reactive `content` child swaps the matched page in place.
|
|
277
|
+
// A function child is a reactive region (see render.insert), so on navigation
|
|
278
|
+
// only the page subtree reconciles, never the shell. 404/403/redirect screens
|
|
279
|
+
// now render inside the shell too (the natural meaning of a "global" layout).
|
|
280
|
+
// Without a globalLayout, `content` itself is the reactive root — Router still
|
|
281
|
+
// returns a reactive function child (back-compat).
|
|
282
|
+
return globalLayout ? h(globalLayout, {}, content) : content;
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
// --- Link Component ---
|