crossroad 3.0.2 → 3.0.4
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/index.d.ts +86 -93
- package/index.min.js +1 -1
- package/package.json +15 -17
- package/readme.md +1 -1
package/index.d.ts
CHANGED
|
@@ -1,102 +1,95 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { ReactNode, ReactElement } from 'react';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
: Type extends "string"
|
|
8
|
-
? { [K in Name]: string }
|
|
9
|
-
: Type extends "date"
|
|
10
|
-
? { [K in Name]: Date }
|
|
11
|
-
: Type extends "boolean"
|
|
12
|
-
? { [K in Name]: boolean }
|
|
13
|
-
: { [K in Name]: string } // default to string
|
|
14
|
-
: T extends `${infer Name}`
|
|
15
|
-
? { [K in Name]: string }
|
|
16
|
-
: never;
|
|
17
|
-
|
|
18
|
-
// Extracts all params from path and combines them
|
|
19
|
-
type ExtractParams<T extends string> =
|
|
20
|
-
T extends `${infer Prefix}/:${infer Param}/${infer Rest}`
|
|
21
|
-
? ParseParamType<Param> & ExtractParams<`/${Rest}`>
|
|
22
|
-
: T extends `${infer Prefix}/:${infer Param}`
|
|
23
|
-
? ParseParamType<Param>
|
|
24
|
-
: {};
|
|
25
|
-
|
|
26
|
-
// Main Route component with type inference
|
|
27
|
-
interface RouteProps<
|
|
28
|
-
T extends Record<string, string | number | boolean | Date> = {},
|
|
29
|
-
> extends Omit<
|
|
30
|
-
{
|
|
31
|
-
path?: string;
|
|
32
|
-
scrollUp?: boolean;
|
|
33
|
-
component?: React.FunctionComponent<T>;
|
|
34
|
-
render?: (params: T) => React.ReactNode;
|
|
35
|
-
children?: React.ReactNode;
|
|
36
|
-
},
|
|
37
|
-
"component" | "render"
|
|
38
|
-
> {
|
|
39
|
-
path?: string;
|
|
4
|
+
interface RouterProps {
|
|
5
|
+
scrollUp?: boolean;
|
|
6
|
+
url?: string;
|
|
7
|
+
children?: React__default.ReactNode;
|
|
40
8
|
}
|
|
9
|
+
declare const _default$6: ({ scrollUp, url: baseUrl, children }: RouterProps) => React__default.JSX.Element;
|
|
41
10
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
11
|
+
type ParseParamType<T extends string> = T extends `${infer Name}<${infer Type}>` ? Type extends "number" ? {
|
|
12
|
+
[K in Name]: number;
|
|
13
|
+
} : Type extends "string" ? {
|
|
14
|
+
[K in Name]: string;
|
|
15
|
+
} : Type extends "date" ? {
|
|
16
|
+
[K in Name]: Date;
|
|
17
|
+
} : Type extends "boolean" ? {
|
|
18
|
+
[K in Name]: boolean;
|
|
19
|
+
} : {
|
|
20
|
+
[K in Name]: string;
|
|
21
|
+
} : {
|
|
22
|
+
[K in T]: string;
|
|
23
|
+
};
|
|
24
|
+
type ExtractParams<T extends string> = T extends `${infer _Prefix}/:${infer Param}/${infer Rest}` ? ParseParamType<Param> & ExtractParams<`/${Rest}`> : T extends `${infer _Prefix}/:${infer Param}` ? ParseParamType<Param> : {};
|
|
25
|
+
type InferParamsFromPath<T extends string> = T extends `${string}/:${string}` ? ExtractParams<T> : {};
|
|
26
|
+
interface RoutePropsBase {
|
|
27
|
+
path?: string;
|
|
28
|
+
scrollUp?: boolean;
|
|
29
|
+
children?: React__default.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
type RouteProps<T extends Record<string, string | number | boolean | Date> = {}> = RoutePropsBase & ({
|
|
32
|
+
component: React__default.FunctionComponent<T>;
|
|
33
|
+
render?: never;
|
|
34
|
+
} | {
|
|
35
|
+
render: (params: T) => React__default.ReactNode;
|
|
36
|
+
component?: never;
|
|
37
|
+
} | {
|
|
38
|
+
component?: never;
|
|
39
|
+
render?: never;
|
|
40
|
+
});
|
|
41
|
+
type RouteType = <P extends string = string>(props: RouteProps<InferParamsFromPath<P>> & {
|
|
42
|
+
path?: P;
|
|
43
|
+
}) => React__default.ReactNode;
|
|
44
|
+
declare const _default$5: RouteType;
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
type
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
type Query = Record<string, string>;
|
|
69
|
-
type Url = URL & {
|
|
70
|
-
path: string;
|
|
71
|
-
query: Query;
|
|
72
|
-
hash?: string;
|
|
46
|
+
type Query = Record<string, string | string[]>;
|
|
47
|
+
type Url = {
|
|
48
|
+
path: string;
|
|
49
|
+
query: Query;
|
|
50
|
+
hash?: string;
|
|
51
|
+
params?: Params;
|
|
73
52
|
};
|
|
74
53
|
type UrlSet = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
path?: string;
|
|
55
|
+
query?: Query;
|
|
56
|
+
hash?: string;
|
|
78
57
|
};
|
|
79
58
|
type Params = Record<string, string | number | boolean | Date>;
|
|
80
|
-
type
|
|
81
|
-
|
|
82
|
-
declare function useUrl(): [Url, Callable<UrlSet | string>];
|
|
83
|
-
declare function usePath(): [string, Callable<string>];
|
|
84
|
-
declare function useQuery(): [Query, Callable<Query>];
|
|
85
|
-
declare function useQuery(key: string): [string, Callable<string>];
|
|
86
|
-
declare function useHash(): [string, Callable<string>];
|
|
87
|
-
declare function useParams<T = Params>(): T;
|
|
88
|
-
declare function useParams<T = string | number | boolean | Date>(
|
|
89
|
-
key: string,
|
|
90
|
-
): T;
|
|
91
|
-
|
|
92
|
-
export default Router;
|
|
93
|
-
export {
|
|
94
|
-
Route,
|
|
95
|
-
Switch,
|
|
96
|
-
useUrl,
|
|
97
|
-
usePath,
|
|
98
|
-
useQuery,
|
|
99
|
-
useHash,
|
|
100
|
-
useParams,
|
|
101
|
-
Context,
|
|
59
|
+
type SetUrlOptions = {
|
|
60
|
+
mode?: "push" | "replace";
|
|
102
61
|
};
|
|
62
|
+
type NewUrlValue = Url | UrlSet | string;
|
|
63
|
+
type SetUrl = (newUrl: NewUrlValue | ((prev: Url) => NewUrlValue), opts?: SetUrlOptions) => void;
|
|
64
|
+
|
|
65
|
+
interface SwitchProps {
|
|
66
|
+
redirect?: string | {
|
|
67
|
+
path: string;
|
|
68
|
+
} | ((url: Url) => string);
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
declare const _default$4: ({ redirect, children }: SwitchProps) => ReactElement | null;
|
|
72
|
+
|
|
73
|
+
declare const _default$3: React.Context<[Url, SetUrl] | undefined>;
|
|
74
|
+
|
|
75
|
+
declare const _default$2: () => [Url, SetUrl];
|
|
76
|
+
|
|
77
|
+
type PathUpdater = (path: string) => string;
|
|
78
|
+
type SetPath = (path: string | PathUpdater, opts?: SetUrlOptions) => void;
|
|
79
|
+
declare const _default$1: () => [string, SetPath];
|
|
80
|
+
|
|
81
|
+
type QueryValue = string | string[] | undefined;
|
|
82
|
+
type SetQueryProp = (value: string | ((prev: QueryValue) => string), opts?: SetUrlOptions) => void;
|
|
83
|
+
type QueryUpdater = (query: Query) => Query | string;
|
|
84
|
+
type SetQuery = (newQuery: Query | string | QueryUpdater, opts?: SetUrlOptions) => void;
|
|
85
|
+
declare function useQuery(key: string): [QueryValue, SetQueryProp];
|
|
86
|
+
declare function useQuery(): [Query, SetQuery];
|
|
87
|
+
|
|
88
|
+
type HashUpdater = (hash: string | undefined) => string;
|
|
89
|
+
type SetHash = (hash: string | HashUpdater, opts?: SetUrlOptions) => void;
|
|
90
|
+
declare const _default: () => [string | undefined, SetHash];
|
|
91
|
+
|
|
92
|
+
declare function useParams(): Params;
|
|
93
|
+
declare function useParams<T = string | number | boolean | Date>(key: string): T;
|
|
94
|
+
|
|
95
|
+
export { _default$3 as Context, _default$5 as Route, _default$4 as Switch, _default$6 as default, _default as useHash, useParams, _default$1 as usePath, useQuery, _default$2 as useUrl };
|
package/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t,{createContext as e,useState as r,useRef as n,useCallback as o,useEffect as i,useContext as s}from"react";var a=e(void 0);const u=t=>t.length>1?t:t[0],c=t=>{if("string"!=typeof t)return t;const e={query:{}},r=new URL(t,"http://localhost:3000/");e.path=(r.pathname.replace(/\/$/,"")||"/").replaceAll("%3C","<").replaceAll("%3E",">"),e.query={};for(const[t]of r.searchParams)e.query[t]=u(r.searchParams.getAll(t));return r.hash&&(e.hash=r.hash.replace(/^#/,"")),e},l=t=>{if("string"==typeof t)return t;const{path:e,query:r={},hash:n}=t||{};let o=e||"/";const i=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return i&&(o+="?"+i),n&&(o+="#"+n),o};var p=()=>"undefined"==typeof window,h=({scrollUp:e,url:s,children:u})=>{const h=s||(p()?"/":window.location.href),[f,y]=r(()=>c(h)),d=n(f),m=o((t,{mode:r="push"}={})=>{if(!history[r+"State"])throw new Error(`Invalid mode "${r}"`);y(e=>{const r="function"==typeof t?t(e):t;return l(e)===l(r)?e:c(r)});const n=d.current,o="function"==typeof t?t(n):t;if(l(n)!==l(o)){const t=c(o);d.current=t,history[r+"State"]({},null,l(t)),e&&window.scrollTo(0,0)}},[]);return i(()=>{d.current=f},[f]),i(()=>{if(p())return;const t=()=>y(c(window.location.href)),e=t=>{const e=t.target,r=(t=>{if(!t)return null;const e=t.getAttribute("href");if(!e)return null;const r=e.trim();return/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(r)||null!==t.getAttribute("target")?null:r})(e?.closest("a")??null);if(!r)return!1;t.preventDefault();const[n,o]=r.split("#");n?m(r):o&&(window.location.hash="#"+o)};return window.addEventListener("popstate",t),document.addEventListener("click",e),()=>{window.removeEventListener("popstate",t),document.removeEventListener("click",e)}},[m]),t.createElement(a.Provider,{value:[f,m]},u)};function f(t,e,r={}){let n=JSON.parse(JSON.stringify(c(t))),o=JSON.parse(JSON.stringify(c(e)));if(o.path=o.path.replace(/\/$/,"")||"/",n.path=n.path.replace(/\/$/,"")||"/",n.path.endsWith("*")){n.path=n.path.replace(/\/?\*/,"")||"/";const t=n.path.split("/").filter(Boolean).length;o.path="/"+o.path.slice(1).split("/").slice(0,t).join("/")}if(Object.entries(n.query).length)for(const t in n.query){if(!(t in o.query))return!1;if(n.query[t]&&n.query[t]!==o.query[t])return!1}if(!n.path.includes(":"))return n.path===o.path&&r;if(n.path.split("/").length!==o.path.split("/").length)return!1;const i={},s=n.path.split("/").every((t,e)=>{const n=o.path.split("/")[e];if(t.startsWith(":")){let e=t.slice(1),o="string";e.includes("<")&&([e,o]=e.split("<"),o=o.slice(0,-1));const s=decodeURIComponent(n);return i[e]="number"===o?Number(s):"date"===o?new Date(/^\d+$/.test(s)?Number(s):s):"boolean"===o?"true"===s:s,r}return n===t});return s&&Object.assign(r,i),s&&r}var y=()=>{const t=s(a);if(!t)throw new Error("Wrap your App with <Router>");return t}
|
|
1
|
+
import t,{createContext as e,useState as r,useRef as n,useCallback as o,useEffect as i,useContext as s}from"react";var a=e(void 0);const u=t=>t.length>1?t:t[0],c=t=>{if("string"!=typeof t)return t;const e={query:{}},r=new URL(t,"http://localhost:3000/");e.path=(r.pathname.replace(/\/$/,"")||"/").replaceAll("%3C","<").replaceAll("%3E",">"),e.query={};for(const[t]of r.searchParams)e.query[t]=u(r.searchParams.getAll(t));return r.hash&&(e.hash=r.hash.replace(/^#/,"")),e},l=t=>{if("string"==typeof t)return t;const{path:e,query:r={},hash:n}=t||{};let o=e||"/";const i=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return i&&(o+="?"+i),n&&(o+="#"+n),o};var p=()=>"undefined"==typeof window,h=({scrollUp:e,url:s,children:u})=>{const h=s||(p()?"/":window.location.href),[f,y]=r(()=>c(h)),d=n(f),m=o((t,{mode:r="push"}={})=>{if(!history[r+"State"])throw new Error(`Invalid mode "${r}"`);y(e=>{const r="function"==typeof t?t(e):t;return l(e)===l(r)?e:c(r)});const n=d.current,o="function"==typeof t?t(n):t;if(l(n)!==l(o)){const t=c(o);d.current=t,history[r+"State"]({},null,l(t)),e&&window.scrollTo(0,0)}},[]);return i(()=>{d.current=f},[f]),i(()=>{if(p())return;const t=()=>y(c(window.location.href)),e=t=>{const e=t.target,r=(t=>{if(!t)return null;const e=t.getAttribute("href");if(!e)return null;const r=e.trim();return/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(r)||null!==t.getAttribute("target")?null:r})(e?.closest("a")??null);if(!r)return!1;t.preventDefault();const[n,o]=r.split("#");n?m(r):o&&(window.location.hash="#"+o)};return window.addEventListener("popstate",t),document.addEventListener("click",e),()=>{window.removeEventListener("popstate",t),document.removeEventListener("click",e)}},[m]),t.createElement(a.Provider,{value:[f,m]},u)};function f(t,e,r={}){let n=JSON.parse(JSON.stringify(c(t))),o=JSON.parse(JSON.stringify(c(e)));if(o.path=o.path.replace(/\/$/,"")||"/",n.path=n.path.replace(/\/$/,"")||"/",n.path.endsWith("*")){n.path=n.path.replace(/\/?\*/,"")||"/";const t=n.path.split("/").filter(Boolean).length;o.path="/"+o.path.slice(1).split("/").slice(0,t).join("/")}if(Object.entries(n.query).length)for(const t in n.query){if(!(t in o.query))return!1;if(n.query[t]&&n.query[t]!==o.query[t])return!1}if(!n.path.includes(":"))return n.path===o.path&&r;if(n.path.split("/").length!==o.path.split("/").length)return!1;const i={},s=n.path.split("/").every((t,e)=>{const n=o.path.split("/")[e];if(t.startsWith(":")){let e=t.slice(1),o="string";e.includes("<")&&([e,o]=e.split("<"),o=o.slice(0,-1));const s=decodeURIComponent(n);return i[e]="number"===o?Number(s):"date"===o?new Date(/^\d+$/.test(s)?Number(s):s):"boolean"===o?"true"===s:s,r}return n===t});return s&&Object.assign(r,i),s&&r}var y=()=>{const t=s(a);if(!t)throw new Error("Wrap your App with <Router>");return t};const d=({path:e="*",scrollUp:r,component:n,render:o,children:i})=>{const s=y(),u=f(e,s[0]);if(!u)return null;if(r&&window.scrollTo(0,0),n){const e=n;i=t.createElement(e,u)}else if(o)i=o(u);else if(!i)throw new Error("Route needs prop `component`, `render` or `children`");return t.createElement(a.Provider,{value:[{...s[0],params:u},s[1]]},i)};var m=({redirect:t,children:e})=>{const[r,n]=y(),o=(t=>(Array.isArray(t)?t:[t]).filter(t=>null!=t&&"object"==typeof t&&"props"in t))(e).find(t=>f(t.props.path||"*",r))||null;return i(()=>{if(!t)return;if(o)return;const e="function"==typeof t?t(r):t;n(l(e))},[t,o]),o},w=()=>{const[t,e]=y(),r=o((t,r)=>{e(e=>{const r="function"==typeof t?t(e.path):t;return{...e,path:"string"==typeof r?r:"/"}},r)},[]);return[t.path,r]};const q=t=>l({query:t});function g(t){return t?(t=>{const[e,r]=y(),n=o((e,n)=>{r(r=>{const n=r.query[t],o="function"==typeof e?e(n):e;if(o===n)return r;if(o)return{...r,query:{...r.query,[t]:o}};{const{[t]:e,...n}=r.query;return{...r,query:n}}},n)},[]);return[e.query[t],n]})(t):(()=>{const[t,e]=y(),r=o((t,r)=>{e(e=>{let r="function"==typeof t?t(e.query):t;"string"==typeof r&&(r=c("/?"+r.replace(/^\?/,"")).query);const n=c(q(r)).query;return q(n)===q(e.query)?e:{...e,query:n}},r)},[]);return[t.query,r]})()}var v=()=>{const[t,e]=y(),r=o((t,r)=>{e(e=>{const r="function"==typeof t?t(e.hash):t,n="string"!=typeof r?"":r.replace(/^#/,"");return{...e,hash:n}},r)},[]);return[t.hash,r]};function A(t){const[{params:e}]=s(a);return t?t in e?e[t]:"":e||{}}export{a as Context,d as Route,m as Switch,h as default,v as useHash,A as useParams,w as usePath,g as useQuery,y as useUrl};
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossroad",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "A React library to handle navigation easily in your WebApp",
|
|
5
5
|
"homepage": "https://crossroad.page/",
|
|
6
|
-
"repository": "
|
|
7
|
-
"bugs": "https://github.com/franciscop/crossroad/issues",
|
|
6
|
+
"repository": "github:franciscop/crossroad",
|
|
8
7
|
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
9
8
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"react",
|
|
12
|
+
"router",
|
|
13
|
+
"routing",
|
|
14
|
+
"javascript",
|
|
15
|
+
"hooks"
|
|
16
|
+
],
|
|
10
17
|
"documentation": {
|
|
11
18
|
"home": "assets/home.html",
|
|
12
19
|
"menu": {
|
|
@@ -16,21 +23,13 @@
|
|
|
16
23
|
"Github": "https://github.com/franciscop/crossroad"
|
|
17
24
|
}
|
|
18
25
|
},
|
|
19
|
-
"license": "MIT",
|
|
20
26
|
"scripts": {
|
|
21
|
-
"build": "rollup -c",
|
|
22
|
-
"
|
|
27
|
+
"build": "tsc -p tsconfig.build.json && rollup -c",
|
|
28
|
+
"lint": "tsc",
|
|
23
29
|
"size": "echo $(gzip -c index.min.js | wc -c) bytes",
|
|
24
30
|
"start": "jest --watch",
|
|
25
31
|
"test": "jest --coverage"
|
|
26
32
|
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"react",
|
|
29
|
-
"router",
|
|
30
|
-
"routing",
|
|
31
|
-
"javascript",
|
|
32
|
-
"hooks"
|
|
33
|
-
],
|
|
34
33
|
"main": "index.min.js",
|
|
35
34
|
"type": "module",
|
|
36
35
|
"types": "index.d.ts",
|
|
@@ -42,19 +41,18 @@
|
|
|
42
41
|
"@babel/preset-env": "^7.29.2",
|
|
43
42
|
"@babel/preset-react": "^7.28.5",
|
|
44
43
|
"@babel/preset-typescript": "^7.28.5",
|
|
44
|
+
"@rollup/plugin-babel": "^6.0.0",
|
|
45
45
|
"@rollup/plugin-terser": "^1.0.0",
|
|
46
46
|
"@types/jest": "^30.0.0",
|
|
47
47
|
"@types/node": "^25.5.2",
|
|
48
48
|
"@types/react": "^18.3.4",
|
|
49
|
-
"babel-loader": "^10.1.1",
|
|
50
|
-
"babel-polyfill": "^6.26.0",
|
|
51
49
|
"jest": "^29.7.0",
|
|
52
50
|
"jest-environment-jsdom": "^29.7.0",
|
|
53
51
|
"react": "^18.3.1",
|
|
54
52
|
"react-dom": "^18.3.1",
|
|
55
|
-
"react-test": "^0.
|
|
53
|
+
"react-test": "^0.23.2",
|
|
56
54
|
"rollup": "^4.60.1",
|
|
57
|
-
"rollup-plugin-
|
|
55
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
58
56
|
"typescript": "^6.0.2"
|
|
59
57
|
},
|
|
60
58
|
"peerDependencies": {
|
package/readme.md
CHANGED
|
@@ -6,7 +6,7 @@ A React library to handle navigation in your WebApp. Built with simple component
|
|
|
6
6
|
- Very useful hooks like [`useUrl`](#useurl), [`useQuery`](#usequery), etc. Follow [the rules of hooks](https://reactjs.org/docs/hooks-rules.html).
|
|
7
7
|
- Links are plain `<a>` instead of custom components. [Read more](#a).
|
|
8
8
|
- The `<Route>` path is `exact` by default and can match query parameters.
|
|
9
|
-
- It's [just ~1.
|
|
9
|
+
- It's [just ~1.8kb](https://bundlephobia.com/package/crossroad) (min+gzip) instead of the 17kb of React Router(+Dom).
|
|
10
10
|
- Add `scrollUp` to `<Router>` o `<Route>` to automatically scroll up on a route change.
|
|
11
11
|
|
|
12
12
|
[**🔗 Demo on CodeSandbox**](https://codesandbox.io/s/recursing-wozniak-uftyo?file=/src/App.js)
|