@symbo.ls/router 3.14.0 → 3.14.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/CHANGELOG.md +10 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/esm/index.js +1 -261
- package/package.json +18 -16
- package/dist/cjs/index.js +0 -281
- package/dist/iife/index.js +0 -754
- package/index.js +0 -322
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var w=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var Q=(e,o)=>{for(var r in o)w(e,r,{get:o[r],enumerable:!0})},D=(e,o,r,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of z(o))!G.call(e,t)&&t!==r&&w(e,t,{get:()=>o[t],enumerable:!(s=$(o,t))||s.enumerable});return e};var H=e=>D(w({},"__esModule",{value:!0}),e);var tt={};Q(tt,{default:()=>Z,getActiveRoute:()=>q,lastLevel:()=>K,lastPathname:()=>C,matchRoute:()=>F,parseQuery:()=>L,parseRoutePattern:()=>B,router:()=>N,runGuards:()=>k});module.exports=H(tt);var i=require("@symbo.ls/utils");const M=/^:(.+)/,A=/^\*$/,U=new Map,B=e=>{const o=U.get(e);if(o)return o;const r=e.replace(/^\//,"").split("/"),s=[];let t=!1;for(let c=0;c<r.length;c++){const n=r[c].match(M);n?s.push({index:c,name:n[1]}):A.test(r[c])&&(t=!0)}const a={segments:r,params:s,hasWildcard:t,pattern:e};return U.set(e,a),a},F=(e,o,r=0)=>{const t=e.replace(/^\//,"").split("/").filter(Boolean).slice(r),a="/"+(t[0]||"");let c=null,n=-1,g={};for(const u in o){if(u==="/*")continue;const x=B(u),v=J(t,x);v>n&&(n=v,c=u,g=V(t,x))}return!c&&o["/*"]&&(c="/*"),{key:c,content:c?o[c]:null,params:g,routePath:a}},J=(e,o)=>{const{segments:r,hasWildcard:s}=o;if(!s&&r.length!==e.length&&r.length!==1&&r.length>e.length)return-1;let t=0;const a=Math.min(r.length,e.length);for(let c=0;c<a;c++)if(r[c]===e[c])t+=3;else if(M.test(r[c]))t+=1;else if(A.test(r[c]))t+=.5;else return-1;return t},V=(e,o)=>{const r={};for(const{index:s,name:t}of o.params)e[s]&&(r[t]=decodeURIComponent(e[s]));return r},L=e=>{if(!e||e==="?")return{};const o={};return new URLSearchParams(e).forEach((s,t)=>{o[t]!==void 0?(Array.isArray(o[t])||(o[t]=[o[t]]),o[t].push(s)):o[t]=s}),o},k=async(e,o)=>{if(!e||!e.length)return!0;for(const r of e){const s=await r(o);if(s===!1)return!1;if(typeof s=="string")return s}return!0},q=(e=0,o)=>{o||(o=typeof i.window<"u"?i.window.location.pathname:"/");const s=o.split("/")[e+1];if(s)return`/${s}`};let C,K=0;const X=e=>!e||e==="srcdoc"||e==="about:srcdoc"?"/":e,Y={level:K,pushState:!0,initialRender:!1,scrollToTop:!0,scrollToNode:!1,scrollNode:i.document&&i.document.documentElement,scrollBody:!1,useFragment:!1,updateState:!0,scrollToOffset:0,contentElementKey:"content",scrollToOptions:{behavior:"smooth"},useParamsMatching:!0},N=async(e,o,r={},s={})=>{const t=o||void 0,a=t?.context?.window||i.window,c=t?.context?.document||i.document,n={...Y,...t.context.routerOptions,...s};K=n.lastLevel;const g=t.__ref;(n.contentElementKey!=="content"&&n.contentElementKey!==g.contentElementKey||!g.contentElementKey)&&(g.contentElementKey=n.contentElementKey||"content");const u=g.contentElementKey||n.contentElementKey||"content",x=a.location.origin!=="null"?a.location.origin:"http://localhost";e=X(e);const v=new a.URL(x+e),{pathname:f,search:R,hash:y}=v,P=L(R),_=t.node,I=y&&y!==a.location.hash.slice(1),W=f!==C;C=f;let T,O,p,b;if(n.useParamsMatching){const l=F(f,t.routes,n.level);T=l.key,O=l.routePath,p=l.content,b=l.params}else T=q(n.level,f),O=T,p=t.routes[T||"/"]||t.routes["/*"],b={};const E=n.scrollToNode?_:n.scrollNode;if(t.state?.root?.debugging){t.state.root.debugging=!1;return}if(!p){n.onNotFound?n.onNotFound({pathname:f,route:T,element:t}):n.silent||console.warn("[smbls/router] no content matched for path",f,"\u2014 available routes:",Object.keys(t.routes||{}));return}if(n.guards&&n.guards.length){const l={pathname:f,route:T,params:b,query:P,hash:y,element:t,state:r},d=await k(n.guards,l);if(d===!1)return;if(typeof d=="string")return N(d,o,r,{...s,guards:[]})}if(n.pushState)try{a.history.pushState(r,null,f+(R||"")+(y||""))}catch{}if(W||!I){const l={route:T,routePath:O,hash:y,debugging:!1};Object.keys(b).length&&(l.params=b),Object.keys(P).length&&(l.query=P),n.updateState&&t.state.update(l,{preventContentUpdate:!0}),u&&n.removeOldElement&&t[u].remove();const d=t.__ref?.origin?.content,S={};if(d)for(const m in d){const h=d[m];m==="__ref"||m==="props"||m==="node"||m==="parent"||m==="key"||(typeof h=="string"||typeof h=="number"||typeof h=="boolean"||typeof h=="object"&&h!==null&&!h.node&&!h.__ref)&&(S[m]=h)}const j={...S,...typeof p=="object"?p:{extends:p}};n.useFragment&&(j.tag="fragment");try{t.set(j,{contentElementKey:u})}catch(m){console.error("[smbls/router] failed to render route content",f,m)}}if(n.scrollToTop&&E?.scrollTo&&E.scrollTo({...n.scrollToOptions||{},top:0,left:0}),n.scrollToNode&&p[u]?.node?.scrollTo&&p[u].node.scrollTo({...n.scrollToOptions||{},top:0,left:0}),y){const l=c.getElementById(y);if(l&&E?.scrollTo){const d=l.getBoundingClientRect().top+_.scrollTop-(n.scrollToOffset||0);E.scrollTo({...n.scrollToOptions||{},top:d,left:0})}}(0,i.triggerEventOn)("routeChanged",t,n)};var Z=N;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,261 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const paramPattern = /^:(.+)/;
|
|
3
|
-
const wildcardPattern = /^\*$/;
|
|
4
|
-
const routeCache = /* @__PURE__ */ new Map();
|
|
5
|
-
const parseRoutePattern = (pattern) => {
|
|
6
|
-
const cached = routeCache.get(pattern);
|
|
7
|
-
if (cached) return cached;
|
|
8
|
-
const segments = pattern.replace(/^\//, "").split("/");
|
|
9
|
-
const params = [];
|
|
10
|
-
let hasWildcard = false;
|
|
11
|
-
for (let i = 0; i < segments.length; i++) {
|
|
12
|
-
const match = segments[i].match(paramPattern);
|
|
13
|
-
if (match) {
|
|
14
|
-
params.push({ index: i, name: match[1] });
|
|
15
|
-
} else if (wildcardPattern.test(segments[i])) {
|
|
16
|
-
hasWildcard = true;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const result = { segments, params, hasWildcard, pattern };
|
|
20
|
-
routeCache.set(pattern, result);
|
|
21
|
-
return result;
|
|
22
|
-
};
|
|
23
|
-
const matchRoute = (pathname, routes, level = 0) => {
|
|
24
|
-
const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
|
|
25
|
-
const relevantSegments = pathSegments.slice(level);
|
|
26
|
-
const routePath = "/" + (relevantSegments[0] || "");
|
|
27
|
-
let bestMatch = null;
|
|
28
|
-
let bestScore = -1;
|
|
29
|
-
let matchedParams = {};
|
|
30
|
-
for (const key in routes) {
|
|
31
|
-
if (key === "/*") continue;
|
|
32
|
-
const parsed = parseRoutePattern(key);
|
|
33
|
-
const score = scoreMatch(relevantSegments, parsed);
|
|
34
|
-
if (score > bestScore) {
|
|
35
|
-
bestScore = score;
|
|
36
|
-
bestMatch = key;
|
|
37
|
-
matchedParams = extractParams(relevantSegments, parsed);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (!bestMatch && routes["/*"]) {
|
|
41
|
-
bestMatch = "/*";
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
key: bestMatch,
|
|
45
|
-
content: bestMatch ? routes[bestMatch] : null,
|
|
46
|
-
params: matchedParams,
|
|
47
|
-
routePath
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
const scoreMatch = (pathSegments, parsed) => {
|
|
51
|
-
const { segments, hasWildcard } = parsed;
|
|
52
|
-
if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
|
|
53
|
-
if (segments.length > pathSegments.length) return -1;
|
|
54
|
-
}
|
|
55
|
-
let score = 0;
|
|
56
|
-
const len = Math.min(segments.length, pathSegments.length);
|
|
57
|
-
for (let i = 0; i < len; i++) {
|
|
58
|
-
if (segments[i] === pathSegments[i]) {
|
|
59
|
-
score += 3;
|
|
60
|
-
} else if (paramPattern.test(segments[i])) {
|
|
61
|
-
score += 1;
|
|
62
|
-
} else if (wildcardPattern.test(segments[i])) {
|
|
63
|
-
score += 0.5;
|
|
64
|
-
} else {
|
|
65
|
-
return -1;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return score;
|
|
69
|
-
};
|
|
70
|
-
const extractParams = (pathSegments, parsed) => {
|
|
71
|
-
const params = {};
|
|
72
|
-
for (const { index, name } of parsed.params) {
|
|
73
|
-
if (pathSegments[index]) {
|
|
74
|
-
params[name] = decodeURIComponent(pathSegments[index]);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return params;
|
|
78
|
-
};
|
|
79
|
-
const parseQuery = (search) => {
|
|
80
|
-
if (!search || search === "?") return {};
|
|
81
|
-
const params = {};
|
|
82
|
-
const searchParams = new URLSearchParams(search);
|
|
83
|
-
searchParams.forEach((value, key) => {
|
|
84
|
-
if (params[key] !== void 0) {
|
|
85
|
-
if (!Array.isArray(params[key])) params[key] = [params[key]];
|
|
86
|
-
params[key].push(value);
|
|
87
|
-
} else {
|
|
88
|
-
params[key] = value;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
return params;
|
|
92
|
-
};
|
|
93
|
-
const runGuards = async (guards, context) => {
|
|
94
|
-
if (!guards || !guards.length) return true;
|
|
95
|
-
for (const guard of guards) {
|
|
96
|
-
const result = await guard(context);
|
|
97
|
-
if (result === false) return false;
|
|
98
|
-
if (typeof result === "string") return result;
|
|
99
|
-
}
|
|
100
|
-
return true;
|
|
101
|
-
};
|
|
102
|
-
const getActiveRoute = (level = 0, route) => {
|
|
103
|
-
if (!route) route = typeof window !== "undefined" ? window.location.pathname : "/";
|
|
104
|
-
const routeArray = route.split("/");
|
|
105
|
-
const activeRoute = routeArray[level + 1];
|
|
106
|
-
if (activeRoute) return `/${activeRoute}`;
|
|
107
|
-
};
|
|
108
|
-
let lastPathname;
|
|
109
|
-
let lastLevel = 0;
|
|
110
|
-
const normalizePath = (p) => !p || p === "srcdoc" || p === "about:srcdoc" ? "/" : p;
|
|
111
|
-
const defaultOptions = {
|
|
112
|
-
level: lastLevel,
|
|
113
|
-
pushState: true,
|
|
114
|
-
initialRender: false,
|
|
115
|
-
scrollToTop: true,
|
|
116
|
-
scrollToNode: false,
|
|
117
|
-
scrollNode: document && document.documentElement,
|
|
118
|
-
scrollBody: false,
|
|
119
|
-
useFragment: false,
|
|
120
|
-
updateState: true,
|
|
121
|
-
scrollToOffset: 0,
|
|
122
|
-
contentElementKey: "content",
|
|
123
|
-
scrollToOptions: { behavior: "smooth" },
|
|
124
|
-
useParamsMatching: true
|
|
125
|
-
};
|
|
126
|
-
const router = async (path, el, state = {}, options = {}) => {
|
|
127
|
-
const element = el || void 0;
|
|
128
|
-
const win = element?.context?.window || window;
|
|
129
|
-
const doc = element?.context?.document || document;
|
|
130
|
-
const opts = {
|
|
131
|
-
...defaultOptions,
|
|
132
|
-
...element.context.routerOptions,
|
|
133
|
-
...options
|
|
134
|
-
};
|
|
135
|
-
lastLevel = opts.lastLevel;
|
|
136
|
-
const ref = element.__ref;
|
|
137
|
-
if (opts.contentElementKey !== "content" && opts.contentElementKey !== ref.contentElementKey || !ref.contentElementKey) {
|
|
138
|
-
ref.contentElementKey = opts.contentElementKey || "content";
|
|
139
|
-
}
|
|
140
|
-
const contentElementKey = ref.contentElementKey || opts.contentElementKey || "content";
|
|
141
|
-
const origin = win.location.origin !== "null" ? win.location.origin : "http://localhost";
|
|
142
|
-
path = normalizePath(path);
|
|
143
|
-
const urlObj = new win.URL(origin + path);
|
|
144
|
-
const { pathname, search, hash } = urlObj;
|
|
145
|
-
const query = parseQuery(search);
|
|
146
|
-
const rootNode = element.node;
|
|
147
|
-
const hashChanged = hash && hash !== win.location.hash.slice(1);
|
|
148
|
-
const pathChanged = pathname !== lastPathname;
|
|
149
|
-
lastPathname = pathname;
|
|
150
|
-
let route, content, params;
|
|
151
|
-
if (opts.useParamsMatching) {
|
|
152
|
-
const match = matchRoute(pathname, element.routes, opts.level);
|
|
153
|
-
route = match.routePath;
|
|
154
|
-
content = match.content;
|
|
155
|
-
params = match.params;
|
|
156
|
-
} else {
|
|
157
|
-
route = getActiveRoute(opts.level, pathname);
|
|
158
|
-
content = element.routes[route || "/"] || element.routes["/*"];
|
|
159
|
-
params = {};
|
|
160
|
-
}
|
|
161
|
-
const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
|
|
162
|
-
if (element.state?.root?.debugging) {
|
|
163
|
-
element.state.root.debugging = false;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (!content) {
|
|
167
|
-
if (opts.onNotFound) {
|
|
168
|
-
opts.onNotFound({ pathname, route, element });
|
|
169
|
-
} else if (!opts.silent) {
|
|
170
|
-
console.warn("[smbls/router] no content matched for path", pathname, "\u2014 available routes:", Object.keys(element.routes || {}));
|
|
171
|
-
}
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (opts.guards && opts.guards.length) {
|
|
175
|
-
const guardContext = { pathname, route, params, query, hash, element, state };
|
|
176
|
-
const guardResult = await runGuards(opts.guards, guardContext);
|
|
177
|
-
if (guardResult === false) return;
|
|
178
|
-
if (typeof guardResult === "string") {
|
|
179
|
-
return router(guardResult, el, state, { ...options, guards: [] });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (opts.pushState) {
|
|
183
|
-
try {
|
|
184
|
-
win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
|
|
185
|
-
} catch (e) {
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (pathChanged || !hashChanged) {
|
|
189
|
-
const stateUpdate = { route, hash, debugging: false };
|
|
190
|
-
if (Object.keys(params).length) stateUpdate.params = params;
|
|
191
|
-
if (Object.keys(query).length) stateUpdate.query = query;
|
|
192
|
-
if (opts.updateState) {
|
|
193
|
-
element.state.update(
|
|
194
|
-
stateUpdate,
|
|
195
|
-
{ preventContentUpdate: true }
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
if (contentElementKey && opts.removeOldElement) {
|
|
199
|
-
element[contentElementKey].remove();
|
|
200
|
-
}
|
|
201
|
-
const originContent = element.__ref?.origin?.content;
|
|
202
|
-
const contentStyles = {};
|
|
203
|
-
if (originContent) {
|
|
204
|
-
for (const k in originContent) {
|
|
205
|
-
const v = originContent[k];
|
|
206
|
-
if (k === "__ref" || k === "props" || k === "node" || k === "parent" || k === "key") continue;
|
|
207
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "object" && v !== null && !v.node && !v.__ref) {
|
|
208
|
-
contentStyles[k] = v;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const nextContent = {
|
|
213
|
-
...contentStyles,
|
|
214
|
-
...typeof content === "object" ? content : { extends: content }
|
|
215
|
-
};
|
|
216
|
-
if (opts.useFragment) nextContent.tag = "fragment";
|
|
217
|
-
try {
|
|
218
|
-
element.set(nextContent, { contentElementKey });
|
|
219
|
-
} catch (err) {
|
|
220
|
-
console.error("[smbls/router] failed to render route content", pathname, err);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (opts.scrollToTop && scrollNode?.scrollTo) {
|
|
224
|
-
scrollNode.scrollTo({
|
|
225
|
-
...opts.scrollToOptions || {},
|
|
226
|
-
top: 0,
|
|
227
|
-
left: 0
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (opts.scrollToNode && content[contentElementKey]?.node?.scrollTo) {
|
|
231
|
-
content[contentElementKey].node.scrollTo({
|
|
232
|
-
...opts.scrollToOptions || {},
|
|
233
|
-
top: 0,
|
|
234
|
-
left: 0
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
if (hash) {
|
|
238
|
-
const activeNode = doc.getElementById(hash);
|
|
239
|
-
if (activeNode && scrollNode?.scrollTo) {
|
|
240
|
-
const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
|
|
241
|
-
scrollNode.scrollTo({
|
|
242
|
-
...opts.scrollToOptions || {},
|
|
243
|
-
top,
|
|
244
|
-
left: 0
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
triggerEventOn("routeChanged", element, opts);
|
|
249
|
-
};
|
|
250
|
-
var index_default = router;
|
|
251
|
-
export {
|
|
252
|
-
index_default as default,
|
|
253
|
-
getActiveRoute,
|
|
254
|
-
lastLevel,
|
|
255
|
-
lastPathname,
|
|
256
|
-
matchRoute,
|
|
257
|
-
parseQuery,
|
|
258
|
-
parseRoutePattern,
|
|
259
|
-
router,
|
|
260
|
-
runGuards
|
|
261
|
-
};
|
|
1
|
+
import{document as O,window as w,triggerEventOn as L}from"@symbo.ls/utils";const _=/^:(.+)/,S=/^\*$/,j=new Map,k=e=>{const n=j.get(e);if(n)return n;const r=e.replace(/^\//,"").split("/"),s=[];let t=!1;for(let c=0;c<r.length;c++){const o=r[c].match(_);o?s.push({index:c,name:o[1]}):S.test(r[c])&&(t=!0)}const a={segments:r,params:s,hasWildcard:t,pattern:e};return j.set(e,a),a},q=(e,n,r=0)=>{const t=e.replace(/^\//,"").split("/").filter(Boolean).slice(r),a="/"+(t[0]||"");let c=null,o=-1,h={};for(const i in n){if(i==="/*")continue;const b=k(i),x=I(t,b);x>o&&(o=x,c=i,h=W(t,b))}return!c&&n["/*"]&&(c="/*"),{key:c,content:c?n[c]:null,params:h,routePath:a}},I=(e,n)=>{const{segments:r,hasWildcard:s}=n;if(!s&&r.length!==e.length&&r.length!==1&&r.length>e.length)return-1;let t=0;const a=Math.min(r.length,e.length);for(let c=0;c<a;c++)if(r[c]===e[c])t+=3;else if(_.test(r[c]))t+=1;else if(S.test(r[c]))t+=.5;else return-1;return t},W=(e,n)=>{const r={};for(const{index:s,name:t}of n.params)e[s]&&(r[t]=decodeURIComponent(e[s]));return r},$=e=>{if(!e||e==="?")return{};const n={};return new URLSearchParams(e).forEach((s,t)=>{n[t]!==void 0?(Array.isArray(n[t])||(n[t]=[n[t]]),n[t].push(s)):n[t]=s}),n},z=async(e,n)=>{if(!e||!e.length)return!0;for(const r of e){const s=await r(n);if(s===!1)return!1;if(typeof s=="string")return s}return!0},G=(e=0,n)=>{n||(n=typeof w<"u"?w.location.pathname:"/");const s=n.split("/")[e+1];if(s)return`/${s}`};let M,A=0;const Q=e=>!e||e==="srcdoc"||e==="about:srcdoc"?"/":e,D={level:A,pushState:!0,initialRender:!1,scrollToTop:!0,scrollToNode:!1,scrollNode:O&&O.documentElement,scrollBody:!1,useFragment:!1,updateState:!0,scrollToOffset:0,contentElementKey:"content",scrollToOptions:{behavior:"smooth"},useParamsMatching:!0},U=async(e,n,r={},s={})=>{const t=n||void 0,a=t?.context?.window||w,c=t?.context?.document||O,o={...D,...t.context.routerOptions,...s};A=o.lastLevel;const h=t.__ref;(o.contentElementKey!=="content"&&o.contentElementKey!==h.contentElementKey||!h.contentElementKey)&&(h.contentElementKey=o.contentElementKey||"content");const i=h.contentElementKey||o.contentElementKey||"content",b=a.location.origin!=="null"?a.location.origin:"http://localhost";e=Q(e);const x=new a.URL(b+e),{pathname:u,search:C,hash:g}=x,E=$(C),K=t.node,B=g&&g!==a.location.hash.slice(1),F=u!==M;M=u;let y,P,m,T;if(o.useParamsMatching){const l=q(u,t.routes,o.level);y=l.key,P=l.routePath,m=l.content,T=l.params}else y=G(o.level,u),P=y,m=t.routes[y||"/"]||t.routes["/*"],T={};const v=o.scrollToNode?K:o.scrollNode;if(t.state?.root?.debugging){t.state.root.debugging=!1;return}if(!m){o.onNotFound?o.onNotFound({pathname:u,route:y,element:t}):o.silent||console.warn("[smbls/router] no content matched for path",u,"\u2014 available routes:",Object.keys(t.routes||{}));return}if(o.guards&&o.guards.length){const l={pathname:u,route:y,params:T,query:E,hash:g,element:t,state:r},f=await z(o.guards,l);if(f===!1)return;if(typeof f=="string")return U(f,n,r,{...s,guards:[]})}if(o.pushState)try{a.history.pushState(r,null,u+(C||"")+(g||""))}catch{}if(F||!B){const l={route:y,routePath:P,hash:g,debugging:!1};Object.keys(T).length&&(l.params=T),Object.keys(E).length&&(l.query=E),o.updateState&&t.state.update(l,{preventContentUpdate:!0}),i&&o.removeOldElement&&t[i].remove();const f=t.__ref?.origin?.content,N={};if(f)for(const d in f){const p=f[d];d==="__ref"||d==="props"||d==="node"||d==="parent"||d==="key"||(typeof p=="string"||typeof p=="number"||typeof p=="boolean"||typeof p=="object"&&p!==null&&!p.node&&!p.__ref)&&(N[d]=p)}const R={...N,...typeof m=="object"?m:{extends:m}};o.useFragment&&(R.tag="fragment");try{t.set(R,{contentElementKey:i})}catch(d){console.error("[smbls/router] failed to render route content",u,d)}}if(o.scrollToTop&&v?.scrollTo&&v.scrollTo({...o.scrollToOptions||{},top:0,left:0}),o.scrollToNode&&m[i]?.node?.scrollTo&&m[i].node.scrollTo({...o.scrollToOptions||{},top:0,left:0}),g){const l=c.getElementById(g);if(l&&v?.scrollTo){const f=l.getBoundingClientRect().top+K.scrollTop-(o.scrollToOffset||0);v.scrollTo({...o.scrollToOptions||{},top:f,left:0})}}L("routeChanged",t,o)};var J=U;export{J as default,G as getActiveRoute,A as lastLevel,M as lastPathname,q as matchRoute,$ as parseQuery,k as parseRoutePattern,U as router,z as runGuards};
|
package/package.json
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/router",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.2",
|
|
4
4
|
"license": "CC-BY-NC-4.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"module": "./index.js",
|
|
7
|
-
"
|
|
8
|
-
"jsdelivr": "./dist/iife/index.js",
|
|
9
|
-
"main": "./index.js",
|
|
6
|
+
"module": "./dist/esm/index.js",
|
|
7
|
+
"main": "./dist/cjs/index.cjs",
|
|
10
8
|
"exports": {
|
|
11
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"development": "./index.js",
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.cjs",
|
|
13
|
+
"default": "./dist/esm/index.js"
|
|
14
|
+
},
|
|
12
15
|
"./package.json": "./package.json"
|
|
13
16
|
},
|
|
14
17
|
"source": "index.js",
|
|
15
18
|
"files": [
|
|
16
19
|
"dist",
|
|
17
|
-
"*.
|
|
20
|
+
"*.md",
|
|
21
|
+
"LICENSE"
|
|
18
22
|
],
|
|
19
23
|
"scripts": {
|
|
20
|
-
"
|
|
21
|
-
"build:
|
|
22
|
-
"build
|
|
23
|
-
"
|
|
24
|
-
"build": "node ../../build/build.js",
|
|
25
|
-
"prepublish": "npm run build && npm run copy:package:cjs"
|
|
24
|
+
"build:esm": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -name \"*.test.js\") --target=es2020 --format=esm --outbase=. --outdir=dist/esm --minify --log-level=warning",
|
|
25
|
+
"build:cjs": "esbuild $(find . -type f -name \"*.js\" -not -path \"*/dist/*\" -not -path \"*/node_modules/*\" -not -path \"*/__tests__/*\" -not -path \"*/test/*\" -not -name \"*.test.js\") --target=node18 --format=cjs --outbase=. --outdir=dist/cjs --out-extension:.js=.cjs --minify --log-level=warning",
|
|
26
|
+
"build": "rimraf dist && npm run build:esm && npm run build:cjs",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
|
-
"@symbo.ls/element": "^3.14.
|
|
29
|
-
"@symbo.ls/utils": "^3.14.
|
|
30
|
+
"@symbo.ls/element": "^3.14.3",
|
|
31
|
+
"@symbo.ls/utils": "^3.14.2"
|
|
30
32
|
},
|
|
31
33
|
"gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681",
|
|
32
34
|
"devDependencies": {
|
|
@@ -35,6 +37,6 @@
|
|
|
35
37
|
"browser": "./index.js",
|
|
36
38
|
"sideEffects": false,
|
|
37
39
|
"publishConfig": {
|
|
38
|
-
"access": "
|
|
40
|
+
"access": "restricted"
|
|
39
41
|
}
|
|
40
42
|
}
|
package/dist/cjs/index.js
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var index_exports = {};
|
|
20
|
-
__export(index_exports, {
|
|
21
|
-
default: () => index_default,
|
|
22
|
-
getActiveRoute: () => getActiveRoute,
|
|
23
|
-
lastLevel: () => lastLevel,
|
|
24
|
-
lastPathname: () => lastPathname,
|
|
25
|
-
matchRoute: () => matchRoute,
|
|
26
|
-
parseQuery: () => parseQuery,
|
|
27
|
-
parseRoutePattern: () => parseRoutePattern,
|
|
28
|
-
router: () => router,
|
|
29
|
-
runGuards: () => runGuards
|
|
30
|
-
});
|
|
31
|
-
module.exports = __toCommonJS(index_exports);
|
|
32
|
-
var import_utils = require("@symbo.ls/utils");
|
|
33
|
-
const paramPattern = /^:(.+)/;
|
|
34
|
-
const wildcardPattern = /^\*$/;
|
|
35
|
-
const routeCache = /* @__PURE__ */ new Map();
|
|
36
|
-
const parseRoutePattern = (pattern) => {
|
|
37
|
-
const cached = routeCache.get(pattern);
|
|
38
|
-
if (cached) return cached;
|
|
39
|
-
const segments = pattern.replace(/^\//, "").split("/");
|
|
40
|
-
const params = [];
|
|
41
|
-
let hasWildcard = false;
|
|
42
|
-
for (let i = 0; i < segments.length; i++) {
|
|
43
|
-
const match = segments[i].match(paramPattern);
|
|
44
|
-
if (match) {
|
|
45
|
-
params.push({ index: i, name: match[1] });
|
|
46
|
-
} else if (wildcardPattern.test(segments[i])) {
|
|
47
|
-
hasWildcard = true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const result = { segments, params, hasWildcard, pattern };
|
|
51
|
-
routeCache.set(pattern, result);
|
|
52
|
-
return result;
|
|
53
|
-
};
|
|
54
|
-
const matchRoute = (pathname, routes, level = 0) => {
|
|
55
|
-
const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
|
|
56
|
-
const relevantSegments = pathSegments.slice(level);
|
|
57
|
-
const routePath = "/" + (relevantSegments[0] || "");
|
|
58
|
-
let bestMatch = null;
|
|
59
|
-
let bestScore = -1;
|
|
60
|
-
let matchedParams = {};
|
|
61
|
-
for (const key in routes) {
|
|
62
|
-
if (key === "/*") continue;
|
|
63
|
-
const parsed = parseRoutePattern(key);
|
|
64
|
-
const score = scoreMatch(relevantSegments, parsed);
|
|
65
|
-
if (score > bestScore) {
|
|
66
|
-
bestScore = score;
|
|
67
|
-
bestMatch = key;
|
|
68
|
-
matchedParams = extractParams(relevantSegments, parsed);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (!bestMatch && routes["/*"]) {
|
|
72
|
-
bestMatch = "/*";
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
key: bestMatch,
|
|
76
|
-
content: bestMatch ? routes[bestMatch] : null,
|
|
77
|
-
params: matchedParams,
|
|
78
|
-
routePath
|
|
79
|
-
};
|
|
80
|
-
};
|
|
81
|
-
const scoreMatch = (pathSegments, parsed) => {
|
|
82
|
-
const { segments, hasWildcard } = parsed;
|
|
83
|
-
if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
|
|
84
|
-
if (segments.length > pathSegments.length) return -1;
|
|
85
|
-
}
|
|
86
|
-
let score = 0;
|
|
87
|
-
const len = Math.min(segments.length, pathSegments.length);
|
|
88
|
-
for (let i = 0; i < len; i++) {
|
|
89
|
-
if (segments[i] === pathSegments[i]) {
|
|
90
|
-
score += 3;
|
|
91
|
-
} else if (paramPattern.test(segments[i])) {
|
|
92
|
-
score += 1;
|
|
93
|
-
} else if (wildcardPattern.test(segments[i])) {
|
|
94
|
-
score += 0.5;
|
|
95
|
-
} else {
|
|
96
|
-
return -1;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return score;
|
|
100
|
-
};
|
|
101
|
-
const extractParams = (pathSegments, parsed) => {
|
|
102
|
-
const params = {};
|
|
103
|
-
for (const { index, name } of parsed.params) {
|
|
104
|
-
if (pathSegments[index]) {
|
|
105
|
-
params[name] = decodeURIComponent(pathSegments[index]);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return params;
|
|
109
|
-
};
|
|
110
|
-
const parseQuery = (search) => {
|
|
111
|
-
if (!search || search === "?") return {};
|
|
112
|
-
const params = {};
|
|
113
|
-
const searchParams = new URLSearchParams(search);
|
|
114
|
-
searchParams.forEach((value, key) => {
|
|
115
|
-
if (params[key] !== void 0) {
|
|
116
|
-
if (!Array.isArray(params[key])) params[key] = [params[key]];
|
|
117
|
-
params[key].push(value);
|
|
118
|
-
} else {
|
|
119
|
-
params[key] = value;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
return params;
|
|
123
|
-
};
|
|
124
|
-
const runGuards = async (guards, context) => {
|
|
125
|
-
if (!guards || !guards.length) return true;
|
|
126
|
-
for (const guard of guards) {
|
|
127
|
-
const result = await guard(context);
|
|
128
|
-
if (result === false) return false;
|
|
129
|
-
if (typeof result === "string") return result;
|
|
130
|
-
}
|
|
131
|
-
return true;
|
|
132
|
-
};
|
|
133
|
-
const getActiveRoute = (level = 0, route) => {
|
|
134
|
-
if (!route) route = typeof import_utils.window !== "undefined" ? import_utils.window.location.pathname : "/";
|
|
135
|
-
const routeArray = route.split("/");
|
|
136
|
-
const activeRoute = routeArray[level + 1];
|
|
137
|
-
if (activeRoute) return `/${activeRoute}`;
|
|
138
|
-
};
|
|
139
|
-
let lastPathname;
|
|
140
|
-
let lastLevel = 0;
|
|
141
|
-
const normalizePath = (p) => !p || p === "srcdoc" || p === "about:srcdoc" ? "/" : p;
|
|
142
|
-
const defaultOptions = {
|
|
143
|
-
level: lastLevel,
|
|
144
|
-
pushState: true,
|
|
145
|
-
initialRender: false,
|
|
146
|
-
scrollToTop: true,
|
|
147
|
-
scrollToNode: false,
|
|
148
|
-
scrollNode: import_utils.document && import_utils.document.documentElement,
|
|
149
|
-
scrollBody: false,
|
|
150
|
-
useFragment: false,
|
|
151
|
-
updateState: true,
|
|
152
|
-
scrollToOffset: 0,
|
|
153
|
-
contentElementKey: "content",
|
|
154
|
-
scrollToOptions: { behavior: "smooth" },
|
|
155
|
-
useParamsMatching: true
|
|
156
|
-
};
|
|
157
|
-
const router = async (path, el, state = {}, options = {}) => {
|
|
158
|
-
const element = el || void 0;
|
|
159
|
-
const win = element?.context?.window || import_utils.window;
|
|
160
|
-
const doc = element?.context?.document || import_utils.document;
|
|
161
|
-
const opts = {
|
|
162
|
-
...defaultOptions,
|
|
163
|
-
...element.context.routerOptions,
|
|
164
|
-
...options
|
|
165
|
-
};
|
|
166
|
-
lastLevel = opts.lastLevel;
|
|
167
|
-
const ref = element.__ref;
|
|
168
|
-
if (opts.contentElementKey !== "content" && opts.contentElementKey !== ref.contentElementKey || !ref.contentElementKey) {
|
|
169
|
-
ref.contentElementKey = opts.contentElementKey || "content";
|
|
170
|
-
}
|
|
171
|
-
const contentElementKey = ref.contentElementKey || opts.contentElementKey || "content";
|
|
172
|
-
const origin = win.location.origin !== "null" ? win.location.origin : "http://localhost";
|
|
173
|
-
path = normalizePath(path);
|
|
174
|
-
const urlObj = new win.URL(origin + path);
|
|
175
|
-
const { pathname, search, hash } = urlObj;
|
|
176
|
-
const query = parseQuery(search);
|
|
177
|
-
const rootNode = element.node;
|
|
178
|
-
const hashChanged = hash && hash !== win.location.hash.slice(1);
|
|
179
|
-
const pathChanged = pathname !== lastPathname;
|
|
180
|
-
lastPathname = pathname;
|
|
181
|
-
let route, content, params;
|
|
182
|
-
if (opts.useParamsMatching) {
|
|
183
|
-
const match = matchRoute(pathname, element.routes, opts.level);
|
|
184
|
-
route = match.routePath;
|
|
185
|
-
content = match.content;
|
|
186
|
-
params = match.params;
|
|
187
|
-
} else {
|
|
188
|
-
route = getActiveRoute(opts.level, pathname);
|
|
189
|
-
content = element.routes[route || "/"] || element.routes["/*"];
|
|
190
|
-
params = {};
|
|
191
|
-
}
|
|
192
|
-
const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
|
|
193
|
-
if (element.state?.root?.debugging) {
|
|
194
|
-
element.state.root.debugging = false;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (!content) {
|
|
198
|
-
if (opts.onNotFound) {
|
|
199
|
-
opts.onNotFound({ pathname, route, element });
|
|
200
|
-
} else if (!opts.silent) {
|
|
201
|
-
console.warn("[smbls/router] no content matched for path", pathname, "\u2014 available routes:", Object.keys(element.routes || {}));
|
|
202
|
-
}
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (opts.guards && opts.guards.length) {
|
|
206
|
-
const guardContext = { pathname, route, params, query, hash, element, state };
|
|
207
|
-
const guardResult = await runGuards(opts.guards, guardContext);
|
|
208
|
-
if (guardResult === false) return;
|
|
209
|
-
if (typeof guardResult === "string") {
|
|
210
|
-
return router(guardResult, el, state, { ...options, guards: [] });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (opts.pushState) {
|
|
214
|
-
try {
|
|
215
|
-
win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
|
|
216
|
-
} catch (e) {
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (pathChanged || !hashChanged) {
|
|
220
|
-
const stateUpdate = { route, hash, debugging: false };
|
|
221
|
-
if (Object.keys(params).length) stateUpdate.params = params;
|
|
222
|
-
if (Object.keys(query).length) stateUpdate.query = query;
|
|
223
|
-
if (opts.updateState) {
|
|
224
|
-
element.state.update(
|
|
225
|
-
stateUpdate,
|
|
226
|
-
{ preventContentUpdate: true }
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
if (contentElementKey && opts.removeOldElement) {
|
|
230
|
-
element[contentElementKey].remove();
|
|
231
|
-
}
|
|
232
|
-
const originContent = element.__ref?.origin?.content;
|
|
233
|
-
const contentStyles = {};
|
|
234
|
-
if (originContent) {
|
|
235
|
-
for (const k in originContent) {
|
|
236
|
-
const v = originContent[k];
|
|
237
|
-
if (k === "__ref" || k === "props" || k === "node" || k === "parent" || k === "key") continue;
|
|
238
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "object" && v !== null && !v.node && !v.__ref) {
|
|
239
|
-
contentStyles[k] = v;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const nextContent = {
|
|
244
|
-
...contentStyles,
|
|
245
|
-
...typeof content === "object" ? content : { extends: content }
|
|
246
|
-
};
|
|
247
|
-
if (opts.useFragment) nextContent.tag = "fragment";
|
|
248
|
-
try {
|
|
249
|
-
element.set(nextContent, { contentElementKey });
|
|
250
|
-
} catch (err) {
|
|
251
|
-
console.error("[smbls/router] failed to render route content", pathname, err);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (opts.scrollToTop && scrollNode?.scrollTo) {
|
|
255
|
-
scrollNode.scrollTo({
|
|
256
|
-
...opts.scrollToOptions || {},
|
|
257
|
-
top: 0,
|
|
258
|
-
left: 0
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
if (opts.scrollToNode && content[contentElementKey]?.node?.scrollTo) {
|
|
262
|
-
content[contentElementKey].node.scrollTo({
|
|
263
|
-
...opts.scrollToOptions || {},
|
|
264
|
-
top: 0,
|
|
265
|
-
left: 0
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
if (hash) {
|
|
269
|
-
const activeNode = doc.getElementById(hash);
|
|
270
|
-
if (activeNode && scrollNode?.scrollTo) {
|
|
271
|
-
const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
|
|
272
|
-
scrollNode.scrollTo({
|
|
273
|
-
...opts.scrollToOptions || {},
|
|
274
|
-
top,
|
|
275
|
-
left: 0
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
(0, import_utils.triggerEventOn)("routeChanged", element, opts);
|
|
280
|
-
};
|
|
281
|
-
var index_default = router;
|