what-router 0.13.4 → 0.13.5
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 +2 -2
- package/dist/match.min.js +1 -1
- package/package.json +2 -2
- package/src/index.js +1 -0
- package/src/match.js +4 -6
package/dist/index.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{signal as R,effect as
|
|
1
|
+
import{signal as R,effect as G,computed as b,batch as M,h as c,ErrorBoundary as X}from"what-core";function U(e){let r=e.replace(/\([\w-]+\)\//g,"").replace(/\[\.\.\.(\w+)\]/g,(a,l)=>`*:${l}`).replace(/\[(\w+)\]/g,":$1"),t=[],n=null,o=r.split("/").map(a=>a.startsWith("*:")?(n=a.slice(2),t.push(n),"(.+)"):a==="*"?(n="rest",t.push("rest"),"(.+)"):a.startsWith(":")?(t.push(a.slice(1)),"([^/]+)"):a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("/");return{regex:new RegExp(`^${o}$`),paramNames:t,catchAll:n}}var E=new Map,z=1e3;function H(e){let r=E.get(e);return r||(r=U(e),E.size>=z&&E.clear(),E.set(e,r)),r}function K(e,r){let t;try{t=decodeURIComponent(e)}catch{return null}let n=t.split(/[/\\]/);return!r&&n.length>1||n.includes("..")?null:t}function _(e,r){let t=r.filter(n=>n.path);t.sort((n,o)=>{let i=(n.path.match(/:/g)||[]).length+(n.path.includes("*")?100:0),a=(o.path.match(/:/g)||[]).length+(o.path.includes("*")?100:0);return i-a});for(let n of t){let{regex:o,paramNames:i,catchAll:a}=H(n.path),l=e.match(o);if(l){let p={},u=!1;if(i.forEach((s,d)=>{let m=K(l[d+1],s===a);if(m===null){u=!0;return}p[s]=m}),u)continue;return{route:n,params:p}}}return null}function A(e){let r=Object.create(null);if(!e)return r;for(let[t,n]of new URLSearchParams(e))Object.prototype.hasOwnProperty.call(r,t)?Array.isArray(r[t])?r[t].push(n):r[t]=[r[t],n]:r[t]=n;return r}var Q=new Set(["http:","https:","mailto:","tel:"]);function k(e){if(typeof e!="string")return!1;let t=e.trim().replace(/[\s\x00-\x1f]/g,"").toLowerCase();if(t.startsWith("javascript:")||t.startsWith("data:")||t.startsWith("vbscript:")||/^[/\\]{2}/.test(t))return!1;let n=t.match(/^([a-z][a-z0-9+.-]*:)/);return n?Q.has(n[1]):!t.includes("\\")}var h=R(typeof location<"u"?location.pathname+location.search+location.hash:"/"),I=R({}),F=R({}),y=R(!1),x=R(null),B=R(null),g={get url(){return h()},get path(){return h().split("?")[0].split("#")[0]},get params(){return I()},get query(){return F()},get hash(){let e=h().split("#")[1];return e?"#"+e:""},get isNavigating(){return y()},get error(){return B()}},T=[],q=[];function D(e,r){return e.push(r),()=>{let t=e.indexOf(r);t!==-1&&e.splice(t,1)}}function ue(e){return D(T,e)}function le(e){return D(q,e)}async function v(e,r={}){let{replace:t=!1,state:n=null,transition:o=!0,_fromPopstate:i=!1,_redirectChain:a=!1}=r;if(a||(w.length=0),!k(e)){typeof console<"u"&&console.warn("[what-router] Blocked navigation to unsafe URL:",e);return}if(typeof window<"u"&&e.startsWith("#")){let d=h().split("#")[0]+e;history.replaceState(n,"",d),h.set(d);let m=document.querySelector(e);m&&m.scrollIntoView({behavior:"smooth"});return}if(e===h()||y.peek())return;y.set(!0),x.set(e);let l=h();if(T.length){let u=!1;try{for(let s of T.slice())if(await s(e,l)===!1){u=!0;break}}catch(s){throw y.set(!1),x.set(null),s}if(u){y.set(!1),x.set(null),i&&typeof history<"u"&&history.pushState(null,"",l);return}}B.set(null);let p=()=>{i||(typeof window<"u"&&C.set(h(),{x:window.scrollX,y:window.scrollY}),t?history.replaceState(n,"",e):history.pushState(n,"",e)),h.set(e),y.set(!1),x.set(null)};if(o&&typeof document<"u"&&document.startViewTransition)try{let u=document.startViewTransition(p);u.ready&&typeof u.ready.catch=="function"&&u.ready.catch(()=>{}),await u.finished}catch{}else p();if(q.length)for(let u of q.slice())u(e,l)}var V=Symbol.for("what.router.redirect"),Y=Symbol.for("what.navigation.signal");function fe(e,r={}){if(!k(e)){let n=typeof e=="string"?e:Object.prototype.toString.call(e),o=new Error(`[what-router] redirect() refused an unsafe target: ${n}`);throw o.code="ERR_UNSAFE_REDIRECT",o.suggestion='redirect() accepts same-origin paths and http:, https:, mailto: or tel: URLs only. Protocol-relative ("//host"), backslash-smuggled and javascript:/data: targets are open-redirect vectors. Check a user-supplied target against an allowlist first.',o.codeExample=`// Bad - a user-controlled target can leave your origin:
|
|
2
2
|
redirect(query.next);
|
|
3
3
|
|
|
4
4
|
// Good - allowlist the target first:
|
|
@@ -12,4 +12,4 @@ redirect(ALLOWED.has(query.next) ? query.next : '/');`,o}let t=new Error(`[what-
|
|
|
12
12
|
function Private() {
|
|
13
13
|
if (!user()) redirect('/login');
|
|
14
14
|
return <Secret />;
|
|
15
|
-
}`,t[V]=!0,t[Y]=()=>{L(e,
|
|
15
|
+
}`,t[V]=!0,t[Y]=()=>{L(e,r)},t.to=e,t.options=r,t}typeof window<"u"&&window.addEventListener("popstate",()=>{C.set(h(),{x:window.scrollX,y:window.scrollY});let e=location.pathname+location.search+location.hash;v(e,{replace:!0,_fromPopstate:!0,transition:!1}).then(()=>{let r=C.get(e);r&&requestAnimationFrame(()=>window.scrollTo(r.x,r.y))})});function J(e,r){let t=[];if(!e.path)return t;let n=e.path.split("/").filter(Boolean),o="";for(let i of n){o+="/"+i;let a=r.find(l=>l.layout&&l.path===o+"/_layout");a&&t.push(a.layout)}return e.layout&&t.push(e.layout),t}var w=[],Z=10;function $(e){return c("div",{class:"what-redirect-loop"},c("h1",null,"Redirect Loop"),c("p",null,e))}function L(e,r){if(x.peek()===e)return null;if(w.push(e),w.length>Z){let o=w.slice(-5).join(" \u2192 ");return w.length=0,console.error(`[what-router] Redirect loop detected: ${o}`),y.set(!1),x.set(null),$("Too many redirects. Check your middleware configuration.")}let t=new Set,n=!1;for(let o of w){if(t.has(o)){n=!0;break}t.add(o)}if(n){let o=w.join(" \u2192 ");return w.length=0,console.error(`[what-router] Redirect cycle detected: ${o}`),y.set(!1),x.set(null),$("Circular redirect detected. Check your middleware configuration.")}return v(e,{replace:!0,...r,_redirectChain:!0}),null}function ee(e,r,t){if(!e)return null;let n=e.split("?")[0].split("#")[0],o=_(n,r);return!o||o.route===t?null:o.route.loading||null}function te({routes:e,fallback:r,globalLayout:t}){let n=()=>{let i=h(),a=i.split("?")[0].split("#")[0],l=i.split("?")[1]?.split("#")[0]||"",p=y(),u=_(a,e);if(u){M(()=>{I.set(u.params),F.set(A(l))});let{route:s,params:d}=u,m=A(l);if(s.middleware&&s.middleware.length>0)for(let N of s.middleware){let P=N({path:a,params:d,query:m,route:s});if(P===!1)return r?c(r,{}):c("div",{class:"what-403"},c("h1",null,"403"),c("p",null,"Access denied"));if(typeof P=="string")return L(P)}let f,S=p?ee(x(),e,s):null;S?f=c(S,{}):s.loading&&p?f=c(s.loading,{}):f=c(s.component,{params:d,query:m,route:s}),s.error&&(f=c(X,{fallback:s.error},f));let O=J(s,e);for(let N of O.reverse())f=c(N,{params:d,query:m},f);return f}return r?c(r,{}):c("div",{class:"what-404"},c("h1",null,"404"),c("p",null,"Page not found"))},o=()=>{let i=null;try{return n()}catch(a){if(!a||!a[V])throw a;i=a}return L(i.to,i.options)};return t?c(t,{},o):o}function re({href:e,class:r,className:t,children:n,replace:o,prefetch:i=!0,activeClass:a="active",exactActiveClass:l="exact-active",transition:p=!0,...u}){let s=k(e)?e:"about:blank";!k(e)&&typeof console<"u"&&console.warn("[what-router] Link blocked unsafe href:",e);let d=s.split("?")[0].split("#")[0];return c("a",{href:s,class:()=>{let f=g.path,S=d==="/"?f==="/":f===d||f.startsWith(d+"/");return[r||t,S&&a,f===d&&l].filter(Boolean).join(" ")||void 0},onclick:f=>{f.ctrlKey||f.metaKey||f.shiftKey||f.altKey||f.button!==0||(f.preventDefault(),v(s,{replace:o,transition:p}))},onmouseenter:i?()=>j(s):void 0,...u},...Array.isArray(n)?n:[n])}function de(e){return re(e)}function pe(e){return Object.entries(e).map(([r,t])=>typeof t=="function"?{path:r,component:t}:{path:r,...t})}function ne(e,r){let t=e.endsWith("/")?e.slice(0,-1):e,n=!r||r==="/"?"/":r.startsWith("/")?r:`/${r}`;return n==="/"?t||"/":t+n}function he(e,r,t={}){let{layout:n,loading:o,error:i}=t;return r.map(a=>({...a,path:ne(e,a.path),layout:a.layout||n,loading:a.loading||o,error:a.error||i}))}function me(e,r,t={}){let{layout:n,middleware:o}=t;return r.map(i=>({...i,_group:e,layout:i.layout||n,middleware:[...i.middleware||[],...o||[]]}))}function ge({to:e}){return v(e,{replace:!0}),null}function ye(e,r){return t=>function(o){let i=e(o);return i instanceof Promise?c("div",{class:"what-guard-loading"},"Loading..."):i?c(t,o):typeof r=="string"?(v(r,{replace:!0}),null):c(r,o)}}function we(e,r={}){let{fallback:t="/login",loading:n=null}=r;return o=>function(a){let l=R("pending"),p=R(null),u=!1;return G(()=>(u=!1,Promise.resolve(e(a)).then(s=>{u||(p.set(s),l.set(s?"allowed":"denied"))}).catch(()=>{u||l.set("denied")}),()=>{u=!0})),()=>{let s=l();return s==="pending"?n?c(n,{}):null:s==="allowed"?c(o,a):typeof t=="string"?(v(t,{replace:!0}),null):c(t,a)}}}var W=new Set;function j(e){if(typeof document>"u"||W.has(e))return;W.add(e);let r=document.createElement("link");r.rel="prefetch",r.href=e,document.head.appendChild(r)}var C=new Map;function xe(){if(typeof window>"u")return;let e=!0;G(()=>{let r=g.url,t=C.get(r),n=g.hash,o=e;e=!1,requestAnimationFrame(()=>{n?oe(n)?.scrollIntoView():t?window.scrollTo(t.x,t.y):o||window.scrollTo(0,0)})})}function oe(e){try{return document.querySelector(e)}catch{return document.getElementById(e.slice(1))}}function Re(e){return{style:{viewTransitionName:e}}}function ve(e){typeof document>"u"||(document.documentElement.dataset.transition=e)}function be(){return{path:b(()=>g.path),params:b(()=>g.params),query:b(()=>g.query),hash:b(()=>g.hash),isNavigating:b(()=>g.isNavigating),navigate:v,prefetch:j}}function Se(){return g.params}function Ee(){return g.query}function _e(){return v}function Ae(e){j(e)}function ke({children:e}){return e||null}function Ce({routes:e,layout:r,fallback:t,error:n}){let o=e.map(i=>({path:i.path,component:i.component,layout:i.layout||void 0,loading:i.loading||void 0,error:i.error||n||void 0,_mode:i.mode||"client"}));return te({routes:o,globalLayout:r,fallback:t||ie})}function ie(){return c("div",{style:"text-align:center;padding:60px 20px"},c("h1",{style:"font-size:48px;margin-bottom:8px"},"404"),c("p",{style:"color:#64748b"},"Page not found"))}export{Ce as FileRouter,re as Link,de as NavLink,ke as Outlet,ge as Redirect,te as Router,le as afterNavigate,we as asyncGuard,ue as beforeNavigate,U as compilePath,pe as defineRoutes,xe as enableScrollRestoration,ye as guard,k as isSafeUrl,_ as matchRoute,v as navigate,he as nestedRoutes,A as parseQuery,j as prefetch,Ae as prefetchRoute,fe as redirect,g as route,me as routeGroup,ve as setViewTransition,_e as useNavigate,Se as useParams,be as useRoute,Ee as useSearch,Re as viewTransitionName};
|
package/dist/match.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function d(c){let e=c.replace(/\([\w-]+\)\//g,"").replace(/\[\.\.\.(\w+)\]/g,(n,l)=>`*:${l}`).replace(/\[(\w+)\]/g,":$1"),r=[],t=null,o=e.split("/").map(n=>n.startsWith("*:")?(t=n.slice(2),r.push(t),"(.+)"):n==="*"?(t="rest",r.push("rest"),"(.+)"):n.startsWith(":")?(r.push(n.slice(1)),"([^/]+)"):n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("/");return{regex:new RegExp(`^${o}$`),paramNames:r,catchAll:t}}var s=new Map,m=1e3;function g(c){let e=s.get(c);return e||(e=d(c),s.size>=m&&s.clear(),s.set(c,e)),e}function x(c,e){let r;try{r=decodeURIComponent(c)}catch{return null}let t=r.split(/[/\\]/);return!e&&t.length>1||t.includes("..")?null:r}function w(c,e){let r=e.filter(t=>t.path);r.sort((t,o)=>{let a=(t.path.match(/:/g)||[]).length+(t.path.includes("*")?100:0),n=(o.path.match(/:/g)||[]).length+(o.path.includes("*")?100:0);return a-n});for(let t of r){let{regex:o,paramNames:a,catchAll:n}=g(t.path),l=c.match(o);if(l){let u={},i=!1;if(a.forEach((p,h)=>{let f=x(l[h+1],p===n);if(f===null){i=!0;return}u[p]=f}),i)continue;return{route:t,params:u}}}return null}function y(c){let e=Object.create(null);if(!c)return e;for(let[r,t]of new URLSearchParams(c))Object.prototype.hasOwnProperty.call(e,r)?Array.isArray(e[r])?e[r].push(t):e[r]=[e[r],t]:e[r]=t;return e}export{d as compilePath,w as matchRoute,y as parseQuery};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-router",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.5",
|
|
4
4
|
"description": "What Framework - File-based & programmatic router with View Transitions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"dependencies": {},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"what-core": "^0.13.
|
|
38
|
+
"what-core": "^0.13.5"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -489,6 +489,7 @@ export function Router({ routes, fallback, globalLayout }) {
|
|
|
489
489
|
// component runs after renderMatch has returned. Anything unbranded
|
|
490
490
|
// propagates to the app's ErrorBoundary unchanged.
|
|
491
491
|
const content = () => {
|
|
492
|
+
/** @type {any} */
|
|
492
493
|
let sig = null;
|
|
493
494
|
try {
|
|
494
495
|
return renderMatch();
|
package/src/match.js
CHANGED
|
@@ -15,6 +15,7 @@ export function compilePath(path) {
|
|
|
15
15
|
.replace(/\[(\w+)\]/g, ':$1'); // File-based [param] to :param
|
|
16
16
|
|
|
17
17
|
const paramNames = [];
|
|
18
|
+
/** @type {string | null} */
|
|
18
19
|
let catchAll = null;
|
|
19
20
|
|
|
20
21
|
const regexStr = normalized
|
|
@@ -112,12 +113,9 @@ export function matchRoute(path, routes) {
|
|
|
112
113
|
export function parseQuery(search) {
|
|
113
114
|
const params = Object.create(null);
|
|
114
115
|
if (!search) return params;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!key) continue;
|
|
119
|
-
const decodedKey = decodeURIComponent(key);
|
|
120
|
-
const decodedVal = val ? decodeURIComponent(val) : '';
|
|
116
|
+
// Match the platform's form-query decoding: '+' is a space, values may
|
|
117
|
+
// contain '=', and malformed escapes remain data rather than aborting SSR.
|
|
118
|
+
for (const [decodedKey, decodedVal] of new URLSearchParams(search)) {
|
|
121
119
|
if (Object.prototype.hasOwnProperty.call(params, decodedKey)) {
|
|
122
120
|
// Collect repeated keys into arrays
|
|
123
121
|
if (Array.isArray(params[decodedKey])) {
|