clear-react-router 1.0.0

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 ADDED
@@ -0,0 +1,169 @@
1
+ A lightweight, type-safe routing library for React applications with nested routes, data loading, navigation blocking, and prefetching.
2
+
3
+ ## Features
4
+
5
+ - 🧩 **Nested Routes** - Organize your UI with nested layouts and routes
6
+ - âš¡ **Data Loading** - Built-in loaders with caching and stale-while-revalidate strategy
7
+ - 🔒 **Navigation Blocking** - Prevent accidental navigation with `useBlocker`
8
+ - 🎯 **Type-safe Redirects** - Redirect from loaders and beforeLoad hooks
9
+ - 📦 **Prefetching** - Preload data on hover for instant navigation
10
+ - 🚀 **Lazy Loading** - Code-split your routes with dynamic imports for optimal performance
11
+ - 🎨 **Flexible API** - Use components or hooks as you prefer
12
+ - 📱 **Browser History** - Full support for browser back/forward buttons
13
+ - 🧠 **Context-aware** - Pass and update context through routes
14
+
15
+ ## API
16
+
17
+ ### `Router`
18
+
19
+ Main component that renders the application based on current URL.
20
+
21
+ | Prop | Type | Description |
22
+ |------|------|-------------|
23
+ | `routeList` | `RouteItem[]` | Array of route configurations |
24
+ | `context` | `object` | Optional initial context (user, theme, etc.) |
25
+
26
+ ### `createRouter(routes)`
27
+
28
+ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic params, builds nested paths.
29
+
30
+ ### `redirect(url, search?)`
31
+
32
+ Redirects from `beforeLoad`.
33
+ ```
34
+ beforeLoad: context => {
35
+ if (!context.isAuthorized) return redirect('/');
36
+ }
37
+ ```
38
+
39
+ ### `Link`
40
+
41
+ Component for client-side navigation with prefetch support.
42
+
43
+ | Prop | Type | Default |
44
+ |------|------|---------|
45
+ | `to` | `string` | required |
46
+ | `prefetch` | `boolean` | `true` |
47
+ | `children` | `ReactElement` | required |
48
+
49
+ ## Hooks
50
+
51
+ ### `useNavigate()`
52
+
53
+ Returns function to navigate programmatically:
54
+
55
+ - `navigate({ pathname: '/about' })` - navigate to path
56
+ - `navigate({ pathname: '/user/123', state: { fromDashboard: true } })` - navigate with state
57
+ - `navigate(-1)` - go back
58
+
59
+ **Note:** Navigation state can be accessed via `useLocation()`:
60
+
61
+ ```
62
+ const navigate = useNavigate();
63
+ navigate({ pathname: '/profile', state: { userId: 123 } });
64
+
65
+ // In Profile component
66
+ const { state } = useLocation();
67
+ console.log(state); // { userId: 123 }
68
+ ```
69
+
70
+ ### `useParams<T>()`
71
+
72
+ Returns route parameters object.
73
+
74
+ const params = useParams<{ userId: string }>();
75
+ // URL: /user/123 → params.userId === '123'
76
+
77
+ ### `useLocation()`
78
+
79
+ Returns current location `{ pathname, search, state }`.
80
+ ```
81
+ const { pathname, search, state } = useLocation();
82
+ ```
83
+
84
+ ### `useLoaderState()`
85
+
86
+ Returns loaderState from current route's loader.
87
+
88
+ ### `useBlocker(callback)`
89
+
90
+ Blocks navigation when callback returns `true`.
91
+
92
+ **Returns:**
93
+
94
+ | Property | Type | Description |
95
+ |----------|------|-------------|
96
+ | `state` | `'unblocked' \| 'charged' \| 'blocked'` | Current blocker state |
97
+ | `process()` | `() => void` | Confirm navigation and proceed |
98
+ | `reset()` | `() => void` | Cancel navigation |
99
+
100
+ ```
101
+ const { state, process, reset } = useBlocker(() => hasUnsavedChanges);
102
+
103
+ useEffect(() => {
104
+ if (state === 'blocked') {
105
+ // Show your custom modal
106
+ if (confirm('Leave without saving?')) {
107
+ process();
108
+ } else {
109
+ reset();
110
+ }
111
+ }
112
+ }, [state, process, reset]);
113
+ ```
114
+
115
+ ### `useBeforeUnload(callback?)`
116
+
117
+ Executes a callback when the page is about to be closed or reloaded. Perfect for auto-saving data at the last moment.
118
+
119
+ **Parameters:**
120
+
121
+ | Parameter | Type | Description |
122
+ |-----------|------|-------------|
123
+ | `callback` | `() => void | undefined` | Function to execute before page unload (e.g., auto-save) |
124
+
125
+ **Note:** This hook does not show a browser confirmation dialog. It silently executes the callback, allowing you to save user data in the background before the page closes.
126
+
127
+ ```
128
+ const [text, setText] = useState('');
129
+ const onSave = useCallback(() => {
130
+ localStorage.setItem('draft', text);
131
+ }, [text]);
132
+
133
+ // Auto-save when user tries to close/reload the page
134
+ useBeforeUnload(text ? onSave : undefined);
135
+ ```
136
+ ## Route Configuration
137
+
138
+ ### `RouteItem`
139
+
140
+ | Property | Type | Description |
141
+ |----------|------|-------------|
142
+ | `path` | `string` | Route path, e.g., `/user/:userId` |
143
+ | `element` | `ReactElement \| () => ReactElement \| LazyComponent` | Component to render |
144
+ | `loader` | `() => Promise<unknown>` | Fetch data |
145
+ | `beforeLoad` | `(context) => Promise<void>` | Auth checks, redirects |
146
+ | `afterLoad` | `(context) => Promise<void>` | Analytics, side effects |
147
+ | `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
148
+ | `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback (for loader) |
149
+ | `errorElement` | `ReactElement \| () => ReactElement` | Error fallback |
150
+ | `staleTime` | `number` | Cache duration in ms for loader data |
151
+ | `children` | `RouteItem[]` | Nested routes |
152
+
153
+ ## Lazy Loading
154
+
155
+ Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
156
+ ```
157
+ {
158
+ path: '/heavy-page',
159
+ element: () => import('./pages/HeavyComponent'),
160
+ fallback: () => <div>Loading...</div>,
161
+ }
162
+ ```
163
+
164
+ ## Requirements
165
+ - React 16.6+ (for React.lazy and Suspense)
166
+ - Use `default` export for your lazy-loaded components
167
+
168
+ ## License
169
+ MIT
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,439 @@
1
+ import { Suspense as e, createContext as t, lazy as n, useCallback as r, useContext as i, useEffect as a, useMemo as o, useRef as s, useState as c } from "react";
2
+ //#region \0rolldown/runtime.js
3
+ var l = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), u = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
4
+ if (typeof require < "u") return require.apply(this, arguments);
5
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
6
+ }), d = t({}), f = t({}), p = t({}), m = /* @__PURE__ */ l(((e) => {
7
+ var t = Symbol.for("react.transitional.element"), n = Symbol.for("react.fragment");
8
+ function r(e, n, r) {
9
+ var i = null;
10
+ if (r !== void 0 && (i = "" + r), n.key !== void 0 && (i = "" + n.key), "key" in n) for (var a in r = {}, n) a !== "key" && (r[a] = n[a]);
11
+ else r = n;
12
+ return n = r.ref, {
13
+ $$typeof: t,
14
+ type: e,
15
+ key: i,
16
+ ref: n === void 0 ? null : n,
17
+ props: r
18
+ };
19
+ }
20
+ e.Fragment = n, e.jsx = r, e.jsxs = r;
21
+ })), h = /* @__PURE__ */ l(((e) => {
22
+ process.env.NODE_ENV !== "production" && (function() {
23
+ function t(e) {
24
+ if (e == null) return null;
25
+ if (typeof e == "function") return e.$$typeof === k ? null : e.displayName || e.name || null;
26
+ if (typeof e == "string") return e;
27
+ switch (e) {
28
+ case v: return "Fragment";
29
+ case b: return "Profiler";
30
+ case y: return "StrictMode";
31
+ case w: return "Suspense";
32
+ case T: return "SuspenseList";
33
+ case O: return "Activity";
34
+ }
35
+ if (typeof e == "object") switch (typeof e.tag == "number" && console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."), e.$$typeof) {
36
+ case _: return "Portal";
37
+ case S: return e.displayName || "Context";
38
+ case x: return (e._context.displayName || "Context") + ".Consumer";
39
+ case C:
40
+ var n = e.render;
41
+ return e = e.displayName, e ||= (e = n.displayName || n.name || "", e === "" ? "ForwardRef" : "ForwardRef(" + e + ")"), e;
42
+ case E: return n = e.displayName || null, n === null ? t(e.type) || "Memo" : n;
43
+ case D:
44
+ n = e._payload, e = e._init;
45
+ try {
46
+ return t(e(n));
47
+ } catch {}
48
+ }
49
+ return null;
50
+ }
51
+ function n(e) {
52
+ return "" + e;
53
+ }
54
+ function r(e) {
55
+ try {
56
+ n(e);
57
+ var t = !1;
58
+ } catch {
59
+ t = !0;
60
+ }
61
+ if (t) {
62
+ t = console;
63
+ var r = t.error, i = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
64
+ return r.call(t, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", i), n(e);
65
+ }
66
+ }
67
+ function i(e) {
68
+ if (e === v) return "<>";
69
+ if (typeof e == "object" && e && e.$$typeof === D) return "<...>";
70
+ try {
71
+ var n = t(e);
72
+ return n ? "<" + n + ">" : "<...>";
73
+ } catch {
74
+ return "<...>";
75
+ }
76
+ }
77
+ function a() {
78
+ var e = A.A;
79
+ return e === null ? null : e.getOwner();
80
+ }
81
+ function o() {
82
+ return Error("react-stack-top-frame");
83
+ }
84
+ function s(e) {
85
+ if (j.call(e, "key")) {
86
+ var t = Object.getOwnPropertyDescriptor(e, "key").get;
87
+ if (t && t.isReactWarning) return !1;
88
+ }
89
+ return e.key !== void 0;
90
+ }
91
+ function c(e, t) {
92
+ function n() {
93
+ P || (P = !0, console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", t));
94
+ }
95
+ n.isReactWarning = !0, Object.defineProperty(e, "key", {
96
+ get: n,
97
+ configurable: !0
98
+ });
99
+ }
100
+ function l() {
101
+ var e = t(this.type);
102
+ return F[e] || (F[e] = !0, console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")), e = this.props.ref, e === void 0 ? null : e;
103
+ }
104
+ function d(e, t, n, r, i, a) {
105
+ var o = n.ref;
106
+ return e = {
107
+ $$typeof: g,
108
+ type: e,
109
+ key: t,
110
+ props: n,
111
+ _owner: r
112
+ }, (o === void 0 ? null : o) === null ? Object.defineProperty(e, "ref", {
113
+ enumerable: !1,
114
+ value: null
115
+ }) : Object.defineProperty(e, "ref", {
116
+ enumerable: !1,
117
+ get: l
118
+ }), e._store = {}, Object.defineProperty(e._store, "validated", {
119
+ configurable: !1,
120
+ enumerable: !1,
121
+ writable: !0,
122
+ value: 0
123
+ }), Object.defineProperty(e, "_debugInfo", {
124
+ configurable: !1,
125
+ enumerable: !1,
126
+ writable: !0,
127
+ value: null
128
+ }), Object.defineProperty(e, "_debugStack", {
129
+ configurable: !1,
130
+ enumerable: !1,
131
+ writable: !0,
132
+ value: i
133
+ }), Object.defineProperty(e, "_debugTask", {
134
+ configurable: !1,
135
+ enumerable: !1,
136
+ writable: !0,
137
+ value: a
138
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
139
+ }
140
+ function f(e, n, i, o, l, u) {
141
+ var f = n.children;
142
+ if (f !== void 0) if (o) if (M(f)) {
143
+ for (o = 0; o < f.length; o++) p(f[o]);
144
+ Object.freeze && Object.freeze(f);
145
+ } else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
146
+ else p(f);
147
+ if (j.call(n, "key")) {
148
+ f = t(e);
149
+ var m = Object.keys(n).filter(function(e) {
150
+ return e !== "key";
151
+ });
152
+ o = 0 < m.length ? "{key: someKey, " + m.join(": ..., ") + ": ...}" : "{key: someKey}", R[f + o] || (m = 0 < m.length ? "{" + m.join(": ..., ") + ": ...}" : "{}", console.error("A props object containing a \"key\" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />", o, f, m, f), R[f + o] = !0);
153
+ }
154
+ if (f = null, i !== void 0 && (r(i), f = "" + i), s(n) && (r(n.key), f = "" + n.key), "key" in n) for (var h in i = {}, n) h !== "key" && (i[h] = n[h]);
155
+ else i = n;
156
+ return f && c(i, typeof e == "function" ? e.displayName || e.name || "Unknown" : e), d(e, f, i, a(), l, u);
157
+ }
158
+ function p(e) {
159
+ m(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e && e.$$typeof === D && (e._payload.status === "fulfilled" ? m(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
160
+ }
161
+ function m(e) {
162
+ return typeof e == "object" && !!e && e.$$typeof === g;
163
+ }
164
+ var h = u("react"), g = Symbol.for("react.transitional.element"), _ = Symbol.for("react.portal"), v = Symbol.for("react.fragment"), y = Symbol.for("react.strict_mode"), b = Symbol.for("react.profiler"), x = Symbol.for("react.consumer"), S = Symbol.for("react.context"), C = Symbol.for("react.forward_ref"), w = Symbol.for("react.suspense"), T = Symbol.for("react.suspense_list"), E = Symbol.for("react.memo"), D = Symbol.for("react.lazy"), O = Symbol.for("react.activity"), k = Symbol.for("react.client.reference"), A = h.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, j = Object.prototype.hasOwnProperty, M = Array.isArray, N = console.createTask ? console.createTask : function() {
165
+ return null;
166
+ };
167
+ h = { react_stack_bottom_frame: function(e) {
168
+ return e();
169
+ } };
170
+ var P, F = {}, I = h.react_stack_bottom_frame.bind(h, o)(), L = N(i(o)), R = {};
171
+ e.Fragment = v, e.jsx = function(e, t, n) {
172
+ var r = 1e4 > A.recentlyCreatedOwnerStacks++;
173
+ return f(e, t, n, !1, r ? Error("react-stack-top-frame") : I, r ? N(i(e)) : L);
174
+ }, e.jsxs = function(e, t, n) {
175
+ var r = 1e4 > A.recentlyCreatedOwnerStacks++;
176
+ return f(e, t, n, !0, r ? Error("react-stack-top-frame") : I, r ? N(i(e)) : L);
177
+ };
178
+ })();
179
+ })), g = (/* @__PURE__ */ l(((e, t) => {
180
+ process.env.NODE_ENV === "production" ? t.exports = m() : t.exports = h();
181
+ })))(), _ = ({ children: e, setContext: t, context: n, updateBlockedRoute: r, updateLocation: i, location: a, params: o, prefetchLoader: s, loaderCache: c, blockerState: l }) => /* @__PURE__ */ (0, g.jsx)(d.Provider, {
182
+ value: {
183
+ updateLocation: i,
184
+ updateBlockedRoute: r,
185
+ prefetchLoader: s,
186
+ setContext: t
187
+ },
188
+ children: /* @__PURE__ */ (0, g.jsx)(f.Provider, {
189
+ value: {
190
+ context: n,
191
+ loaderCache: c
192
+ },
193
+ children: /* @__PURE__ */ (0, g.jsx)(p.Provider, {
194
+ value: {
195
+ blockerState: l,
196
+ params: o,
197
+ location: a
198
+ },
199
+ children: e
200
+ })
201
+ })
202
+ }), v = (t, r) => {
203
+ let i = n(() => t().then((e) => ({ default: e.default || e })));
204
+ return () => /* @__PURE__ */ (0, g.jsx)(e, {
205
+ fallback: typeof r == "function" ? r() : r || null,
206
+ children: /* @__PURE__ */ (0, g.jsx)(i, {})
207
+ });
208
+ }, y = (e) => typeof e.element == "function" && e.element.toString().includes("import("), b = (e, t = [], n = "") => {
209
+ let r = e.path.match(/:[^/]+/g), i = e.path.replaceAll(/:[^/]+(\/|$)/g, "").split("/").filter(Boolean), a = e.path.split("/"), o = r ? [...t, ...r.map((e, t) => ({
210
+ key: i[t],
211
+ value: e.slice(1)
212
+ }))] : t, s = o.length ? `${n}${a.slice(0, a.length - 1).join("/")}` : e.path, c = y(e) ? v(e.element, e.fallback) : e.element;
213
+ return [{
214
+ ...e,
215
+ path: s,
216
+ params: o,
217
+ element: c
218
+ }, ...e.children?.flatMap((e) => b(e, o, s)) || []];
219
+ }, x = (e) => e.flatMap((e) => b(e, [])), S = (e) => {
220
+ let { pathname: t } = window.location, n = t.split("/");
221
+ return (e || []).map((e) => ({
222
+ index: n.findIndex((t) => t === e.key),
223
+ value: e.value
224
+ })).reduce((e, t) => ({
225
+ ...e,
226
+ [t.value]: n[t.index + 1]
227
+ }), {});
228
+ }, C = (e) => ({
229
+ pathname: e.pathname,
230
+ search: e.search
231
+ }), w = (e, t) => {
232
+ let n = e.path.split("/").filter(Boolean), r = e.params ? Object.keys(e.params).length : 0, i = t.split("/").filter(Boolean);
233
+ return n.every((e, t) => e === i[t + +!!t]) && i.length === n.length + r;
234
+ }, T = class {
235
+ url;
236
+ search;
237
+ cause;
238
+ constructor(e, t) {
239
+ this.url = e, this.search = t, this.cause = "redirect";
240
+ }
241
+ }, E = (e, t) => Promise.reject(new T(e, t)), D = (e) => {
242
+ let t = s(e);
243
+ return a(() => {
244
+ t.current = e;
245
+ }, [e]), t;
246
+ }, O = ({ setLocation: e, routeList: t, context: n, revalidateCache: i }) => {
247
+ let [l, u] = c({
248
+ from: "",
249
+ to: ""
250
+ }), d = s(""), f = D(r(async (r) => {
251
+ try {
252
+ let a = t.find((e) => w(e, r.pathname));
253
+ a?.beforeLoad && await a?.beforeLoad(n), e(r), r.pathname !== window.location.pathname && (history.pushState(null, "", r.pathname), d.current = r.pathname), await i(a), a?.afterLoad && await a?.afterLoad(n);
254
+ } catch (t) {
255
+ if (!(t instanceof T)) return t;
256
+ history.replaceState(null, "", `${t.url}${t.search || ""}`), e({
257
+ pathname: t.url,
258
+ search: t.search
259
+ });
260
+ }
261
+ }, [
262
+ n,
263
+ i,
264
+ t,
265
+ e
266
+ ])), p = r(({ type: e, payload: t = "" }) => u((n) => n.from === t && e === "charge" ? n : t && n.from !== t && e === "charge" ? {
267
+ ...n,
268
+ from: t
269
+ } : e === "reset" ? {
270
+ ...n,
271
+ to: ""
272
+ } : (e === "process" && f.current({ pathname: n.to }), !n.from && !n.to ? n : {
273
+ from: "",
274
+ to: ""
275
+ })), [f]), m = r(async (e) => {
276
+ l.from ? u((t) => ({
277
+ ...t,
278
+ to: e.pathname
279
+ })) : await f.current(e);
280
+ }, [l.from, f]);
281
+ return a(() => {
282
+ let t = async (t) => {
283
+ let n = C(t.target.location);
284
+ d.current === l.from ? (u({
285
+ from: d.current,
286
+ to: n.pathname
287
+ }), history.replaceState(null, "", d.current)) : e(n);
288
+ };
289
+ return window.addEventListener("popstate", t), () => window.removeEventListener("popstate", t);
290
+ }, [l.from, e]), a(() => {
291
+ let e = C(window.location);
292
+ f.current(e), d.current = e.pathname;
293
+ }, [f]), {
294
+ blockerState: o(() => l.from && l.to ? "blocked" : l.from ? "charged" : "unblocked", [l]),
295
+ updateLocation: m,
296
+ updateBlockedRoute: p
297
+ };
298
+ }, k = (e) => {
299
+ let [t, n] = c({}), [i, a] = c({}), [o, s] = c(!1), [l, u] = c({}), d = r(({ key: e, value: t }) => n((n) => ({
300
+ ...n,
301
+ [e]: t
302
+ })), []), f = r((e) => {
303
+ if (!e) return !0;
304
+ let t = i[e.path];
305
+ return !!(t && Date.now() - t < (e.staleTime || 0));
306
+ }, [i]), p = r(async (e) => {
307
+ if (e?.loader && !f(e)) {
308
+ u((t) => ({
309
+ ...t,
310
+ [e.path]: !0
311
+ })), n((t) => Object.keys(t).filter((t) => t !== e.path).reduce((e, n) => ({
312
+ ...e,
313
+ [n]: t[n]
314
+ }), {}));
315
+ try {
316
+ s(!1);
317
+ let t = await e?.loader();
318
+ a((t) => ({
319
+ ...t,
320
+ [e.path]: Date.now()
321
+ })), d({
322
+ key: e.path,
323
+ value: t
324
+ });
325
+ } catch {
326
+ s(!0);
327
+ } finally {
328
+ u((t) => ({
329
+ ...t,
330
+ [e.path]: !1
331
+ }));
332
+ }
333
+ }
334
+ }, [f, d]);
335
+ return {
336
+ loaderCache: t,
337
+ loaderError: o,
338
+ prefetchLoader: r(async (t) => {
339
+ let n = e.find((e) => w(e, t));
340
+ n && await p(n);
341
+ }, [p, e]),
342
+ revalidateCache: p,
343
+ isLoadingMap: l
344
+ };
345
+ }, A = (e) => e ? typeof e == "function" ? /* @__PURE__ */ (0, g.jsx)(e, {}) : e : null, j = "error 404. Page not found", M = "*", N = ({ routeList: e, context: t = {} }) => {
346
+ let [n, r] = c(C(window.location)), [i, a] = c(t), s = o(() => e.find((e) => e.path === M || w(e, n.pathname)), [n.pathname, e]), { loaderError: l, loaderCache: u, prefetchLoader: d, revalidateCache: f, isLoadingMap: p } = k(e), { blockerState: m, updateLocation: h, updateBlockedRoute: v } = O({
347
+ setLocation: r,
348
+ routeList: e,
349
+ context: i,
350
+ revalidateCache: f
351
+ }), y = o(() => s?.params ? S(s.params) : {}, [s]), b = o(() => ({
352
+ location: n,
353
+ updateLocation: h,
354
+ params: y,
355
+ loaderCache: u,
356
+ prefetchLoader: d,
357
+ updateBlockedRoute: v,
358
+ blockerState: m,
359
+ context: i,
360
+ setContext: a
361
+ }), [
362
+ m,
363
+ u,
364
+ n,
365
+ y,
366
+ d,
367
+ i,
368
+ v,
369
+ h
370
+ ]);
371
+ return s?.loader && !l && p[n.pathname] ? /* @__PURE__ */ (0, g.jsx)(_, {
372
+ ...b,
373
+ children: A(s?.loaderFallback)
374
+ }) : l ? /* @__PURE__ */ (0, g.jsx)(_, {
375
+ ...b,
376
+ children: A(s?.errorElement)
377
+ }) : /* @__PURE__ */ (0, g.jsx)(_, {
378
+ ...b,
379
+ children: A(s?.element) || j
380
+ });
381
+ }, P = () => {
382
+ let e = i(p);
383
+ if (!Object.keys(e).length) throw Error("useNavigationState must be used within Router component");
384
+ return e;
385
+ }, F = () => {
386
+ let e = i(d);
387
+ if (!Object.keys(e).length) throw Error("useRouterActions must be used within Router component");
388
+ return e;
389
+ }, I = () => {
390
+ let e = i(f);
391
+ if (!Object.keys(e).length) throw Error("useRouterData must be used within Router component");
392
+ return e;
393
+ }, L = () => {
394
+ let { updateLocation: e } = F();
395
+ return r(async (t) => t === -1 ? history.go(-1) : await e(t), [e]);
396
+ }, R = ({ children: e, to: t, prefetch: n = !0 }) => {
397
+ let { prefetchLoader: r } = F(), i = L();
398
+ return /* @__PURE__ */ (0, g.jsx)("a", {
399
+ style: { cursor: "pointer" },
400
+ onClick: () => i({ pathname: t }),
401
+ onMouseOver: () => n && r(t),
402
+ children: e
403
+ });
404
+ }, z = () => {
405
+ let { params: e } = P();
406
+ return e;
407
+ }, B = () => P().location, V = () => {
408
+ let { loaderCache: e } = I();
409
+ return e[window.location.pathname];
410
+ }, H = (e) => {
411
+ let { location: { pathname: t }, blockerState: n } = P(), { updateBlockedRoute: r } = F(), i = e();
412
+ return a(() => r(i ? {
413
+ type: "charge",
414
+ payload: t
415
+ } : { type: "unblock" }), [
416
+ i,
417
+ t,
418
+ r
419
+ ]), {
420
+ state: n,
421
+ process: () => r({ type: "process" }),
422
+ reset: () => r({ type: "reset" })
423
+ };
424
+ }, U = (e) => {
425
+ a(() => {
426
+ let t = (t) => {
427
+ e && (t.preventDefault(), e());
428
+ };
429
+ return window.addEventListener("beforeunload", t), () => window.removeEventListener("beforeunload", t);
430
+ }, [e]);
431
+ }, W = () => {
432
+ let { context: e } = I(), { setContext: t } = F();
433
+ return {
434
+ context: e,
435
+ setContext: t
436
+ };
437
+ };
438
+ //#endregion
439
+ export { R as Link, N as Router, x as createRouter, E as redirect, U as useBeforeUnload, H as useBlocker, V as useLoaderState, B as useLocation, L as useNavigate, z as useParams, W as useRouterContext };
@@ -0,0 +1,6 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("react")):typeof define==`function`&&define.amd?define([`exports`,`react`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ClearReactRouter={},e.React))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),r=(0,t.createContext)({}),i=(0,t.createContext)({}),a=(0,t.createContext)({}),o=n((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),s=n((e=>{process.env.NODE_ENV!==`production`&&(function(){function t(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===O?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case _:return`Fragment`;case y:return`Profiler`;case v:return`StrictMode`;case C:return`Suspense`;case w:return`SuspenseList`;case D:return`Activity`}if(typeof e==`object`)switch(typeof e.tag==`number`&&console.error(`Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.`),e.$$typeof){case g:return`Portal`;case x:return e.displayName||`Context`;case b:return(e._context.displayName||`Context`)+`.Consumer`;case S:var n=e.render;return e=e.displayName,e||=(e=n.displayName||n.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case T:return n=e.displayName||null,n===null?t(e.type)||`Memo`:n;case E:n=e._payload,e=e._init;try{return t(e(n))}catch{}}return null}function n(e){return``+e}function r(e){try{n(e);var t=!1}catch{t=!0}if(t){t=console;var r=t.error,i=typeof Symbol==`function`&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||`Object`;return r.call(t,`The provided key is an unsupported type %s. This value must be coerced to a string before using it here.`,i),n(e)}}function i(e){if(e===_)return`<>`;if(typeof e==`object`&&e&&e.$$typeof===E)return`<...>`;try{var n=t(e);return n?`<`+n+`>`:`<...>`}catch{return`<...>`}}function a(){var e=k.A;return e===null?null:e.getOwner()}function o(){return Error(`react-stack-top-frame`)}function s(e){if(A.call(e,`key`)){var t=Object.getOwnPropertyDescriptor(e,`key`).get;if(t&&t.isReactWarning)return!1}return e.key!==void 0}function c(e,t){function n(){N||(N=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}function l(){var e=t(this.type);return P[e]||(P[e]=!0,console.error(`Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.`)),e=this.props.ref,e===void 0?null:e}function u(e,t,n,r,i,a){var o=n.ref;return e={$$typeof:h,type:e,key:t,props:n,_owner:r},(o===void 0?null:o)===null?Object.defineProperty(e,"ref",{enumerable:!1,value:null}):Object.defineProperty(e,"ref",{enumerable:!1,get:l}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:i}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:a}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function d(e,n,i,o,l,d){var p=n.children;if(p!==void 0)if(o)if(j(p)){for(o=0;o<p.length;o++)f(p[o]);Object.freeze&&Object.freeze(p)}else console.error(`React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.`);else f(p);if(A.call(n,`key`)){p=t(e);var m=Object.keys(n).filter(function(e){return e!==`key`});o=0<m.length?`{key: someKey, `+m.join(`: ..., `)+`: ...}`:`{key: someKey}`,L[p+o]||(m=0<m.length?`{`+m.join(`: ..., `)+`: ...}`:`{}`,console.error(`A props object containing a "key" prop is being spread into JSX:
2
+ let props = %s;
3
+ <%s {...props} />
4
+ React keys must be passed directly to JSX without using spread:
5
+ let props = %s;
6
+ <%s key={someKey} {...props} />`,o,p,m,p),L[p+o]=!0)}if(p=null,i!==void 0&&(r(i),p=``+i),s(n)&&(r(n.key),p=``+n.key),`key`in n)for(var h in i={},n)h!==`key`&&(i[h]=n[h]);else i=n;return p&&c(i,typeof e==`function`?e.displayName||e.name||`Unknown`:e),u(e,p,i,a(),l,d)}function f(e){p(e)?e._store&&(e._store.validated=1):typeof e==`object`&&e&&e.$$typeof===E&&(e._payload.status===`fulfilled`?p(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function p(e){return typeof e==`object`&&!!e&&e.$$typeof===h}var m=require("react"),h=Symbol.for(`react.transitional.element`),g=Symbol.for(`react.portal`),_=Symbol.for(`react.fragment`),v=Symbol.for(`react.strict_mode`),y=Symbol.for(`react.profiler`),b=Symbol.for(`react.consumer`),x=Symbol.for(`react.context`),S=Symbol.for(`react.forward_ref`),C=Symbol.for(`react.suspense`),w=Symbol.for(`react.suspense_list`),T=Symbol.for(`react.memo`),E=Symbol.for(`react.lazy`),D=Symbol.for(`react.activity`),O=Symbol.for(`react.client.reference`),k=m.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,A=Object.prototype.hasOwnProperty,j=Array.isArray,M=console.createTask?console.createTask:function(){return null};m={react_stack_bottom_frame:function(e){return e()}};var N,P={},F=m.react_stack_bottom_frame.bind(m,o)(),I=M(i(o)),L={};e.Fragment=_,e.jsx=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!1,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)},e.jsxs=function(e,t,n){var r=1e4>k.recentlyCreatedOwnerStacks++;return d(e,t,n,!0,r?Error(`react-stack-top-frame`):F,r?M(i(e)):I)}})()})),c=n(((e,t)=>{process.env.NODE_ENV===`production`?t.exports=o():t.exports=s()}))(),l=({children:e,setContext:t,context:n,updateBlockedRoute:o,updateLocation:s,location:l,params:u,prefetchLoader:d,loaderCache:f,blockerState:p})=>(0,c.jsx)(r.Provider,{value:{updateLocation:s,updateBlockedRoute:o,prefetchLoader:d,setContext:t},children:(0,c.jsx)(i.Provider,{value:{context:n,loaderCache:f},children:(0,c.jsx)(a.Provider,{value:{blockerState:p,params:u,location:l},children:e})})}),u=(e,n)=>{let r=(0,t.lazy)(()=>e().then(e=>({default:e.default||e})));return()=>(0,c.jsx)(t.Suspense,{fallback:typeof n==`function`?n():n||null,children:(0,c.jsx)(r,{})})},d=e=>typeof e.element==`function`&&e.element.toString().includes(`import(`),f=(e,t=[],n=``)=>{let r=e.path.match(/:[^/]+/g),i=e.path.replaceAll(/:[^/]+(\/|$)/g,``).split(`/`).filter(Boolean),a=e.path.split(`/`),o=r?[...t,...r.map((e,t)=>({key:i[t],value:e.slice(1)}))]:t,s=o.length?`${n}${a.slice(0,a.length-1).join(`/`)}`:e.path,c=d(e)?u(e.element,e.fallback):e.element;return[{...e,path:s,params:o,element:c},...e.children?.flatMap(e=>f(e,o,s))||[]]},p=e=>e.flatMap(e=>f(e,[])),m=e=>{let{pathname:t}=window.location,n=t.split(`/`);return(e||[]).map(e=>({index:n.findIndex(t=>t===e.key),value:e.value})).reduce((e,t)=>({...e,[t.value]:n[t.index+1]}),{})},h=e=>({pathname:e.pathname,search:e.search}),g=(e,t)=>{let n=e.path.split(`/`).filter(Boolean),r=e.params?Object.keys(e.params).length:0,i=t.split(`/`).filter(Boolean);return n.every((e,t)=>e===i[t+ +!!t])&&i.length===n.length+r},_=class{url;search;cause;constructor(e,t){this.url=e,this.search=t,this.cause=`redirect`}},v=(e,t)=>Promise.reject(new _(e,t)),y=e=>{let n=(0,t.useRef)(e);return(0,t.useEffect)(()=>{n.current=e},[e]),n},b=({setLocation:e,routeList:n,context:r,revalidateCache:i})=>{let[a,o]=(0,t.useState)({from:``,to:``}),s=(0,t.useRef)(``),c=y((0,t.useCallback)(async t=>{try{let a=n.find(e=>g(e,t.pathname));a?.beforeLoad&&await a?.beforeLoad(r),e(t),t.pathname!==window.location.pathname&&(history.pushState(null,``,t.pathname),s.current=t.pathname),await i(a),a?.afterLoad&&await a?.afterLoad(r)}catch(t){if(!(t instanceof _))return t;history.replaceState(null,``,`${t.url}${t.search||``}`),e({pathname:t.url,search:t.search})}},[r,i,n,e])),l=(0,t.useCallback)(({type:e,payload:t=``})=>o(n=>n.from===t&&e===`charge`?n:t&&n.from!==t&&e===`charge`?{...n,from:t}:e===`reset`?{...n,to:``}:(e===`process`&&c.current({pathname:n.to}),!n.from&&!n.to?n:{from:``,to:``})),[c]),u=(0,t.useCallback)(async e=>{a.from?o(t=>({...t,to:e.pathname})):await c.current(e)},[a.from,c]);return(0,t.useEffect)(()=>{let t=async t=>{let n=h(t.target.location);s.current===a.from?(o({from:s.current,to:n.pathname}),history.replaceState(null,``,s.current)):e(n)};return window.addEventListener(`popstate`,t),()=>window.removeEventListener(`popstate`,t)},[a.from,e]),(0,t.useEffect)(()=>{let e=h(window.location);c.current(e),s.current=e.pathname},[c]),{blockerState:(0,t.useMemo)(()=>a.from&&a.to?`blocked`:a.from?`charged`:`unblocked`,[a]),updateLocation:u,updateBlockedRoute:l}},x=e=>{let[n,r]=(0,t.useState)({}),[i,a]=(0,t.useState)({}),[o,s]=(0,t.useState)(!1),[c,l]=(0,t.useState)({}),u=(0,t.useCallback)(({key:e,value:t})=>r(n=>({...n,[e]:t})),[]),d=(0,t.useCallback)(e=>{if(!e)return!0;let t=i[e.path];return!!(t&&Date.now()-t<(e.staleTime||0))},[i]),f=(0,t.useCallback)(async e=>{if(e?.loader&&!d(e)){l(t=>({...t,[e.path]:!0})),r(t=>Object.keys(t).filter(t=>t!==e.path).reduce((e,n)=>({...e,[n]:t[n]}),{}));try{s(!1);let t=await e?.loader();a(t=>({...t,[e.path]:Date.now()})),u({key:e.path,value:t})}catch{s(!0)}finally{l(t=>({...t,[e.path]:!1}))}}},[d,u]);return{loaderCache:n,loaderError:o,prefetchLoader:(0,t.useCallback)(async t=>{let n=e.find(e=>g(e,t));n&&await f(n)},[f,e]),revalidateCache:f,isLoadingMap:c}},S=e=>e?typeof e==`function`?(0,c.jsx)(e,{}):e:null,C=`error 404. Page not found`,w=`*`,T=({routeList:e,context:n={}})=>{let[r,i]=(0,t.useState)(h(window.location)),[a,o]=(0,t.useState)(n),s=(0,t.useMemo)(()=>e.find(e=>e.path===w||g(e,r.pathname)),[r.pathname,e]),{loaderError:u,loaderCache:d,prefetchLoader:f,revalidateCache:p,isLoadingMap:_}=x(e),{blockerState:v,updateLocation:y,updateBlockedRoute:T}=b({setLocation:i,routeList:e,context:a,revalidateCache:p}),E=(0,t.useMemo)(()=>s?.params?m(s.params):{},[s]),D=(0,t.useMemo)(()=>({location:r,updateLocation:y,params:E,loaderCache:d,prefetchLoader:f,updateBlockedRoute:T,blockerState:v,context:a,setContext:o}),[v,d,r,E,f,a,T,y]);return s?.loader&&!u&&_[r.pathname]?(0,c.jsx)(l,{...D,children:S(s?.loaderFallback)}):u?(0,c.jsx)(l,{...D,children:S(s?.errorElement)}):(0,c.jsx)(l,{...D,children:S(s?.element)||C})},E=()=>{let e=(0,t.useContext)(a);if(!Object.keys(e).length)throw Error(`useNavigationState must be used within Router component`);return e},D=()=>{let e=(0,t.useContext)(r);if(!Object.keys(e).length)throw Error(`useRouterActions must be used within Router component`);return e},O=()=>{let e=(0,t.useContext)(i);if(!Object.keys(e).length)throw Error(`useRouterData must be used within Router component`);return e},k=()=>{let{updateLocation:e}=D();return(0,t.useCallback)(async t=>t===-1?history.go(-1):await e(t),[e])};e.Link=({children:e,to:t,prefetch:n=!0})=>{let{prefetchLoader:r}=D(),i=k();return(0,c.jsx)(`a`,{style:{cursor:`pointer`},onClick:()=>i({pathname:t}),onMouseOver:()=>n&&r(t),children:e})},e.Router=T,e.createRouter=p,e.redirect=v,e.useBeforeUnload=e=>{(0,t.useEffect)(()=>{let t=t=>{e&&(t.preventDefault(),e())};return window.addEventListener(`beforeunload`,t),()=>window.removeEventListener(`beforeunload`,t)},[e])},e.useBlocker=e=>{let{location:{pathname:n},blockerState:r}=E(),{updateBlockedRoute:i}=D(),a=e();return(0,t.useEffect)(()=>i(a?{type:`charge`,payload:n}:{type:`unblock`}),[a,n,i]),{state:r,process:()=>i({type:`process`}),reset:()=>i({type:`reset`})}},e.useLoaderState=()=>{let{loaderCache:e}=O();return e[window.location.pathname]},e.useLocation=()=>E().location,e.useNavigate=k,e.useParams=()=>{let{params:e}=E();return e},e.useRouterContext=()=>{let{context:e}=O(),{setContext:t}=D();return{context:e,setContext:t}}});
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "clear-react-router",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight, type-safe routing library for React applications",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/AndrewBubnov/clear-react-router"
8
+ },
9
+ "sideEffects": false,
10
+ "type": "module",
11
+ "main": "dist/index.umd.js",
12
+ "module": "dist/index.es.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.es.js",
17
+ "require": "./dist/index.umd.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "vite build"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=16.8.0",
29
+ "react-dom": ">=16.8.0"
30
+ },
31
+ "keywords": [
32
+ "react",
33
+ "router",
34
+ "routing",
35
+ "typescript",
36
+ "spa",
37
+ "nested-routes"
38
+ ],
39
+ "license": "MIT",
40
+ "author": "Andrew Bubnov",
41
+ "devDependencies": {
42
+ "@types/react": "^19.2.17",
43
+ "@vitejs/plugin-react": "^6.0.2",
44
+ "react": "^19.2.7",
45
+ "react-dom": "^19.2.7",
46
+ "typescript": "^6.0.3",
47
+ "vite": "^8.0.16",
48
+ "vite-plugin-dts": "^5.0.2"
49
+ }
50
+ }