npm package: @ripetchor/r-router
Description: HistoryAPI router.
Latest version: 1.0.2

# R-Router

HistoryAPI router.

**WARNING**: This router is primarily intended for personal use. It may contain bugs or unexpected behavior. Use at your own risk.

## Usage

```typescript
import { createRouter, RouterOutlet, RouteContext } from '@ripetchor/r-router';

const routes = [
  {
    path: '/',
    component: () => {
      const div = document.createElement('div');
      div.textContent = 'Home';
      return div;
    },
  },
  {
    path: '/user/:id',
    component: (ctx: RouteContext) => {
      const div = document.createElement('div');
      div.textContent = `User ID: ${ctx.params.id}`;
      return div;
    },
  },
  {
    path: '/about',
    lazy: async () => {
      const { default: About } = await import('./About');
      return About();
    },
  },
  {
    path: '/old-path',
    redirectTo: '/new-path',
  },
  {
    path: '*',
    component: () => {
      const div = document.createElement('div');
      div.textContent = '404 Not Found';
      return div;
    },
  },
];

const router = createRouter(routes, { defaultTitle: 'My App' });

async function init() {
  const outlet: RouterOutlet = await router.initialize();

  const header = document.createElement('header');
  const nav = document.createElement('nav');
  const links = [
    { text: 'Home', path: '/' },
    { text: 'About', path: '/about' },
    { text: 'User', path: '/user/1' },
  ];

  links.forEach((link) => {
    const a = document.createElement('a');
    a.href = link.path;
    a.textContent = link.text;
    a.addEventListener('click', (e) => {
      e.preventDefault();
      router.push(link.path);
    });
    nav.appendChild(a);
  });

  header.appendChild(nav);

  const footer = document.createElement('footer');

  document.body.appendChild(header);
  document.body.appendChild(outlet);
  document.body.appendChild(footer);
}

init();
```

## API

### `createRouter(routes: Route[], options?: RouterOptions): Router`

Creates a router instance.

- `routes`: Array of route definitions.
- `options`:
  - `defaultTitle`: Fallback document title.
  - `globalErrorComponent`: Custom error component.
  - `sortRoutesBySpecificity`: Sort routes by specificity (default: `true`).
  - `ignoreSameLocation`: Skip same-path navigation (default: `true`).

#### `initialize(): Promise<RouterOutlet>`

Sets up popstate listener, navigates to current path, returns root `RouterOutlet`(web-component). Throws an error if it has been initialized.

#### `push(path: string, state?: unknown): Promise<void>`

Navigates, adding to history.

#### `replace(path: string, state?: unknown): Promise<void>`

Navigates, replacing history entry.

### Route Types

- `SyncRoute`: Returns `HTMLElement` via `component(ctx: RouteContext)`.
- `AsyncRoute`: Returns `Promise<HTMLElement>` via `lazy(ctx: RouteContext)`.
- `RedirectRoute`: Redirects via `redirectTo`.

#### `Route.beforeEnter`

Runs before navigation. Return `false` to cancel, string to redirect, or `true` to proceed.

```typescript
{
  path: '/protected',
  beforeEnter: [(ctx: RouteContext) => isAuthenticated() ? true : '/login'],
}
```

#### `Route.afterEnter`

Runs after navigation.

```typescript
{
  path: '/dashboard',
  afterEnter: [(ctx: RouteContext) => console.log('Entered dashboard')],
}
```

### Error Handling

Use `errorComponent` in routes or `globalErrorComponent` in options.

```typescript
const routes = [
  {
    path: '*',
    errorComponent: (error, ctx) => {
      const div = document.createElement('div');
      div.textContent = `Error: ${error.message}`;
      return div;
    },
  },
];

// or

const router = createRouter(routes, {
  globalErrorComponent: (error) => {
    const div = document.createElement('div');
    div.textContent = 'Global Error';
    return div;
  },
});
```

## License

MIT