@arielgonzaguer/michi-router 2.2.2 → 3.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 CHANGED
@@ -6,22 +6,72 @@
6
6
 
7
7
  ![Logo de MichiRouter](https://cdn.jsdelivr.net/gh/Ariel-GonzAguer/michi-router@main/public/michiRouter_LOGO.png)
8
8
 
9
- El router minimalista y simple para React.
10
- El objetivo principal de esta herramienta es proporcionar la funcionalidad básica de enrutamiento.
11
- No requiere de ninguna dependencia externa, ideal para proyectos pequeños.
12
- Creado con TypeScript.
9
+ Router minimalista para React, enfocado en tamaño, DX y seguridad para navegación interna del lado del cliente.
13
10
 
14
11
  ## Características
15
12
 
16
- - **Minimalista**: Solo las funcionalidades esenciales de enrutamiento
17
- - **TypeScript**: Completamente tipado para mejor experiencia de desarrollo
18
- - **Zero dependencias**: No requiere librerías externas
19
- - **Liviano**: Bundle pequeño y optimizado
20
- - **Componente Protected**: Control de acceso integrado
13
+ - **Ligero**: pensado para mantenerse pequeño.
14
+ - **TypeScript first**: API completamente tipada.
15
+ - **Zero dependencias de runtime**.
16
+ - **Rutas dinámicas**: soporte para `:params` y wildcard `*`.
17
+ - **Hooks**: `useNavigate`, `useLocation`, `useParams`.
18
+ - **Basename**: útil para apps bajo subrutas (`/app`).
19
+ - **Componente `Protected`** opcional.
20
+ - **Navegación segura interna**: bloquea protocolos inseguros y destinos externos.
21
21
 
22
22
  ## Instalación
23
23
 
24
24
  ```bash
25
25
  npm install @arielgonzaguer/michi-router
26
26
  ```
27
- Para ver la documentación completa, visite [la página de documentación](https://michirouter.vercel.app/).
27
+
28
+ ## Uso rápido
29
+
30
+ ```tsx
31
+ import { RouterProvider, Link, useNavigate, useParams } from '@arielgonzaguer/michi-router';
32
+
33
+ function Home() {
34
+ const navigate = useNavigate();
35
+ return (
36
+ <>
37
+ <h1>Home</h1>
38
+ <Link to="/users/42">Ir al usuario</Link>
39
+ <button onClick={() => navigate('/search?q=michi#top')}>Buscar</button>
40
+ </>
41
+ );
42
+ }
43
+
44
+ function UserPage() {
45
+ const { id } = useParams<{ id: string }>();
46
+ return <h1>User {id}</h1>;
47
+ }
48
+
49
+ export default function App() {
50
+ return (
51
+ <RouterProvider
52
+ basename="/app"
53
+ routes={[
54
+ { path: '/', component: <Home /> },
55
+ { path: '/users/:id', component: <UserPage /> },
56
+ { path: '/docs/*', component: <h1>Docs</h1> }
57
+ ]}
58
+ notFound={<h1>404</h1>}
59
+ />
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## API
65
+
66
+ - `navigate(to, options?)` con `options.replace` y `options.state`.
67
+ - `useLocation()` retorna `{ pathname, search, hash, fullPath }`.
68
+ - `useParams()` retorna los parámetros de la ruta activa.
69
+ - `Link` está diseñado para rutas internas.
70
+
71
+ ## Entradas del paquete
72
+
73
+ - `@arielgonzaguer/michi-router`: API completa.
74
+ - `@arielgonzaguer/michi-router/core`: router base sin `Protected`.
75
+ - `@arielgonzaguer/michi-router/protected`: export del componente `Protected`.
76
+
77
+ Documentación: [https://michirouter.vercel.app/](https://michirouter.vercel.app/)
@@ -0,0 +1,12 @@
1
+ import { a as LinkProps, g as RouterProviderProps, f as RouterLocation, N as NavigateFn, d as RouteParams } from './types-DvzlhMRY.mjs';
2
+ export { L as LayoutProps, b as NavigateOptions, R as Route, e as RouterContextType } from './types-DvzlhMRY.mjs';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import 'react';
5
+
6
+ declare function RouterProvider({ routes, children, notFound, basename, layout: Layout }: RouterProviderProps): react_jsx_runtime.JSX.Element;
7
+ declare const Link: ({ to, children, className, onClick, ...rest }: LinkProps) => react_jsx_runtime.JSX.Element;
8
+ declare const useNavigate: () => NavigateFn;
9
+ declare const useLocation: () => RouterLocation;
10
+ declare const useParams: <T extends RouteParams = RouteParams>() => T;
11
+
12
+ export { Link, LinkProps, NavigateFn, RouteParams, RouterLocation, RouterProvider, RouterProviderProps, useLocation, useNavigate, useParams };
package/dist/core.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { LayoutProps, LinkProps, NavigateFn, NavigateOptions, Route, RouteParams, RouterContextType, RouterLocation, RouterProviderProps } from './michi-router/types';
2
+ export { Link, RouterProvider, useLocation, useNavigate, useParams } from './michi-router/Michi-router';
package/dist/core.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useParams = exports.useNavigate = exports.useLocation = exports.RouterProvider = exports.Link = void 0;
4
+ var Michi_router_1 = require("./michi-router/Michi-router");
5
+ Object.defineProperty(exports, "Link", { enumerable: true, get: function () { return Michi_router_1.Link; } });
6
+ Object.defineProperty(exports, "RouterProvider", { enumerable: true, get: function () { return Michi_router_1.RouterProvider; } });
7
+ Object.defineProperty(exports, "useLocation", { enumerable: true, get: function () { return Michi_router_1.useLocation; } });
8
+ Object.defineProperty(exports, "useNavigate", { enumerable: true, get: function () { return Michi_router_1.useNavigate; } });
9
+ Object.defineProperty(exports, "useParams", { enumerable: true, get: function () { return Michi_router_1.useParams; } });
package/dist/core.mjs ADDED
@@ -0,0 +1 @@
1
+ import{useCallback as b,createContext as E,useContext as w,useEffect as F,useMemo as C,useState as A}from"react";var k="https://michi-router.local",I=new Set(["http:","https:"]),N=t=>t.startsWith("/")?t:`/${t}`,g=t=>{let e=t.trim();if(!e)return"/";let n=N(e);return(n.length>1?n.replace(/\/+$/,""):n)||"/"},R=t=>t?g(t):"/",z=(t,e)=>e==="/"?!0:t===e||t.startsWith(`${e}/`),B=(t,e)=>e==="/"?t:t===e?"/":t.startsWith(`${e}/`)?t.slice(e.length)||"/":t,O=(t,e)=>t==="/"?e:e==="/"?t:`${t}${e}`,$=t=>{try{return decodeURIComponent(t)}catch{return t}},P=(t,e="/")=>{if(typeof t!="string")return null;let n=t.trim();if(!n||/[\u0000-\u001f\u007f]/.test(n))return null;let o=R(e),s=typeof window<"u"?window.location.origin:k,i=o==="/"?`${s}/`:`${s}${o}/`,a;try{a=new URL(n,i)}catch{return null}if(!I.has(a.protocol)||typeof window<"u"&&a.origin!==window.location.origin)return null;let r=g(a.pathname),c=z(r,o)?g(B(r,o)):r,u=g(O(o,c)),p=a.search||"",h=a.hash||"",l=`${c}${p}${h}`,m=`${u}${p}${h}`;return{pathname:c,search:p,hash:h,fullPath:l,browserPath:m}},y=(t,e)=>{let n=g(t),o=g(e);if(n==="*")return{"*":o==="/"?"":o.slice(1)};let s=n==="/"?[]:n.slice(1).split("/"),i=o==="/"?[]:o.slice(1).split("/"),a={},r=0,c=0;for(;r<s.length&&c<i.length;){let u=s[r];if(u==="*"){if(r!==s.length-1)return null;a["*"]=i.slice(c).map($).join("/"),c=i.length,r=s.length;break}let p=i[c];if(u.startsWith(":")){let h=u.slice(1);if(!h)return null;a[h]=$(p)}else if(u!==p)return null;r+=1,c+=1}return r<s.length&&s[r]==="*"&&r===s.length-1&&(a["*"]="",r+=1),r!==s.length||c!==i.length?null:a};import{jsx as L}from"react/jsx-runtime";var v=typeof window<"u",T={pathname:"/",search:"",hash:"",fullPath:"/"},S=t=>{if(!v)return T;let e=`${window.location.pathname}${window.location.search}${window.location.hash}`,n=P(e,t);if(n)return{pathname:n.pathname,search:n.search,hash:n.hash,fullPath:n.fullPath};let o=g(window.location.pathname);return{pathname:o,search:window.location.search,hash:window.location.hash,fullPath:`${o}${window.location.search}${window.location.hash}`}},f=E({path:"/",location:T,params:{},basename:"/",navigate:()=>{console.warn("RouterContext usado fuera de RouterProvider")}});function U({routes:t,children:e,notFound:n,basename:o,layout:s}){let i=C(()=>R(o),[o]),[a,r]=A(()=>S(i));F(()=>{if(!v)return;let l=()=>r(S(i));return window.addEventListener("popstate",l),()=>window.removeEventListener("popstate",l)},[i]);let c=b((l,m)=>{let d=P(l,i);if(!d){console.warn(`MichiRouter: navegaci\xF3n bloqueada. URL interna no v\xE1lida: "${l}"`);return}let x={pathname:d.pathname,search:d.search,hash:d.hash,fullPath:d.fullPath};if(!v){r(x);return}m?.replace?window.history.replaceState(m?.state??null,"",d.browserPath):window.history.pushState(m?.state??null,"",d.browserPath),r(x)},[i]),u=C(()=>{for(let l of t){let m=y(l.path,a.pathname);if(m)return{route:l,params:m}}return null},[a.pathname,t]),p=u?.params??{},h=u?u.route.component:n??e;return L(f.Provider,{value:{path:a.pathname,location:a,params:p,basename:i,navigate:c},children:s?L(s,{children:h}):h})}var M=({to:t,children:e,className:n,onClick:o,...s})=>{let{navigate:i,basename:a}=w(f),r=P(t,a),c=r?r.browserPath:"#";return L("a",{href:c,className:n,onClick:u=>{o?.(u),!u.defaultPrevented&&(u.preventDefault(),i(t))},...s,children:e})},W=()=>w(f).navigate,_=()=>w(f).location,D=()=>w(f).params;export{M as Link,U as RouterProvider,_ as useLocation,W as useNavigate,D as useParams};
@@ -0,0 +1,5 @@
1
+ export { A as AuthState, L as LayoutProps, a as LinkProps, N as NavigateFn, b as NavigateOptions, P as ProtectedConfig, c as ProtectedProps, R as Route, d as RouteParams, e as RouterContextType, f as RouterLocation, g as RouterProviderProps } from './types-DvzlhMRY.mjs';
2
+ export { Link, RouterProvider, useLocation, useNavigate, useParams } from './core.mjs';
3
+ export { Protected } from './protected.mjs';
4
+ import 'react';
5
+ import 'react/jsx-runtime';
package/dist/index.d.ts CHANGED
@@ -1,75 +1,3 @@
1
- import React$1, { ReactNode } from 'react';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
3
-
4
- interface LayoutProps {
5
- children: ReactNode;
6
- }
7
- interface Route {
8
- path: string;
9
- component: ReactNode;
10
- }
11
- interface RouterContextType {
12
- path: string;
13
- navigate: (to: string) => void;
14
- }
15
- interface RouterProviderProps {
16
- routes: Array<{
17
- path: string;
18
- component: React.ReactNode;
19
- }>;
20
- children?: React.ReactNode;
21
- layout?: React.ComponentType<{
22
- children: React.ReactNode;
23
- }>;
24
- }
25
- interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
26
- to: string;
27
- children: ReactNode;
28
- className?: string;
29
- }
30
- interface AuthState<TUser = any> {
31
- user: TUser | null;
32
- isLoading: boolean;
33
- }
34
- interface ProtectedConfig<TUser = any> {
35
- states: AuthState<TUser>;
36
- redirectionPath: string;
37
- loadingComponent?: ReactNode;
38
- defaultMessage?: string;
39
- }
40
- interface ProtectedProps<TUser = any> {
41
- children: JSX.Element;
42
- configObject: ProtectedConfig<TUser>;
43
- }
44
-
45
- /**
46
- * Proveedor principal del enrutador.
47
- * @param routes - Array de rutas disponibles
48
- * @param children - Contenido a mostrar cuando no hay coincidencia de ruta (404)
49
- * @param layout - Componente de layout opcional para envolver todas las rutas
50
- */
51
- declare function RouterProvider({ routes, children, layout: Layout }: RouterProviderProps): react_jsx_runtime.JSX.Element;
52
- /**
53
- * Componente Link para navegación declarativa.
54
- * Previene la recarga de página y utiliza el historial del navegador.
55
- */
56
- declare const Link: ({ to, children, className, ...rest }: LinkProps & React$1.AnchorHTMLAttributes<HTMLAnchorElement>) => react_jsx_runtime.JSX.Element;
57
- /**
58
- * Hook para acceder a la función de navegación programática.
59
- * @returns Función navigate que permite cambiar de ruta
60
- */
61
- declare const useNavigate: () => (to: string) => void;
62
-
63
- /**
64
- * Un componente que restringe el acceso a sus hijos según el estado de autenticación del usuario.
65
- *
66
- * Si el usuario no está autenticado, redirige a la página de landing ("/").
67
- * De lo contrario, renderiza sus hijos.
68
- *
69
- * @param {Object} props - Props del componente
70
- * @param {React.ReactNode} props.children - Los nodos React a renderizar si el usuario está autenticado.
71
- * @returns {JSX.Element|null} Los hijos si está autenticado, de lo contrario loader.
72
- */
73
- declare function Protected<TUser = any>({ children, configObject }: ProtectedProps<TUser>): JSX.Element | null;
74
-
75
- export { type LayoutProps, Link, type LinkProps, Protected, type ProtectedProps, type Route, type RouterContextType, RouterProvider, type RouterProviderProps, useNavigate };
1
+ export * from './core';
2
+ export { Protected } from './protected';
3
+ export type { AuthState, ProtectedConfig, ProtectedProps } from './michi-router/types';
package/dist/index.js CHANGED
@@ -1,7 +1,20 @@
1
- "use strict";var l=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var L=(t,e)=>{for(var a in e)l(t,a,{get:e[a],enumerable:!0})},E=(t,e,a,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of x(e))!y.call(t,r)&&r!==a&&l(t,r,{get:()=>e[r],enumerable:!(o=w(e,r))||o.enumerable});return t};var M=t=>E(l({},"__esModule",{value:!0}),t);var T={};L(T,{Link:()=>C,Protected:()=>P,RouterProvider:()=>R,useNavigate:()=>d});module.exports=M(T);var s=require("react"),u=require("react/jsx-runtime"),c=typeof window<"u",v=()=>c?window.location.pathname:"/",f=(0,s.createContext)({path:v(),navigate:()=>{console.warn("RouterContext usado fuera de RouterProvider")}});function R({routes:t,children:e,layout:a}){let[o,r]=(0,s.useState)(v());(0,s.useEffect)(()=>{if(!c)return;let n=()=>r(window.location.pathname);return window.addEventListener("popstate",n),()=>window.removeEventListener("popstate",n)},[]);let i=(0,s.useCallback)(n=>{if(!c){r(n);return}if(typeof n!="string"||n.includes("javascript:")||n.includes("data:")){console.warn("MichiRouter: URL potencialmente insegura bloqueada:",n);return}window.history.pushState({},"",n),r(n)},[]),m=t.find(n=>n.path===o),g=m?m.component:e;try{return(0,u.jsx)(f.Provider,{value:{path:o,navigate:i},children:a?(0,u.jsx)(a,{children:g}):g})}catch(n){return console.error("Error en RouterProvider:",n),(0,u.jsx)("div",{children:"Error en el enrutador. Consulta la consola para m\xE1s detalles."})}}var C=({to:t,children:e,className:a,...o})=>{let{navigate:r}=(0,s.useContext)(f);return(0,u.jsx)("a",{href:t,className:a,onClick:i=>{i.preventDefault(),r(t)},...o,children:e})},d=()=>{let{navigate:t}=(0,s.useContext)(f);return t};var h=require("react");var p=require("react/jsx-runtime");function P({children:t,configObject:e}){let a=d(),o={states:e?.states||{user:null,isLoading:!1},redirectionPath:e?.redirectionPath||"/",loadingComponent:e?.loadingComponent||null,defaultMessage:e?.defaultMessage||void 0};if(!e?.states)return console.error(`Componente Protected: El objeto de configuraci\xF3n es inv\xE1lido. Este es el formato esperado:
2
- {
3
- states: { user: any; isLoading: boolean };
4
- redirectionPath: string;
5
- loadingComponent?: React.ReactNode;
6
- defaultMessage?: string;
7
- }`),null;let{user:r,isLoading:i}=o.states;return(0,h.useEffect)(()=>{!i&&!r&&a(o.redirectionPath)},[i,r,a,o.redirectionPath]),i?o.loadingComponent?o.loadingComponent:o.defaultMessage?(0,p.jsx)(p.Fragment,{children:o.defaultMessage}):null:r?t:null}0&&(module.exports={Link,Protected,RouterProvider,useNavigate});
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Protected = void 0;
18
+ __exportStar(require("./core"), exports);
19
+ var protected_1 = require("./protected");
20
+ Object.defineProperty(exports, "Protected", { enumerable: true, get: function () { return protected_1.Protected; } });
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import{useCallback as g,createContext as v,useContext as f,useState as R,useEffect as C}from"react";import{jsx as i}from"react/jsx-runtime";var u=typeof window<"u",P=()=>u?window.location.pathname:"/",d=v({path:P(),navigate:()=>{console.warn("RouterContext usado fuera de RouterProvider")}});function h({routes:n,children:o,layout:a}){let[t,r]=R(P());C(()=>{if(!u)return;let e=()=>r(window.location.pathname);return window.addEventListener("popstate",e),()=>window.removeEventListener("popstate",e)},[]);let s=g(e=>{if(!u){r(e);return}if(typeof e!="string"||e.includes("javascript:")||e.includes("data:")){console.warn("MichiRouter: URL potencialmente insegura bloqueada:",e);return}window.history.pushState({},"",e),r(e)},[]),l=n.find(e=>e.path===t),c=l?l.component:o;try{return i(d.Provider,{value:{path:t,navigate:s},children:a?i(a,{children:c}):c})}catch(e){return console.error("Error en RouterProvider:",e),i("div",{children:"Error en el enrutador. Consulta la consola para m\xE1s detalles."})}}var w=({to:n,children:o,className:a,...t})=>{let{navigate:r}=f(d);return i("a",{href:n,className:a,onClick:s=>{s.preventDefault(),r(n)},...t,children:o})},p=()=>{let{navigate:n}=f(d);return n};import{useEffect as x}from"react";import{Fragment as y,jsx as L}from"react/jsx-runtime";function m({children:n,configObject:o}){let a=p(),t={states:o?.states||{user:null,isLoading:!1},redirectionPath:o?.redirectionPath||"/",loadingComponent:o?.loadingComponent||null,defaultMessage:o?.defaultMessage||void 0};if(!o?.states)return console.error(`Componente Protected: El objeto de configuraci\xF3n es inv\xE1lido. Este es el formato esperado:
2
- {
3
- states: { user: any; isLoading: boolean };
4
- redirectionPath: string;
5
- loadingComponent?: React.ReactNode;
6
- defaultMessage?: string;
7
- }`),null;let{user:r,isLoading:s}=t.states;return x(()=>{!s&&!r&&a(t.redirectionPath)},[s,r,a,t.redirectionPath]),s?t.loadingComponent?t.loadingComponent:t.defaultMessage?L(y,{children:t.defaultMessage}):null:r?n:null}export{w as Link,m as Protected,h as RouterProvider,p as useNavigate};
1
+ import{useCallback as F,createContext as O,useContext as w,useEffect as W,useMemo as N,useState as _}from"react";var z="https://michi-router.local",B=new Set(["http:","https:"]),M=t=>t.startsWith("/")?t:`/${t}`,f=t=>{let e=t.trim();if(!e)return"/";let r=M(e);return(r.length>1?r.replace(/\/+$/,""):r)||"/"},R=t=>t?f(t):"/",A=(t,e)=>e==="/"?!0:t===e||t.startsWith(`${e}/`),U=(t,e)=>e==="/"?t:t===e?"/":t.startsWith(`${e}/`)?t.slice(e.length)||"/":t,b=(t,e)=>t==="/"?e:e==="/"?t:`${t}${e}`,T=t=>{try{return decodeURIComponent(t)}catch{return t}},g=(t,e="/")=>{if(typeof t!="string")return null;let r=t.trim();if(!r||/[\u0000-\u001f\u007f]/.test(r))return null;let n=R(e),s=typeof window<"u"?window.location.origin:z,i=n==="/"?`${s}/`:`${s}${n}/`,o;try{o=new URL(r,i)}catch{return null}if(!B.has(o.protocol)||typeof window<"u"&&o.origin!==window.location.origin)return null;let a=f(o.pathname),c=A(a,n)?f(U(a,n)):a,l=f(b(n,c)),d=o.search||"",h=o.hash||"",u=`${c}${d}${h}`,p=`${l}${d}${h}`;return{pathname:c,search:d,hash:h,fullPath:u,browserPath:p}},k=(t,e)=>{let r=f(t),n=f(e);if(r==="*")return{"*":n==="/"?"":n.slice(1)};let s=r==="/"?[]:r.slice(1).split("/"),i=n==="/"?[]:n.slice(1).split("/"),o={},a=0,c=0;for(;a<s.length&&c<i.length;){let l=s[a];if(l==="*"){if(a!==s.length-1)return null;o["*"]=i.slice(c).map(T).join("/"),c=i.length,a=s.length;break}let d=i[c];if(l.startsWith(":")){let h=l.slice(1);if(!h)return null;o[h]=T(d)}else if(l!==d)return null;a+=1,c+=1}return a<s.length&&s[a]==="*"&&a===s.length-1&&(o["*"]="",a+=1),a!==s.length||c!==i.length?null:o};import{jsx as L}from"react/jsx-runtime";var v=typeof window<"u",I={pathname:"/",search:"",hash:"",fullPath:"/"},E=t=>{if(!v)return I;let e=`${window.location.pathname}${window.location.search}${window.location.hash}`,r=g(e,t);if(r)return{pathname:r.pathname,search:r.search,hash:r.hash,fullPath:r.fullPath};let n=f(window.location.pathname);return{pathname:n,search:window.location.search,hash:window.location.hash,fullPath:`${n}${window.location.search}${window.location.hash}`}},P=O({path:"/",location:I,params:{},basename:"/",navigate:()=>{console.warn("RouterContext usado fuera de RouterProvider")}});function q({routes:t,children:e,notFound:r,basename:n,layout:s}){let i=N(()=>R(n),[n]),[o,a]=_(()=>E(i));W(()=>{if(!v)return;let u=()=>a(E(i));return window.addEventListener("popstate",u),()=>window.removeEventListener("popstate",u)},[i]);let c=F((u,p)=>{let m=g(u,i);if(!m){console.warn(`MichiRouter: navegaci\xF3n bloqueada. URL interna no v\xE1lida: "${u}"`);return}let S={pathname:m.pathname,search:m.search,hash:m.hash,fullPath:m.fullPath};if(!v){a(S);return}p?.replace?window.history.replaceState(p?.state??null,"",m.browserPath):window.history.pushState(p?.state??null,"",m.browserPath),a(S)},[i]),l=N(()=>{for(let u of t){let p=k(u.path,o.pathname);if(p)return{route:u,params:p}}return null},[o.pathname,t]),d=l?.params??{},h=l?l.route.component:r??e;return L(P.Provider,{value:{path:o.pathname,location:o,params:d,basename:i,navigate:c},children:s?L(s,{children:h}):h})}var D=({to:t,children:e,className:r,onClick:n,...s})=>{let{navigate:i,basename:o}=w(P),a=g(t,o),c=a?a.browserPath:"#";return L("a",{href:c,className:r,onClick:l=>{n?.(l),!l.defaultPrevented&&(l.preventDefault(),i(t))},...s,children:e})},x=()=>w(P).navigate,H=()=>w(P).location,G=()=>w(P).params;import{useEffect as K}from"react";import{Fragment as C,jsx as y}from"react/jsx-runtime";var J=t=>{let e=g(t,"/");return e?e.fullPath:"/"};function $({children:t,configObject:e}){let r=x(),n={states:e?.states||{user:null,isLoading:!1},redirectionPath:e?.redirectionPath||"/",loadingComponent:e?.loadingComponent||null,defaultMessage:e?.defaultMessage||void 0};if(!e?.states)return console.error(`Componente Protected: El objeto de configuraci\xF3n es inv\xE1lido. Este es el formato esperado:
2
+ {
3
+ states: { user: any; isLoading: boolean };
4
+ redirectionPath: string;
5
+ loadingComponent?: React.ReactNode;
6
+ defaultMessage?: string;
7
+ }`),null;let s=J(n.redirectionPath),{user:i,isLoading:o}=n.states;return K(()=>{!o&&!i&&(s!==n.redirectionPath&&console.warn(`Protected: redirectionPath inseguro bloqueado ("${n.redirectionPath}"). Se usa "/" como fallback.`),r(s,{replace:!0}))},[o,i,r,s,n.redirectionPath]),o?n.loadingComponent?y(C,{children:n.loadingComponent}):n.defaultMessage?y(C,{children:n.defaultMessage}):null:i?y(C,{children:t}):null}export{D as Link,$ as Protected,q as RouterProvider,H as useLocation,x as useNavigate,G as useParams};
@@ -0,0 +1,7 @@
1
+ import { c as ProtectedProps } from './types-DvzlhMRY.mjs';
2
+ export { A as AuthState, P as ProtectedConfig } from './types-DvzlhMRY.mjs';
3
+ import React from 'react';
4
+
5
+ declare function Protected<TUser = unknown>({ children, configObject }: ProtectedProps<TUser>): React.ReactElement | null;
6
+
7
+ export { Protected, ProtectedProps };
@@ -0,0 +1,2 @@
1
+ export type { AuthState, ProtectedConfig, ProtectedProps } from './michi-router/types';
2
+ export { default as Protected } from './michi-router/Protected';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Protected = void 0;
7
+ var Protected_1 = require("./michi-router/Protected");
8
+ Object.defineProperty(exports, "Protected", { enumerable: true, get: function () { return __importDefault(Protected_1).default; } });
@@ -0,0 +1,7 @@
1
+ import{useEffect as z}from"react";import{useCallback as b,createContext as T,useContext as E,useEffect as A,useMemo as F,useState as W}from"react";var v="https://michi-router.local",L=new Set(["http:","https:"]),C=t=>t.startsWith("/")?t:`/${t}`,i=t=>{let e=t.trim();if(!e)return"/";let r=C(e);return(r.length>1?r.replace(/\/+$/,""):r)||"/"},x=t=>t?i(t):"/",$=(t,e)=>e==="/"?!0:t===e||t.startsWith(`${e}/`),S=(t,e)=>e==="/"?t:t===e?"/":t.startsWith(`${e}/`)?t.slice(e.length)||"/":t,y=(t,e)=>t==="/"?e:e==="/"?t:`${t}${e}`;var p=(t,e="/")=>{if(typeof t!="string")return null;let r=t.trim();if(!r||/[\u0000-\u001f\u007f]/.test(r))return null;let n=x(e),a=typeof window<"u"?window.location.origin:v,s=n==="/"?`${a}/`:`${a}${n}/`,o;try{o=new URL(r,s)}catch{return null}if(!L.has(o.protocol)||typeof window<"u"&&o.origin!==window.location.origin)return null;let l=i(o.pathname),c=$(l,n)?i(S(l,n)):l,P=i(y(n,c)),u=o.search||"",h=o.hash||"",w=`${c}${u}${h}`,R=`${P}${u}${h}`;return{pathname:c,search:u,hash:h,fullPath:w,browserPath:R}};import{jsx as _}from"react/jsx-runtime";var I={pathname:"/",search:"",hash:"",fullPath:"/"};var k=T({path:"/",location:I,params:{},basename:"/",navigate:()=>{console.warn("RouterContext usado fuera de RouterProvider")}});var g=()=>E(k).navigate;import{Fragment as d,jsx as f}from"react/jsx-runtime";var B=t=>{let e=p(t,"/");return e?e.fullPath:"/"};function m({children:t,configObject:e}){let r=g(),n={states:e?.states||{user:null,isLoading:!1},redirectionPath:e?.redirectionPath||"/",loadingComponent:e?.loadingComponent||null,defaultMessage:e?.defaultMessage||void 0};if(!e?.states)return console.error(`Componente Protected: El objeto de configuraci\xF3n es inv\xE1lido. Este es el formato esperado:
2
+ {
3
+ states: { user: any; isLoading: boolean };
4
+ redirectionPath: string;
5
+ loadingComponent?: React.ReactNode;
6
+ defaultMessage?: string;
7
+ }`),null;let a=B(n.redirectionPath),{user:s,isLoading:o}=n.states;return z(()=>{!o&&!s&&(a!==n.redirectionPath&&console.warn(`Protected: redirectionPath inseguro bloqueado ("${n.redirectionPath}"). Se usa "/" como fallback.`),r(a,{replace:!0}))},[o,s,r,a,n.redirectionPath]),o?n.loadingComponent?f(d,{children:n.loadingComponent}):n.defaultMessage?f(d,{children:n.defaultMessage}):null:s?f(d,{children:t}):null}export{m as Protected};
@@ -0,0 +1,58 @@
1
+ import { ReactNode, AnchorHTMLAttributes, ComponentType } from 'react';
2
+
3
+ interface LayoutProps {
4
+ children: ReactNode;
5
+ }
6
+ type RouteParams = Record<string, string>;
7
+ interface RouterLocation {
8
+ pathname: string;
9
+ search: string;
10
+ hash: string;
11
+ fullPath: string;
12
+ }
13
+ interface NavigateOptions {
14
+ replace?: boolean;
15
+ state?: unknown;
16
+ }
17
+ type NavigateFn = (to: string, options?: NavigateOptions) => void;
18
+ interface Route {
19
+ path: string;
20
+ component: ReactNode;
21
+ }
22
+ interface RouterContextType {
23
+ path: string;
24
+ location: RouterLocation;
25
+ params: RouteParams;
26
+ basename: string;
27
+ navigate: NavigateFn;
28
+ }
29
+ interface RouterProviderProps {
30
+ routes: Route[];
31
+ children?: ReactNode;
32
+ notFound?: ReactNode;
33
+ basename?: string;
34
+ layout?: ComponentType<{
35
+ children: ReactNode;
36
+ }>;
37
+ }
38
+ interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
39
+ to: string;
40
+ children: ReactNode;
41
+ className?: string;
42
+ }
43
+ interface AuthState<TUser = unknown> {
44
+ user: TUser | null;
45
+ isLoading: boolean;
46
+ }
47
+ interface ProtectedConfig<TUser = unknown> {
48
+ states: AuthState<TUser>;
49
+ redirectionPath: string;
50
+ loadingComponent?: ReactNode;
51
+ defaultMessage?: string;
52
+ }
53
+ interface ProtectedProps<TUser = unknown> {
54
+ children: ReactNode;
55
+ configObject: ProtectedConfig<TUser>;
56
+ }
57
+
58
+ export type { AuthState as A, LayoutProps as L, NavigateFn as N, ProtectedConfig as P, Route as R, LinkProps as a, NavigateOptions as b, ProtectedProps as c, RouteParams as d, RouterContextType as e, RouterLocation as f, RouterProviderProps as g };
@@ -0,0 +1,58 @@
1
+ import { ReactNode, AnchorHTMLAttributes, ComponentType } from 'react';
2
+
3
+ interface LayoutProps {
4
+ children: ReactNode;
5
+ }
6
+ type RouteParams = Record<string, string>;
7
+ interface RouterLocation {
8
+ pathname: string;
9
+ search: string;
10
+ hash: string;
11
+ fullPath: string;
12
+ }
13
+ interface NavigateOptions {
14
+ replace?: boolean;
15
+ state?: unknown;
16
+ }
17
+ type NavigateFn = (to: string, options?: NavigateOptions) => void;
18
+ interface Route {
19
+ path: string;
20
+ component: ReactNode;
21
+ }
22
+ interface RouterContextType {
23
+ path: string;
24
+ location: RouterLocation;
25
+ params: RouteParams;
26
+ basename: string;
27
+ navigate: NavigateFn;
28
+ }
29
+ interface RouterProviderProps {
30
+ routes: Route[];
31
+ children?: ReactNode;
32
+ notFound?: ReactNode;
33
+ basename?: string;
34
+ layout?: ComponentType<{
35
+ children: ReactNode;
36
+ }>;
37
+ }
38
+ interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
39
+ to: string;
40
+ children: ReactNode;
41
+ className?: string;
42
+ }
43
+ interface AuthState<TUser = unknown> {
44
+ user: TUser | null;
45
+ isLoading: boolean;
46
+ }
47
+ interface ProtectedConfig<TUser = unknown> {
48
+ states: AuthState<TUser>;
49
+ redirectionPath: string;
50
+ loadingComponent?: ReactNode;
51
+ defaultMessage?: string;
52
+ }
53
+ interface ProtectedProps<TUser = unknown> {
54
+ children: ReactNode;
55
+ configObject: ProtectedConfig<TUser>;
56
+ }
57
+
58
+ export type { AuthState as A, LayoutProps as L, NavigateFn as N, ProtectedConfig as P, Route as R, LinkProps as a, NavigateOptions as b, ProtectedProps as c, RouteParams as d, RouterContextType as e, RouterLocation as f, RouterProviderProps as g };
package/package.json CHANGED
@@ -1,25 +1,42 @@
1
1
  {
2
2
  "name": "@arielgonzaguer/michi-router",
3
3
  "private": false,
4
- "version": "2.2.2",
4
+ "version": "3.0.0",
5
5
  "description": "El router minimalista y simple para React.\nEl objetivo principal de esta herramienta es proporcionar la funcionalidad básica de enrutamiento.",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
8
8
  "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./core": {
16
+ "types": "./dist/core.d.ts",
17
+ "import": "./dist/core.mjs",
18
+ "require": "./dist/core.js"
19
+ },
20
+ "./protected": {
21
+ "types": "./dist/protected.d.ts",
22
+ "import": "./dist/protected.mjs",
23
+ "require": "./dist/protected.js"
24
+ }
25
+ },
26
+ "sideEffects": false,
9
27
  "files": [
10
- "dist/index.mjs",
11
- "dist/index.js",
12
- "dist/index.d.ts"
28
+ "dist"
13
29
  ],
14
30
  "scripts": {
15
31
  "build": "tsc",
16
32
  "prepare": "npm run build:bundle",
17
33
  "clean": "rimraf dist",
18
34
  "prebuild": "npm run clean",
19
- "test": "vitest",
35
+ "test": "vitest --run",
20
36
  "test:watch": "vitest --watch",
21
- "build:bundle": "tsup src/index.ts --format esm,cjs --minify --dts --out-dir dist --external react --external react-dom",
22
- "prebublish": "npm run build:bundle && npm test",
37
+ "build:bundle": "npm run clean && tsup src/index.ts src/core.ts src/protected.ts --format esm,cjs --minify --dts --out-dir dist --external react --splitting false",
38
+ "size:check": "node scripts/check-size.js",
39
+ "prepublish": "npm run build:bundle && npm test && npm run size:check",
23
40
  "release:patch": "npm version patch && npm publish --access public && git push origin main --tags",
24
41
  "release:minor": "npm version minor && npm publish --access public && git push origin main --tags",
25
42
  "release:major": "npm version major && npm publish --access public && git push origin main --tags"
@@ -35,8 +52,7 @@
35
52
  "front-end"
36
53
  ],
37
54
  "peerDependencies": {
38
- "react": ">=17.0.0",
39
- "react-dom": ">=17.0.0"
55
+ "react": ">=17.0.0"
40
56
  },
41
57
  "devDependencies": {
42
58
  "@swc/core": "1.13.5",
@@ -53,6 +69,14 @@
53
69
  "typescript": "4.9.5",
54
70
  "vitest": "3.2.4"
55
71
  },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/Ariel-GonzAguer/michi-router.git"
75
+ },
76
+ "bugs": {
77
+ "url": "https://github.com/Ariel-GonzAguer/michi-router/issues"
78
+ },
79
+ "homepage": "https://github.com/Ariel-GonzAguer/michi-router#readme",
56
80
  "author": "Ariel Gonzales Agüero",
57
81
  "license": "MIT"
58
82
  }