{"_id":"@bartlomiej-bykowy/toy-router","_rev":"2-0027b0d36c5a9ba1774dee2c185eac07","name":"@bartlomiej-bykowy/toy-router","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@bartlomiej-bykowy/toy-router","version":"1.0.0","_id":"@bartlomiej-bykowy/toy-router@1.0.0","maintainers":[{"name":"bartlomiej-bykowy","email":"bartlomiej.bykowy@gmail.com"}],"dist":{"shasum":"e424f4fc44afc2ee5e2e3ecb1b2b4d36022ec44f","tarball":"https://registry.npmjs.org/@bartlomiej-bykowy/toy-router/-/toy-router-1.0.0.tgz","fileCount":8,"integrity":"sha512-Id3IFjX+fiQNAc2sJ/C1JtXjRnE+IdFGg+RVpbEs7VKRzANuKCviwqS/AKgpgLS5X0+PAJj1xrR2K+yt7Ojl+A==","signatures":[{"sig":"MEUCIAZ5PXkgLnv3JMNeGmOrShlmeOppSa9e7+KLUnCc6HRNAiEA4FKkSgyT+YYLIvPMGXexW9r5GcdkhZ5AvVu9DoXSzqU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":81975},"main":"dist/index.cjs","type":"module","_from":"file:bartlomiej-bykowy-toy-router-1.0.0.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"lint":"eslint .","test":"vitest --run","build":"tsup","test-watch":"vitest","format:check":"prettier --check \"src/**/*.{ts,md}\"","format:write":"prettier --write \"src/**/*.{ts,md}\""},"_npmUser":{"name":"bartlomiej-bykowy","email":"bartlomiej.bykowy@gmail.com"},"_resolved":"/tmp/051efd2b4c388d930ff684748744d102/bartlomiej-bykowy-toy-router-1.0.0.tgz","_integrity":"sha512-Id3IFjX+fiQNAc2sJ/C1JtXjRnE+IdFGg+RVpbEs7VKRzANuKCviwqS/AKgpgLS5X0+PAJj1xrR2K+yt7Ojl+A==","_npmVersion":"10.8.2","description":"A tiny vanilla JS router for SPA apps","directories":{},"_nodeVersion":"20.19.5","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jiti":"^2.6.1","tsup":"^8.5.1","vite":"^7.2.2","jsdom":"^27.2.0","eslint":"^9.39.1","vitest":"^4.0.12","globals":"^16.5.0","prettier":"3.6.2","@eslint/js":"^9.39.1","typescript":"~5.9.3","typescript-eslint":"^8.48.0","eslint-config-prettier":"^10.1.8"},"_npmOperationalInternal":{"tmp":"tmp/toy-router_1.0.0_1764079342142_0.5778836631390207","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bartlomiej-bykowy/toy-router","version":"1.0.2","description":"A tiny vanilla JS router for SPA apps","main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"devDependencies":{"@eslint/js":"^9.39.1","eslint":"^9.39.1","eslint-config-prettier":"^10.1.8","globals":"^16.5.0","jiti":"^2.6.1","jsdom":"^27.2.0","prettier":"3.6.2","tsup":"^8.5.1","typescript":"~5.9.3","typescript-eslint":"^8.48.0","vite":"^7.2.2","vitest":"^4.0.12"},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","test":"vitest --run","test-watch":"vitest","format:write":"prettier --write \"src/**/*.{ts,md}\"","format:check":"prettier --check \"src/**/*.{ts,md}\"","lint":"eslint ."},"_id":"@bartlomiej-bykowy/toy-router@1.0.2","_integrity":"sha512-BnFumLODOndfFppLyEpxdQU7lUruXGqZ23ojRUo52qhez19fYP1a93m9pV5qH0ttnXeQxpEfzilalqbhuqPYsA==","_resolved":"/tmp/d7afdaeeaed8f3a3da6e651247197a4a/bartlomiej-bykowy-toy-router-1.0.2.tgz","_from":"file:bartlomiej-bykowy-toy-router-1.0.2.tgz","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-BnFumLODOndfFppLyEpxdQU7lUruXGqZ23ojRUo52qhez19fYP1a93m9pV5qH0ttnXeQxpEfzilalqbhuqPYsA==","shasum":"766b2f9219b4557755176893e768875817ca5ca6","tarball":"https://registry.npmjs.org/@bartlomiej-bykowy/toy-router/-/toy-router-1.0.2.tgz","fileCount":9,"unpackedSize":87846,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDAAsNylbZlv7LiNtfNQbMtQNyes9xJmx7owat4bhQKCwIgdG2S4n6DFxthj14SHNRjyhse/t27gQczbLrSd16gC8A="}]},"_npmUser":{"name":"bartlomiej-bykowy","email":"bartlomiej.bykowy@gmail.com"},"directories":{},"maintainers":[{"name":"bartlomiej-bykowy","email":"bartlomiej.bykowy@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/toy-router_1.0.2_1764080477577_0.8695550404694827"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-25T14:02:22.068Z","modified":"2025-11-25T14:21:17.956Z","1.0.0":"2025-11-25T14:02:22.336Z","1.0.2":"2025-11-25T14:21:17.780Z"},"description":"A tiny vanilla JS router for SPA apps","maintainers":[{"name":"bartlomiej-bykowy","email":"bartlomiej.bykowy@gmail.com"}],"readme":"# toy-router\n\n---\n\n## ⚠️ This package was created as a side/hobby project and it's not meant to be used in production.\n\n---\n\n_toy-router_ is a small, dependency-free client-side router for building simple SPA-style navigation in plain JavaScript.\nIt was created to demonstrate how routing works under the hood and how to build a minimal framework-agnostic navigation system.\nTo see it in action you can use [this](https://github.com/bartlomiej-bykowy/toy-shop) simple demo app.\n\n---\n\n## 🚀 Features\n\n- 🎯 Zero dependencies\n- 🧭 History API navigation (`pushState`, `replaceState`)\n- 🪝 Route guards (`beforeEnter`)\n- 🧩 Views as:\n  - strings and HTML strings (`<h1>Hello</h1>`)\n  - Web Components\n  - HTMLElements\n  - Lazy loaded views (`() => import(\"./page.js\")`)\n- 🔍 Dynamic route params (`/users/:id`)\n- 🔄 Route change events\n- 📌 Scroll restoration per route\n- 🔗 Hash scrolling (`/page#section`)\n- ⚠️ Dev-mode error overlay\n- 🚫 404 catch-all\n- 🔥 Full TypeScript support\n\n---\n\n## 📦 Installation\n\nInstall the router from npm:\n\n```sh\nnpm install @bartlomiej-bykowy/toy-router\n# or\npnpm add @bartlomiej-bykowy/toy-router\n# or\nyarn add @bartlomiej-bykowy/toy-router\n```\n\nthen install:\n\n```bash\nnpm install @bartlomiej-bykowy/toy-router\n# or\nyarn add @bartlomiej-bykowy/toy-router\n# or\npnpm add @bartlomiej-bykowy/toy-router\n```\n\n---\n\n## 🕹️ Basic Usage\n\nindex.ts\n\n```ts\nimport { Router } from \"@bartlomiej-bykowy/toy-router\";\n\nconst routes = [\n  {\n    path: \"/\",\n    view: \"<h1>Home</h1>\"\n  },\n  {\n    path: \"/about\",\n    view: \"about-page\"\n  },\n  {\n    path: \"/products\",\n    view: () => import(\"./pages/ProductsPage.js\")\n  },\n  {\n    path: \"/admin\",\n    view: \"<h1>Admin</h1>\",\n    beforeEnter: ({ query }) => query.token === \"secret\"\n  }\n];\n\nconst router = new Router(routes, \"#app\");\n```\n\nindex.html\n\n```html\n<body>\n  <div id=\"app\"></div>\n</body>\n```\n\nThe router will:\n\n- intercept `<a href=\"\">` clicks,\n- update the URL without reloading,\n- render the matching view into `#app`.\n\n---\n\n# 🔭 API\n\n`new Router(routes, rootSelector, devMode?)`\n\n| Parameter      | Type      | Description                           |\n| -------------- | --------- | ------------------------------------- |\n| `routes`       | `Route[]` | Route definitions                     |\n| `rootSelector` | `string`  | DOM selector where views are rendered |\n| `devMode`      | `boolean` | Enables the dev error overlay         |\n\n---\n\n## Types\n\n### `Route` type\n\n```ts\ntype Route = {\n  path: string; // \"/users/:id\"\n  view: ViewTypes; // See ViewTypes below\n  beforeEnter?: (ctx: RouteContext) => boolean | Promise<boolean>;\n  preserveScrollPosition?: boolean;\n  title?: string;\n};\n```\n\n### `ViewTypes` type\n\n```ts\nexport type HtmlStringView = string; // \"<h1>Hello</h1>\"\nexport type WebComponentView = `${Lowercase<string>}-${string}`; // \"app-home\"\nexport type HtmlElementView = HTMLElement; // new SomePage()\nexport type LazyView = () => Promise<any>; // dynamic import\n\nexport type ViewTypes =\n  | HtmlStringView\n  | WebComponentView\n  | HtmlElementView\n  | LazyView;\n```\n\n### `RouteContext` type\n\n```ts\ntype RouteContext = {\n  url: URL;\n  pathname: string;\n  params: Record<string, string>;\n  query: Record<string, string>;\n  hash: string;\n};\n```\n\n---\n\n### Methods\n\n`navigate(path: string)`\nUpdates the URL and renders the route:\n\n```ts\nrouter.navigate(\"/products/42\");\n```\n\n`redirect(path: string)`\nReplaces the current history entry:\n\n```ts\nrouter.redirect(\"/login\");\n```\n\n`onRouteChange(cb)`\nSubscribes to route changes:\n\n```ts\nrouter.onRouteChange((ctx) => {\n  console.log(\"Now at:\", ctx.pathname);\n});\n```\n\n`params()`\nReturns:\n\n```ts\n{\n  params: { id: \"42\" },\n  query: { sort: \"desc\" },\n  hash: \"section3\"\n}\n```\n\n`currentRoute()`\nReturns info about current route.\n\n```ts\n{\n  route: {\n    path: string; // \"/users/:id\"\n    view: ViewTypes; // See ViewTypes below\n    beforeEnter?: (ctx: RouteContext) => boolean | Promise<boolean>;\n    preserveScrollPosition?: boolean;\n    title?: string;\n  },\n  params: { id: \"42\" };\n}\n```\n\n`url()`\nBasically a wrappper over a new URL().\n\n---\n\n## 🛣️ Defining Routes\n\n### Static route\n\n```ts\n{ path: \"/about\", view: \"<h1>About</h1>\" }\n```\n\n### Dynamic params\n\n```ts\n{ path: \"/users/:id\", view: \"user-profile\" }\n```\n\n### Web component\n\n```ts\n{ path: \"/settings\", view: \"settings-page\" }\n```\n\n### HTMLElement instance\n\n```ts\n{ path: \"/cart\", view: document.createElement(\"cart-page\") }\n```\n\n### Lazy loaded view\n\n```ts\n{\n  path: \"/dashboard\",\n  view: () => import(\"./Dashboard.js\")\n}\n```\n\n### Route guard (beforeEnter)\n\n```ts\n{\n  path: \"/admin\",\n  view: \"admin-panel\",\n  beforeEnter: (ctx) => isLoggedIn()\n}\n```\n\nAsync:\n\n```ts\nbeforeEnter: async () => await checkServerPermission();\n```\n\n---\n\n### Set the page title\n\n```ts\n{\n  path: \"/products\",\n  view: \"products-page\"\n  title: \"Products\"\n}\n```\n\n---\n\n## 🔄 Route Change Events\n\nProvided callback will be fired on every route change.\n\n```ts\nconst unsubscribe = router.onRouteChange((ctx) => {\n  console.log(\"Route changed:\", ctx.pathname);\n});\n\n// later\nunsubscribe();\n```\n\n---\n\n## 🔁 Scroll Behavior\n\n- Restores scroll if `preserveScrollPosition: true`\n- Hash links scroll automatically\n\n---\n\n## ⚠️ Dev Mode Error Overlay\n\nIn dev mode, router renders a fullscreen overlay for:\n\n- invalid views\n- dynamic import errors\n- guard errors\n- missing root element\n- double initialization\n\nIn production errors go to console only.\n\n---\n\n## 🔧 TypeScript Support\n\nTypes are included:\n\n```ts\nimport type { Route, RouteContext } from \"@bartlomiej-bykowy/toy-router\";\n```\n\n## Dev mode\n\nRouter will try to automatically detect the enviorment set by the bundler. If you don't use the bundler, then you can set it manualy `new Router(routes,\"#app\",true)`.\n","readmeFilename":"README.npm.md"}