[
  {
    "path": ".cursor/rules/contributing.mdc",
    "content": "---\ndescription:\nglobs:\nalwaysApply: true\n---\n\n# Wouter - Minimalist React Router\n\n## Project Overview\n\nWouter is a **minimalist-friendly ~2.1KB React router** that focuses on performance, small bundle size, and simplicity. It provides hook-based routing for React and Preact applications.\n\n### Key Features\n\n- Tiny bundle size (2.1KB gzipped vs 18.7KB React Router)\n- Hook-based API (`useLocation`, `useRoute`, `useParams`, etc.)\n- Zero dependencies\n- Supports React and Preact\n- Optional top-level `<Router />` component\n- Nested routing support\n- SSR support\n- Memory location for testing\n\n## Project Structure\n\n```\nwouter/\n├── packages/\n│   ├── wouter/                 # Main React package\n│   │   ├── src/\n│   │   │   ├── index.js        # Main router implementation\n│   │   │   ├── use-browser-location.js\n│   │   │   ├── use-hash-location.js\n│   │   │   ├── memory-location.js\n│   │   │   ├── react-deps.js   # React imports\n│   │   │   └── paths.js        # Path utilities\n│   │   ├── test/               # Test files\n│   │   ├── types/              # TypeScript definitions\n│   │   └── esm/                # Built ES modules\n│   └── wouter-preact/          # Preact package\n├── assets/                     # Documentation assets\n└── .github/                    # GitHub workflows\n```\n\n## Development Workflow\n\n### Prerequisites\n\n- Node.js v20.17.0+ or v22.9.0+\n- npm\n\n### Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run tests (watch mode)\nnpm test\n\n# Run tests (single run)\nnpm test -- --run\n\n# Run specific test file\nnpm test -- packages/wouter/test/router.test.tsx --run\n\n# Run tests with coverage\nnpm test -- --coverage\n\n# Build packages\nnpm run build\n\n# Type checking\nnpm run typecheck\n\n# Lint code\nnpm run lint\n```\n\n### Test Files Organization\n\n- `test/router.test.tsx` - Router component and memoization tests\n- `test/use-location.test.tsx` - Location hook tests\n- `test/use-route.test.tsx` - Route matching tests\n- `test/link.test.tsx` - Link component tests\n- `test/memory-location.test.ts` - Memory location for testing\n- `test/nested-route.test.tsx` - Nested routing tests\n- `test/ssr.test.tsx` - Server-side rendering tests\n\n## Key Development Principles\n\n### Performance First\n\n- **Bundle size matters**: Every byte counts. Use size-limit CI checks\n- **Stable object references**: Prevent unnecessary re-renders with proper memoization\n- **Minimize dependencies**: Zero external dependencies policy\n\n### Code Style\n\n- **Functional programming**: Prefer hooks over class components\n- **Compact code**: Optimize for bundle size (may sacrifice readability)\n- **React patterns**: Use `React.memo`, `useCallback`, `useMemo` appropriately\n\n### Testing Guidelines\n\n- **Comprehensive coverage**: Test all public APIs\n- **Real-world scenarios**: Include complex use cases (like language switching)\n- **Memory location**: Use for isolated testing\n- **Memoization tests**: Verify stable object references\n\n## Core APIs\n\n### Hooks\n\n- `useLocation()` - Get/set current location\n- `useRoute(pattern)` - Match current location against pattern\n- `useParams()` - Extract route parameters\n- `useSearch()` - Get search/query string\n- `useSearchParams()` - Get/set URLSearchParams\n- `useRouter()` - Access router configuration\n\n### Components\n\n- `<Router />` - Optional configuration wrapper\n- `<Route />` - Conditional rendering based on path\n- `<Link />` - Navigation component\n- `<Switch />` - Exclusive routing\n- `<Redirect />` - Programmatic navigation\n\n### Location Hooks\n\n- `useBrowserLocation` - Browser history API\n- `useHashLocation` - Hash-based routing\n- `memoryLocation` - In-memory for testing\n\n## Performance Considerations\n\n### Router Memoization\n\nThe Router component uses sophisticated memoization to prevent unnecessary re-renders:\n\n- Only creates new router objects when props actually change\n- Preserves stable references for `React.memo` components\n- Critical for performance in complex applications\n\n### Bundle Optimization\n\n- Use `// ... existing code ...` pattern in edits to minimize diffs\n- Prefer shorter variable names in production builds\n- Optimize for gzip/brotli compression\n\n## Common Patterns\n\n### Testing Router Components\n\n```javascript\nimport { memoryLocation } from \"wouter/memory-location\";\n\nconst { hook } = memoryLocation({ path: \"/test/path\" });\nrender(\n  <Router hook={hook}>\n    <YourComponent />\n  </Router>\n);\n```\n\n### Custom Location Hooks\n\n```javascript\nconst useCustomLocation = () => {\n  const [location, setLocation] = useBrowserLocation();\n  // Add custom logic here\n  return [location, setLocation];\n};\n```\n\n### Nested Routing\n\n```javascript\n<Route path=\"/app\" nest>\n  <Route path=\"/users/:id\">\n    <UserProfile />\n  </Route>\n</Route>\n```\n\n## Debugging Tips\n\n### Common Issues\n\n1. **Router memoization**: Check if unnecessary re-renders occur\n2. **Base path conflicts**: Verify base path inheritance in nested routers\n3. **Pattern matching**: Use regexparam syntax for route patterns\n4. **SSR hydration**: Ensure client/server router configs match\n\n### Debugging Tools\n\n- React DevTools Profiler for render tracking\n- Network tab for bundle size verification\n- Console warnings for deprecated patterns\n\n## Contributing Guidelines\n\n### Before Making Changes\n\n1. Run full test suite: `npm test -- --run`\n2. Check bundle size impact\n3. Verify TypeScript definitions\n4. Test with both React and Preact\n\n### Adding New Features\n\n1. Consider bundle size impact\n2. Add comprehensive tests\n3. Update TypeScript definitions\n4. Document in README if public API\n5. Maintain backward compatibility\n\n### Performance Changes\n\n1. Add before/after performance tests\n2. Verify memoization behavior\n3. Test with complex nested scenarios\n4. Check for memory leaks in long-running apps\n\n## Architecture Notes\n\n### Router Context System\n\n- Default router created on-demand\n- Context inheritance with override capabilities\n- Memoization prevents cascade re-renders\n- Base path accumulation in nested routers\n\n### Pattern Matching\n\n- Uses regexparam library internally\n- Supports named parameters, wildcards, optional segments\n- Regex patterns supported for complex matching\n- Loose mode for nested routing\n\nThis project prioritizes performance and minimalism while maintaining full feature parity with larger routing solutions.\n"
  },
  {
    "path": ".cursor/rules/publishing.mdc",
    "content": "---\ndescription: \nglobs: \nalwaysApply: true\n---\n# Publishing Wouter Packages\n\nThis document outlines the process for publishing new versions of the wouter packages to npm.\n\n## Prerequisites\n\n- Ensure you have npm publish permissions for both `wouter` and `wouter-preact` packages\n- Have npm authentication set up (you'll need OTP access)\n- All tests should be passing\n- All changes should be committed to the repository\n\n## Publishing Process\n\n### 1. Version Bump\n\nUpdate the version in both package.json files:\n- `packages/wouter/package.json`\n- `packages/wouter-preact/package.json`\n\nFor semantic versioning:\n- **Patch** (x.x.X): Bug fixes, small improvements\n- **Minor** (x.X.x): New features, backward compatible\n- **Major** (X.x.x): Breaking changes\n\n### 2. Dry Run Validation\n\nRun dry publish commands to validate the packages before actual publishing:\n\n```bash\nnpm publish --dry-run -w wouter\nnpm publish --dry-run -w wouter-preact\n```\n\nThis will show you:\n- Package contents and file list\n- Bundle size information\n- Version confirmation\n- Any potential issues\n\n### 3. Build Verification (Optional)\n\nCheck the build artifacts in the `/esm/` folders to ensure the latest updates are properly built:\n- `packages/wouter/esm/`\n- `packages/wouter-preact/esm/`\n\nThe `prepublishOnly` script automatically builds the packages, but it's good to verify.\n\n## ⚠️ Confirmation Required\n\n**ALWAYS ask for user confirmation before proceeding with the following steps:**\n\n### 4. Publish to npm\n\nPublish both packages to the npm registry:\n\n```bash\nnpm publish -w wouter\nnpm publish -w wouter-preact\n```\n\n**Note:** npm might fail with \"EOTP\" (Error One-Time Password) even when the OTP was entered correctly in the browser. This is a known npm issue - the packages are usually still published successfully despite the error message.\n\n### 5. Git Commit and Tag\n\nAfter successful publishing:\n\n1. **Commit the version changes:**\n   ```bash\n   git add packages/wouter/package.json packages/wouter-preact/package.json\n   git commit -m \"Bump version to X.X.X\"\n   ```\n\n2. **Create a version tag:**\n   ```bash\n   git tag vX.X.X\n   ```\n\n3. **Push to remote (optional):**\n   ```bash\n   git push origin main\n   git push origin vX.X.X\n   ```\n\n## Troubleshooting\n\n### Common Issues\n\n- **Authentication errors**: Run `npm login` or use the browser authentication flow\n- **OTP timeout**: Generate a fresh OTP code from your authenticator app\n- **Permission denied**: Ensure you have publish permissions for both packages\n- **Version conflicts**: Check if the version already exists on npm\n\n### Verification\n\nAfter publishing, verify the packages are available:\n- Check [wouter on npm](mdc:https:/www.npmjs.com/package/wouter)\n- Check [wouter-preact on npm](mdc:https:/www.npmjs.com/package/wouter-preact)\n- Test installation: `npm install wouter@latest`\n\n## Package Information\n\n- **wouter**: Main React package (~22KB package size)\n- **wouter-preact**: Preact-specific package (~22KB package size)\n- Both packages maintain version parity and should be published together"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [\"molefrog\"]\n"
  },
  {
    "path": ".github/workflows/ci-tests.yml",
    "content": "name: Run Tests & Linters\n\non:\n  push:\n    branches: \"*\"\n  pull_request:\n    branches: \"*\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    env:\n      FORCE_COLOR: true\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Install Dependencies\n        run: bun install --frozen-lockfile\n\n      - name: Prepare wouter-preact (copy source files)\n        run: cd packages/wouter-preact && npm run prepublishOnly\n\n      - name: Run test\n        run: bun test --coverage --coverage-reporter=lcov --coverage-reporter=text\n\n      - name: Run type check\n        run: bun run test-types\n\n      - name: Lint Sources with ESLint\n        run: bun run lint\n\n      - name: Upload Coverage to Coveralls\n        uses: coverallsapp/github-action@v2\n        with:\n          file: ./coverage/lcov.info\n"
  },
  {
    "path": ".github/workflows/size.yml",
    "content": "name: Size\non: [pull_request]\njobs:\n  size:\n    runs-on: ubuntu-latest\n    env:\n      CI_JOB_NUMBER: 1\n    steps:\n      - uses: actions/checkout@v3\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n      - name: Install Dependencies\n        run: bun install --frozen-lockfile\n      - name: Prepare wouter-preact (copy source files)\n        run: cd packages/wouter-preact && npm run prepublishOnly\n      - name: Symlink npm to bun\n        run: |\n          sudo ln -sf $(which bun) /usr/local/bin/npm\n          sudo ln -sf $(which bunx) /usr/local/bin/npx\n      - uses: andresz1/size-limit-action@v1\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          skip_step: install\n          build_script: build\n"
  },
  {
    "path": ".gitignore",
    "content": "# NPM\nnpm-debug.log*\nnode_modules/\n.npm\n\n# IDEs\n.vscode/\n*.code-workspace\n\n# bundler\nesm/\n.cache\n\n# OSX\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# test coverage\ncoverage/\n\n# type definitions in the project root are copied from types/ folder\n# before publishing, so they should be ignored\n/*.d.ts\n\n# README is copied from the root folder\npackages/wouter/README.md\npackages/wouter-preact/README.md\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "---\ndescription: Use Bun instead of Node.js, npm, pnpm, or vite.\nglobs: \"*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json\"\nalwaysApply: false\n---\n\nDefault to using Bun instead of Node.js.\n\n- Use `bun <file>` instead of `node <file>` or `ts-node <file>`\n- Use `bun test` instead of `jest` or `vitest`\n- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`\n- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`\n- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`\n- Bun automatically loads .env, so don't use dotenv.\n\n## APIs\n\n- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.\n- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.\n- `Bun.redis` for Redis. Don't use `ioredis`.\n- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.\n- `WebSocket` is built-in. Don't use `ws`.\n- Prefer `Bun.file` over `node:fs`'s readFile/writeFile\n- Bun.$`ls` instead of execa.\n\n## Testing\n\nUse `bun test` to run tests.\n\n```ts#index.test.ts\nimport { test, expect } from \"bun:test\";\n\ntest(\"hello world\", () => {\n  expect(1).toBe(1);\n});\n```\n\n## Frontend\n\nUse HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.\n\nServer:\n\n```ts#index.ts\nimport index from \"./index.html\"\n\nBun.serve({\n  routes: {\n    \"/\": index,\n    \"/api/users/:id\": {\n      GET: (req) => {\n        return new Response(JSON.stringify({ id: req.params.id }));\n      },\n    },\n  },\n  // optional websocket support\n  websocket: {\n    open: (ws) => {\n      ws.send(\"Hello, world!\");\n    },\n    message: (ws, message) => {\n      ws.send(message);\n    },\n    close: (ws) => {\n      // handle close\n    }\n  },\n  development: {\n    hmr: true,\n    console: true,\n  }\n})\n```\n\nHTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.\n\n```html#index.html\n<html>\n  <body>\n    <h1>Hello, world!</h1>\n    <script type=\"module\" src=\"./frontend.tsx\"></script>\n  </body>\n</html>\n```\n\nWith the following `frontend.tsx`:\n\n```tsx#frontend.tsx\nimport React from \"react\";\n\n// import .css files directly and it works\nimport './index.css';\n\nimport { createRoot } from \"react-dom/client\";\n\nconst root = createRoot(document.body);\n\nexport default function Frontend() {\n  return <h1>Hello, world!</h1>;\n}\n\nroot.render(<Frontend />);\n```\n\nThen, run index.ts\n\n```sh\nbun --hot ./index.ts\n```\n\nFor more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img src=\"assets/logo.svg\" width=\"80\" alt=\"Wouter — a super-tiny React router (logo by Katya Simacheva)\" />\n</div>\n\n<br />\n\n<div align=\"center\">\n  <a href=\"https://npmjs.org/package/wouter\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/wouter.svg?color=black&labelColor=888\" /></a>\n  <a href=\"https://travis-ci.org/molefrog/wouter\"><img alt=\"CI\" src=\"https://img.shields.io/github/actions/workflow/status/molefrog/wouter/size.yml?color=black&labelColor=888&label=2.5KB+limit\" /></a>\n  <a href=\"https://coveralls.io/github/molefrog/wouter?branch=v3\"><img alt=\"Coverage\" src=\"https://img.shields.io/coveralls/github/molefrog/wouter/v3.svg?color=black&labelColor=888\" /></a>\n  <a href=\"https://www.npmjs.com/package/wouter\"><img alt=\"Coverage\" src=\"https://img.shields.io/npm/dm/wouter.svg?color=black&labelColor=888\" /></a>\n  <a href=\"https://pr.new/molefrog/wouter\"><img alt=\"Edit in StackBlitz IDE\" src=\"https://img.shields.io/badge/StackBlitz-New%20PR-black?labelColor=888\" /></a>\n</div>\n\n<div align=\"center\">\n  <b>wouter</b> is a tiny router for modern React and Preact apps that relies on Hooks. <br />\n  A router you wanted so bad in your project!<br>\n</div>\n\n## Features\n\n<img src=\"assets/wouter.svg\" align=\"right\" width=\"250\" alt=\"by Katya Simacheva\" />\n\n- Minimum dependencies, only **2.1 KB** gzipped vs 18.7KB\n  [React Router](https://github.com/ReactTraining/react-router).\n- Supports both **React** and **[Preact](https://preactjs.com/)**! Read\n  _[\"Preact support\" section](#preact-support)_ for more details.\n- No top-level `<Router />` component, it is **fully optional**.\n- Mimics [React Router](https://github.com/ReactTraining/react-router)'s best practices by providing\n  familiar **[`Route`](#route-pathpattern-)**, **[`Link`](#link-hrefpath-)**,\n  **[`Switch`](#switch-)** and **[`Redirect`](#redirect-topath-)** components.\n- Has hook-based API for more granular control over routing (like animations):\n  **[`useLocation`](#uselocation-working-with-the-history)**,\n  **[`useRoute`](#useroute-route-matching-and-parameters)** and\n  **[`useRouter`](#userouter-accessing-the-router-object)**.\n\n## developers :sparkling_heart: wouter\n\n> ... I love Wouter. It’s tiny, fully embraces hooks, and has an intuitive and barebones API. I can\n> accomplish everything I could with react-router with Wouter, and it just feels **more minimalist\n> while not being inconvenient.**\n>\n> [**Matt Miller**, _An exhaustive React ecosystem for 2020_](https://medium.com/@mmiller42/an-exhaustive-react-guide-for-2020-7859f0bddc56)\n\nWouter provides a simple API that many developers and library authors appreciate. Some notable\nprojects that use wouter: **[Ultra](https://ultrajs.dev/)**,\n**[React-three-fiber](https://github.com/react-spring/react-three-fiber)**,\n**[Sunmao UI](https://sunmao-ui.com/)**, **[Million](https://million.dev/)** and many more.\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n  - [Browser Support](#browser-support)\n- [Wouter API](#wouter-api)\n  - [The list of methods available](#the-list-of-methods-available)\n- [Hooks API](#hooks-api)\n  - [`useRoute`: route matching and parameters](#useroute-route-matching-and-parameters)\n  - [`useLocation`: working with the history](#uselocation-working-with-the-history)\n    - [Additional navigation parameters](#additional-navigation-parameters)\n    - [Customizing the location hook](#customizing-the-location-hook)\n  - [`useParams`: extracting matched parameters](#useparams-extracting-matched-parameters)\n  - [`useSearch`: query strings](#usesearch-query-strings)\n  - [`useSearchParams`: search parameters](#usesearchparams-search-parameters)\n  - [`useRouter`: accessing the router object](#userouter-accessing-the-router-object)\n- [Component API](#component-api)\n\n  - [`<Route path={pattern} />`](#route-pathpattern-)\n    - [Route nesting](#route-nesting)\n  - [`<Link href={path} />`](#link-hrefpath-)\n  - [`<Switch />`](#switch-)\n  - [`<Redirect to={path} />`](#redirect-topath-)\n  - [`<Router hook={hook} parser={fn} base={basepath} />`](#router-hookhook-parserfn-basebasepath-hrefsfn-)\n\n- [FAQ and Code Recipes](#faq-and-code-recipes)\n  - [I deploy my app to the subfolder. Can I specify a base path?](#i-deploy-my-app-to-the-subfolder-can-i-specify-a-base-path)\n  - [How do I make a default route?](#how-do-i-make-a-default-route)\n  - [How do I make a link active for the current route?](#how-do-i-make-a-link-active-for-the-current-route)\n  - [Are strict routes supported?](#are-strict-routes-supported)\n  - [Are relative routes and links supported?](#are-relative-routes-and-links-supported)\n  - [Can I initiate navigation from outside a component?](#can-i-initiate-navigation-from-outside-a-component)\n  - [Can I use _wouter_ in my TypeScript project?](#can-i-use-wouter-in-my-typescript-project)\n  - [How can add animated route transitions?](#how-can-add-animated-route-transitions)\n  - [How do I add view transitions to my app?](#how-do-i-add-view-transitions-to-my-app)\n  - [Preact support?](#preact-support)\n  - [Server-side Rendering support (SSR)?](#server-side-rendering-support-ssr)\n  - [How do I configure the router to render a specific route in tests?](#how-do-i-configure-the-router-to-render-a-specific-route-in-tests)\n  - [1KB is too much, I can't afford it!](#1kb-is-too-much-i-cant-afford-it)\n- [Acknowledgements](#acknowledgements)\n\n## Getting Started\n\nFirst, add wouter to your project.\n\n```bash\nnpm i wouter\n```\n\nOr, if you're using Preact the use the following command [`npm i wouter-preact`](#preact-support).\n\nCheck out this simple demo app below. It doesn't cover hooks and other features such as nested routing, but it's a good starting point for those who are migrating from React Router.\n\n```js\nimport { Link, Route, Switch } from \"wouter\";\n\nconst App = () => (\n  <>\n    <Link href=\"/users/1\">Profile</Link>\n\n    <Route path=\"/about\">About Us</Route>\n\n    {/* \n      Routes below are matched exclusively -\n      the first matched route gets rendered\n    */}\n    <Switch>\n      <Route path=\"/inbox\" component={InboxPage} />\n\n      <Route path=\"/users/:name\">\n        {(params) => <>Hello, {params.name}!</>}\n      </Route>\n\n      {/* Default route in a switch */}\n      <Route>404: No such page!</Route>\n    </Switch>\n  </>\n);\n```\n\n### Browser Support\n\nThis library is designed for **ES2020+** compatibility. If you need to support older browsers, make sure that you transpile `node_modules`. Additionally, the minimum supported TypeScript version is 4.1 in order to support route parameter inference.\n\n## Wouter API\n\nWouter comes with three kinds of APIs: low-level **standalone location hooks**, hooks for **routing and pattern matching** and more traditional **component-based\nAPI** similar to React Router's one.\n\nYou are free to choose whatever works for you: use location hooks when you want to keep your app as small as\npossible and don't need pattern matching; use routing hooks when you want to build custom routing components; or if you're building a traditional app\nwith pages and navigation — components might come in handy.\n\nCheck out also [FAQ and Code Recipes](#faq-and-code-recipes) for more advanced things like active\nlinks, default routes, server-side rendering etc.\n\n### The list of methods available\n\n**Location Hooks**\n\nThese can be used separately from the main module and have an interface similar to `useState`. These hooks are standalone and don't include built-in support for nesting, base path, or route matching. However, when passed to `<Router>`, they work seamlessly with all Router features including nesting and base paths.\n\n- **[`import { useBrowserLocation } from \"wouter/use-browser-location\"`](https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-browser-location.js)** —\n  allows to manipulate current location in the browser's address bar, a tiny wrapper around the History API.\n- **[`import { useHashLocation } from \"wouter/use-hash-location\"`](https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-hash-location.js)** — similarly, gets location from the hash part of the address, i.e. the string after a `#`.\n- **[`import { memoryLocation } from \"wouter/memory-location\"`](#uselocation-working-with-the-history)** — an in-memory location hook with history support, external navigation and immutable mode for testing. **Note** the module name because it is a high-order hook. See how memory location can be used in [testing](#how-do-i-configure-the-router-to-render-a-specific-route-in-tests).\n\n**Routing Hooks**\n\nImport from `wouter` module.\n\n- **[`useRoute`](#useroute-the-power-of-hooks)** — shows whether or not current page matches the\n  pattern provided.\n- **[`useLocation`](#uselocation-working-with-the-history)** — allows to manipulate current\n  router's location, by default subscribes to browser location. **Note:** this isn't the same as `useBrowserLocation`, read below.\n- **[`useParams`](#useparams-extracting-matched-parameters)** — returns an object with parameters matched from the closest route.\n- **[`useSearch`](#usesearch-query-strings)** — returns a search string – everything that goes after the `?`.\n- **[`useRouter`](#userouter-accessing-the-router-object)** — returns a global router object that\n  holds the configuration. Only use it if you want to customize the routing.\n\n**Components**\n\nImport from `wouter` module.\n\n- **[`<Route />`](#route-pathpattern-)** — conditionally renders a component based on a pattern.\n- **[`<Link />`](#link-hrefpath-)** — wraps `<a>`, allows to perform a navigation.\n- **[`<Switch />`](#switch-)** — exclusive routing, only renders the first matched route.\n- **[`<Redirect />`](#redirect-topath-)** — when rendered, performs an immediate navigation.\n- **[`<Router />`](#router-hookhook-matchermatchfn-basebasepath-)** — an optional top-level\n  component for advanced routing configuration.\n\n## Hooks API\n\n### `useRoute`: route matching and parameters\n\nChecks if the current location matches the pattern provided and returns an object with parameters. This is powered by a wonderful [`regexparam`](https://github.com/lukeed/regexparam) library, so all its pattern syntax is fully supported.\n\nYou can use `useRoute` to perform manual routing or implement custom logic, such as route transitions, etc.\n\n```js\nimport { useRoute } from \"wouter\";\n\nconst Users = () => {\n  // `match` is a boolean\n  const [match, params] = useRoute(\"/users/:name\");\n\n  if (match) {\n    return <>Hello, {params.name}!</>;\n  } else {\n    return null;\n  }\n};\n```\n\nA quick cheatsheet of what types of segments are supported:\n\n```js\nuseRoute(\"/app/:page\");\nuseRoute(\"/app/:page/:section\");\n\n// optional parameter, matches \"/en/home\" and \"/home\"\nuseRoute(\"/:locale?/home\");\n\n// suffixes\nuseRoute(\"/movies/:title.(mp4|mov)\");\n\n// wildcards, matches \"/app\", \"/app-1\", \"/app/home\"\nuseRoute(\"/app*\");\n\n// optional wildcards, matches \"/orders\", \"/orders/\"\n// and \"/orders/completed/list\"\nuseRoute(\"/orders/*?\");\n\n// regex for matching complex patterns,\n// matches \"/hello:123\"\nuseRoute(/^[/]([a-z]+):([0-9]+)[/]?$/);\n// and with named capture groups\nuseRoute(/^[/](?<word>[a-z]+):(?<num>[0-9]+)[/]?$/);\n```\n\nThe second item in the pair `params` is an object with parameters or null if there was no match. For wildcard segments the parameter name is `\"*\"`:\n\n```js\n// wildcards, matches \"/app\", \"/app-1\", \"/app/home\"\nconst [match, params] = useRoute(\"/app*\");\n\nif (match) {\n  // \"/home\" for \"/app/home\"\n  const page = params[\"*\"];\n}\n```\n\n### `useLocation`: working with the history\n\nTo get the current path and navigate between pages, call the `useLocation` hook. Similarly to `useState`, it returns a value and a setter: the component will re-render when the location changes and by calling `navigate` you can update this value and perform navigation.\n\nBy default, it uses `useBrowserLocation` under the hood, though you can configure this in a top-level `Router` component (for example, if you decide at some point to switch to a hash-based routing). `useLocation` will also return scoped path when used within nested routes or with base path setting.\n\n```js\nimport { useLocation } from \"wouter\";\n\nconst CurrentLocation = () => {\n  const [location, navigate] = useLocation();\n\n  return (\n    <div>\n      {`The current page is: ${location}`}\n      <a onClick={() => navigate(\"/somewhere\")}>Click to update</a>\n    </div>\n  );\n};\n```\n\nAll the components internally call the `useLocation` hook.\n\n#### Additional navigation parameters\n\nThe setter method of `useLocation` can also accept an optional object with parameters to control how\nthe navigation update will happen.\n\nWhen browser location is used (default), `useLocation` hook accepts `replace` flag to tell the hook to modify the current\nhistory entry instead of adding a new one. It is the same as calling `replaceState`.\n\n```jsx\nconst [location, navigate] = useLocation();\n\nnavigate(\"/jobs\"); // `pushState` is used\nnavigate(\"/home\", { replace: true }); // `replaceState` is used\n```\n\nAdditionally, you can provide a `state` option to update `history.state` while navigating:\n\n```jsx\nnavigate(\"/home\", { state: { modal: \"promo\" } });\n\nhistory.state; // { modal: \"promo\" }\n```\n\n#### Customizing the location hook\n\nBy default, **wouter** uses `useLocation` hook that reacts to `pushState` and `replaceState`\nnavigation via `useBrowserLocation`.\n\nTo customize this, wrap your app in a `Router` component:\n\n```js\nimport { Router, Route } from \"wouter\";\nimport { useHashLocation } from \"wouter/use-hash-location\";\n\nconst App = () => (\n  <Router hook={useHashLocation}>\n    <Route path=\"/about\" component={About} />\n    ...\n  </Router>\n);\n```\n\nBecause these hooks have return values similar to `useState`, it is easy and fun to build your own location hooks: `useCrossTabLocation`, `useLocalStorage`, `useMicroFrontendLocation` and whatever routing logic you want to support in the app. Give it a try!\n\n### `useParams`: extracting matched parameters\n\nThis hook allows you to access the parameters exposed through [matching dynamic segments](#matching-dynamic-segments). Internally, we simply wrap your components in a context provider allowing you to access this data anywhere within the `Route` component.\n\nThis allows you to avoid \"prop drilling\" when dealing with deeply nested components within the route. **Note:** `useParams` will only extract parameters from the closest parent route.\n\n```js\nimport { Route, useParams } from \"wouter\";\n\nconst User = () => {\n  const params = useParams();\n\n  params.id; // \"1\"\n\n  // alternatively, use the index to access the prop\n  params[0]; // \"1\"\n};\n\n<Route path=\"/user/:id\" component={User}> />\n```\n\nIt is the same for regex paths. Capture groups can be accessed by their index, or if there is a named capture group, that can be used instead.\n\n```js\nimport { Route, useParams } from \"wouter\";\n\nconst User = () => {\n  const params = useParams();\n\n  params.id; // \"1\"\n  params[0]; // \"1\"\n};\n\n<Route path={/^[/]user[/](?<id>[0-9]+)[/]?$/} component={User}> />\n```\n\n### `useSearch`: query strings\n\nUse this hook to get the current search (query) string value. It will cause your component to re-render only when the string itself and not the full location updates. The search string returned **does not** contain a `?` character.\n\n```jsx\nimport { useSearch } from \"wouter\";\n\n// returns \"tab=settings&id=1\"\nconst searchString = useSearch();\n```\n\nFor the SSR, use `ssrSearch` prop passed to the router.\n\n```jsx\n<Router ssrSearch={request.search}>{/* SSR! */}</Router>\n```\n\nRefer to [Server-Side Rendering](#server-side-rendering-support-ssr) for more info on rendering and hydration.\n\n### `useSearchParams`: search parameters\n\nReturns a `URLSearchParams` object and a setter function to update search parameters. The setter accepts either a value (object, URLSearchParams, string[][], etc.) or a **callback function** that receives the current params and must return the new params.\n\n```jsx\nimport { useSearchParams } from 'wouter';\n\nconst [searchParams, setSearchParams] = useSearchParams();\n\n// extract a specific search parameter\nconst id = searchParams.get('id');\n\n// modify a specific search parameter\nsetSearchParams((prev) => {\n  prev.set('tab', 'settings');\n  return prev;\n});\n\n// override all search parameters\nsetSearchParams({\n  id: 1234,\n  tab: 'settings',\n});\n\n// by default, setSearchParams() will push a new history entry\n// to avoid this, set `replace` option to `true`\nsetSearchParams(\n  (prev) => {\n    prev.set('order', 'desc');\n    return prev;\n  },\n  {\n    replace: true,\n  },\n);\n\n// you can also pass a history state in options\nsetSearchParams(\n  (prev) => {\n    prev.set('foo', 'bar');\n    return prev;\n  },\n  {\n    state: 'hello',\n  },\n);\n```\n\n### `useRouter`: accessing the router object\n\nIf you're building advanced integration, for example custom location hook, you might want to get\naccess to the global router object. Router is a simple object that holds routing options that you configure in the `Router` component.\n\n```js\nimport { useRouter } from \"wouter\";\n\nconst Custom = () => {\n  const router = useRouter();\n\n  router.hook; // `useBrowserLocation` by default\n  router.base; // \"/app\"\n};\n\nconst App = () => (\n  <Router base=\"/app\">\n    <Custom />\n  </Router>\n);\n```\n\n## Component API\n\n### `<Route path={pattern} />`\n\n`Route` represents a piece of the app that is rendered conditionally based on a pattern `path`. Pattern has the same syntax as the argument you pass to [`useRoute`](#useroute-route-matching-and-parameters).\n\nThe library provides multiple ways to declare a route's body:\n\n```js\nimport { Route } from \"wouter\";\n\n// simple form\n<Route path=\"/home\"><Home /></Route>\n\n// render-prop style\n<Route path=\"/users/:id\">\n  {params => <UserPage id={params.id} />}\n</Route>\n\n// the `params` prop will be passed down to <Orders />\n<Route path=\"/orders/:status\" component={Orders} />\n```\n\nA route with no path is considered to always match, and it is the same as `<Route path=\"*\" />`. When developing your app, use this trick to peek at the route's content without navigation.\n\n```diff\n-<Route path=\"/some/page\">\n+<Route>\n  {/* Strip out the `path` to make this visible */}\n</Route>\n```\n\n#### Route Nesting\n\nNesting is a core feature of wouter and can be enabled on a route via the `nest` prop. When this prop is present, the route matches everything that starts with a given pattern and it creates a nested routing context. All child routes will receive location relative to that pattern.\n\nLet's take a look at this example:\n\n```js\n<Route path=\"/app\" nest>\n  <Route path=\"/users/:id\" nest>\n    <Route path=\"/orders\" />\n  </Route>\n</Route>\n```\n\n1. This first route will be active for all paths that start with `/app`, this is equivalent to having a base path in your app.\n\n2. The second one uses dynamic pattern to match paths like `/app/user/1`, `/app/user/1/anything` and so on.\n\n3. Finally, the inner-most route will only work for paths that look like `/app/users/1/orders`. The match is strict, since that route does not have a `nest` prop and it works as usual.\n\nIf you call `useLocation()` inside the last route, it will return `/orders` and not `/app/users/1/orders`. This creates a nice isolation and it makes it easier to make changes to parent route without worrying that the rest of the app will stop working. If you need to navigate to a top-level page however, you can use a prefix `~` to refer to an absolute path:\n\n```js\n<Route path=\"/payments\" nest>\n  <Route path=\"/all\">\n    <Link to=\"~/home\">Back to Home</Link>\n  </Route>\n</Route>\n```\n\n**Note:** The `nest` prop does not alter the regex passed into regex paths.\nInstead, the `nest` prop will only determine if nested routes will match against the rest of path or the same path.\nTo make a strict path regex, use a regex pattern like `/^[/](your pattern)[/]?$/` (this matches an optional end slash and the end of the string).\nTo make a nestable regex, use a regex pattern like `/^[/](your pattern)(?=$|[/])/` (this matches either the end of the string or a slash for future segments).\n\n### `<Link href={path} />`\n\nLink component renders an `<a />` element that, when clicked, performs a navigation.\n\n```js\nimport { Link } from \"wouter\"\n\n<Link href=\"/\">Home</Link>\n\n// `to` is an alias for `href`\n<Link to=\"/\">Home</Link>\n\n// all standard `a` props are proxied\n<Link href=\"/\" className=\"link\" aria-label=\"Go to homepage\">Home</Link>\n\n// all location hook options are supported\n<Link href=\"/\" replace state={{ animate: true }} />\n```\n\nLink will always wrap its children in an `<a />` tag, unless `asChild` prop is provided. Use this when you need to have a custom component that renders an `<a />` under the hood.\n\n```jsx\n// use this instead\n<Link to=\"/\" asChild>\n  <UIKitLink />\n</Link>\n\n// Remember, `UIKitLink` must implement an `onClick` handler\n// in order for navigation to work!\n```\n\nWhen you pass a function as a `className` prop, it will be called with a boolean value indicating whether the link is active for the current route. You can use this to style active links (e.g. for links in navigation menu)\n\n```jsx\n<Link className={(active) => (active ? \"active\" : \"\")}>Nav</Link>\n```\n\nRead more about [active links here](#how-do-i-make-a-link-active-for-the-current-route).\n\n### `<Switch />`\n\nThere are cases when you want to have an exclusive routing: to make sure that only one route is\nrendered at the time, even if the routes have patterns that overlap. That's what `Switch` does: it\nonly renders **the first matching route**.\n\n```js\nimport { Route, Switch } from \"wouter\";\n\n<Switch>\n  <Route path=\"/orders/all\" component={AllOrders} />\n  <Route path=\"/orders/:status\" component={Orders} />\n\n  {/* \n     in wouter, any Route with empty path is considered always active. \n     This can be used to achieve \"default\" route behaviour within Switch. \n     Note: the order matters! See examples below.\n  */}\n  <Route>This is rendered when nothing above has matched</Route>\n</Switch>;\n```\n\nWhen no route in switch matches, the last empty `Route` will be used as a fallback. See [**FAQ and Code Recipes** section](#how-do-i-make-a-default-route) to read about default routes.\n\n### `<Redirect to={path} />`\n\nWhen mounted performs a redirect to a `path` provided. Uses `useLocation` hook internally to trigger\nthe navigation inside of a `useEffect` block.\n\n`Redirect` can also accept props for [customizing how navigation will be performed](#additional-navigation-parameters), for example for setting history state when navigating. These options are specific to the currently used location hook.\n\n```jsx\n<Redirect to=\"/\" />\n\n// arbitrary state object\n<Redirect to=\"/\" state={{ modal: true }} />\n\n// use `replaceState`\n<Redirect to=\"/\" replace />\n```\n\nIf you need more advanced logic for navigation, for example, to trigger the redirect inside of an\nevent handler, consider using\n[`useLocation` hook instead](#uselocation-working-with-the-history):\n\n```js\nimport { useLocation } from \"wouter\";\n\nconst [location, setLocation] = useLocation();\n\nfetchOrders().then((orders) => {\n  setOrders(orders);\n  setLocation(\"/app/orders\");\n});\n```\n\n### `<Router hook={hook} parser={fn} base={basepath} hrefs={fn} />`\n\nUnlike _React Router_, routes in wouter **don't have to be wrapped in a top-level component**. An\ninternal router object will be constructed on demand, so you can start writing your app without\npolluting it with a cascade of top-level providers. There are cases however, when the routing\nbehaviour needs to be customized.\n\nThese cases include hash-based routing, basepath support, custom matcher function etc.\n\n```jsx\nimport { useHashLocation } from \"wouter/use-hash-location\";\n\n<Router hook={useHashLocation} base=\"/app\">\n  {/* Your app goes here */}\n</Router>;\n```\n\nA router is a simple object that holds the routing configuration options. You can always obtain this\nobject using a [`useRouter` hook](#userouter-accessing-the-router-object). The list of currently\navailable options:\n\n- **`hook: () => [location: string, setLocation: fn]`** — is a React Hook function that subscribes\n  to location changes. It returns a pair of current `location` string e.g. `/app/users` and a\n  `setLocation` function for navigation. You can use this hook from any component of your app by\n  calling [`useLocation()` hook](#uselocation-working-with-the-history). See [Customizing the location hook](#customizing-the-location-hook).\n\n- **`searchHook: () => [search: string, setSearch: fn]`** — similar to `hook`, but for obtaining the [current search string](#usesearch-query-strings).\n\n- **`base: string`** — an optional setting that allows to specify a base path, such as `/app`. All\n  application routes will be relative to that path. To navigate out to an absolute path, prefix your path with an `~`. [See the FAQ](#are-relative-routes-and-links-supported).\n\n- **`parser: (path: string, loose?: boolean) => { pattern, keys }`** — a pattern parsing\n  function. Produces a RegExp for matching the current location against the user-defined patterns like\n  `/app/users/:id`. Has the same interface as the [`parse`](https://github.com/lukeed/regexparam?tab=readme-ov-file#regexparamparseinput-regexp) function from `regexparam`. See [this example](#are-strict-routes-supported) that demonstrates custom parser feature.\n\n- **`ssrPath: string`** and **`ssrSearch: string`** use these when [rendering your app on the server](#server-side-rendering-support-ssr).\n\n- `hrefs: (href: boolean) => string` — a function for transforming `href` attribute of an `<a />` element rendered by `Link`. It is used to support hash-based routing. By default, `href` attribute is the same as the `href` or `to` prop of a `Link`. A location hook can also define a `hook.hrefs` property, in this case the `href` will be inferred.\n\n- **`aroundNav: (navigate, to, options) => void`** — a handler that wraps all navigation calls. Use this to intercept navigation and perform custom logic before and after the navigation occurs. You can modify navigation parameters, add side effects, or prevent navigation entirely. This is particularly useful for implementing [view transitions](#how-do-i-add-view-transitions-to-my-app). By default, it simply calls `navigate(to, options)`.\n\n  ```js\n  const aroundNav = (navigate, to, options) => {\n    // do something before navigation\n    navigate(to, options); // perform navigation\n    // do something after navigation\n  };\n  ```\n\n## FAQ and Code Recipes\n\n### I deploy my app to the subfolder. Can I specify a base path?\n\nYou can! Wrap your app with `<Router base=\"/app\" />` component and that should do the trick:\n\n```js\nimport { Router, Route, Link } from \"wouter\";\n\nconst App = () => (\n  <Router base=\"/app\">\n    {/* the link's href attribute will be \"/app/users\" */}\n    <Link href=\"/users\">Users</Link>\n\n    <Route path=\"/users\">The current path is /app/users!</Route>\n  </Router>\n);\n```\n\nCalling `useLocation()` within a route in an app with base path will return a path scoped to the base. Meaning that when base is `\"/app\"` and pathname is `\"/app/users\"` the returned string is `\"/users\"`. Accordingly, calling `navigate` will automatically append the base to the path argument for you.\n\nWhen you have multiple nested routers, base paths are inherited and stack up.\n\n```js\n<Router base=\"/app\">\n  <Router base=\"/cms\">\n    <Route path=\"/users\">Path is /app/cms/users!</Route>\n  </Router>\n</Router>\n```\n\n### How do I make a default route?\n\nOne of the common patterns in application routing is having a default route that will be shown as a\nfallback, in case no other route matches (for example, if you need to render 404 message). In\n**wouter** this can easily be done as a combination of `<Switch />` component and a default route:\n\n```js\nimport { Switch, Route } from \"wouter\";\n\n<Switch>\n  <Route path=\"/about\">...</Route>\n  <Route>404, Not Found!</Route>\n</Switch>;\n```\n\n_Note:_ the order of switch children matters, default route should always come last.\n\nIf you want to have access to the matched segment of the path you can use wildcard parameters:\n\n```js\n<Switch>\n  <Route path=\"/users\">...</Route>\n\n  {/* will match anything that starts with /users/, e.g. /users/foo, /users/1/edit etc. */}\n  <Route path=\"/users/*\">...</Route>\n\n  {/* will match everything else */}\n  <Route path=\"*\">\n    {(params) => `404, Sorry the page ${params[\"*\"]} does not exist!`}\n  </Route>\n</Switch>\n```\n\n**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-ts-8q532r)**\n\n### How do I make a link active for the current route?\n\nInstead of a regular `className` string, provide a function to use custom class when this link matches the current route. Note that it will always perform an exact match (i.e. `/users` will not be active for `/users/1`).\n\n```jsx\n<Link className={(active) => (active ? \"active\" : \"\")}>Nav link</Link>\n```\n\nIf you need to control other props, such as `aria-current` or `style`, you can write your own `<Link />` wrapper\nand detect if the path is active by using the `useRoute` hook.\n\n```js\nconst [isActive] = useRoute(props.href);\n\nreturn (\n  <Link {...props} asChild>\n    <a style={isActive ? { color: \"red\" } : {}}>{props.children}</a>\n  </Link>\n);\n```\n\n**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-ts-8q532r?file=/src/ActiveLink.tsx)**\n\n### Are strict routes supported?\n\nIf a trailing slash is important for your app's routing, you could specify a custom parser. Parser is a method that takes a pattern string and returns a RegExp and an array of parsed key. It uses the signature of a [`parse`](https://github.com/lukeed/regexparam?tab=readme-ov-file#regexparamparseinput-regexp) function from `regexparam`.\n\nLet's write a custom parser based on a popular [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) package that does support strict routes option.\n\n```js\nimport { pathToRegexp } from \"path-to-regexp\";\n\n/**\n * Custom parser based on `pathToRegexp` with strict route option\n */\nconst strictParser = (path, loose) => {\n  const keys = [];\n  const pattern = pathToRegexp(path, keys, { strict: true, end: !loose });\n\n  return {\n    pattern,\n    // `pathToRegexp` returns some metadata about the keys,\n    // we want to strip it to just an array of keys\n    keys: keys.map((k) => k.name),\n  };\n};\n\nconst App = () => (\n  <Router parser={strictParser}>\n    <Route path=\"/foo\">...</Route>\n    <Route path=\"/foo/\">...</Route>\n  </Router>\n);\n```\n\n**[▶ Demo Sandbox](https://codesandbox.io/p/sandbox/wouter-v3-strict-routes-w3xdtz)**\n\n### Are relative routes and links supported?\n\nYes! Any route with `nest` prop present creates a nesting context. Keep in mind, that the location inside a nested route will be scoped.\n\n```js\nconst App = () => (\n  <Router base=\"/app\">\n    <Route path=\"/dashboard\" nest>\n      {/* the href is \"/app/dashboard/users\" */}\n      <Link to=\"/users\" />\n\n      <Route path=\"/users\">\n        {/* Here `useLocation()` returns \"/users\"! */}\n      </Route>\n    </Route>\n  </Router>\n);\n```\n\n**[▶ Demo Sandbox](https://codesandbox.io/p/sandbox/wouter-v3-nested-routes-l8p23s)**\n\n### Can I initiate navigation from outside a component?\n\nYes, the `navigate` function is exposed from the `\"wouter/use-browser-location\"` module:\n\n```js\nimport { navigate } from \"wouter/use-browser-location\";\n\nnavigate(\"/\", { replace: true });\n```\n\nIt's the same function that is used internally.\n\n### Can I use _wouter_ in my TypeScript project?\n\nYes! Although the project isn't written in TypeScript, the type definition files are bundled with\nthe package.\n\n### How can add animated route transitions?\n\nLet's take look at how wouter routes can be animated with [`framer-motion`](framer.com/motion).\nAnimating enter transitions is easy, but exit transitions require a bit more work. We'll use the `AnimatePresence` component that will keep the page in the DOM until the exit animation is complete.\n\nUnfortunately, `AnimatePresence` only animates its **direct children**, so this won't work:\n\n```jsx\nimport { motion, AnimatePresence } from \"framer-motion\";\n\nexport const MyComponent = () => (\n  <AnimatePresence>\n    {/* This will not work! `motion.div` is not a direct child */}\n    <Route path=\"/\">\n      <motion.div\n        initial={{ opacity: 0 }}\n        animate={{ opacity: 1 }}\n        exit={{ opacity: 0 }}\n      />\n    </Route>\n  </AnimatePresence>\n);\n```\n\nThe workaround is to match this route manually with `useRoute`:\n\n```jsx\nexport const MyComponent = ({ isVisible }) => {\n  const [isMatch] = useRoute(\"/\");\n\n  return (\n    <AnimatePresence>\n      {isMatch && (\n        <motion.div\n          initial={{ opacity: 0 }}\n          animate={{ opacity: 1 }}\n          exit={{ opacity: 0 }}\n        />\n      )}\n    </AnimatePresence>\n  );\n};\n```\n\nMore complex examples involve using `useRoutes` hook (similar to how React Router does it), but wouter does not ship it out-of-the-box. Please refer to [this issue](https://github.com/molefrog/wouter/issues/414#issuecomment-1954192679) for the workaround.\n\n### How do I use wouter with View Transitions API?\n\nWouter works seamlessly with the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API), but you'll need to manually activate it. This is because view transitions require synchronous DOM rendering and must be wrapped in `flushSync` from `react-dom`. Following wouter's philosophy of staying lightweight and avoiding unnecessary dependencies, view transitions aren't built-in. However, there's a simple escape hatch to enable them: the `aroundNav` prop.\n\n```jsx\nimport { flushSync } from \"react-dom\";\nimport { Router, type AroundNavHandler } from \"wouter\";\n\nconst aroundNav: AroundNavHandler = (navigate, to, options) => {\n  // Check if View Transitions API is supported\n  if (!document.startViewTransition) {\n    navigate(to, options);\n    return;\n  }\n\n  document.startViewTransition(() => {\n    flushSync(() => {\n      navigate(to, options);\n    });\n  });\n};\n\nconst App = () => (\n  <Router aroundNav={aroundNav}>\n    {/* Your routes here */}\n  </Router>\n);\n```\n\nYou can also enable transitions selectively using the `transition` prop, which will be available in the `options` parameter:\n\n```jsx\n// Enable transition for a specific link\n<Link to=\"/about\" transition>About</Link>\n\n// Or programmatically\nconst [location, navigate] = useLocation();\nnavigate(\"/about\", { transition: true });\n\n// Then check for it in your handler\nconst aroundNav: AroundNavHandler = (navigate, to, options) => {\n  if (!document.startViewTransition) {\n    navigate(to, options);\n    return;\n  }\n\n  if (options?.transition) {\n    document.startViewTransition(() => {\n      flushSync(() => {\n        navigate(to, options);\n      });\n    });\n  } else {\n    navigate(to, options);\n  }\n};\n```\n\n### Preact support?\n\nPreact exports are available through a separate package named `wouter-preact` (or within the\n`wouter/preact` namespace, however this method isn't recommended as it requires React as a peer\ndependency):\n\n```diff\n- import { useRoute, Route, Switch } from \"wouter\";\n+ import { useRoute, Route, Switch } from \"wouter-preact\";\n```\n\nYou might need to ensure you have the latest version of\n[Preact X](https://github.com/preactjs/preact/releases/tag/10.0.0-alpha.0) with support for hooks.\n\n**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-preact-0lr3n)**\n\n### Server-side Rendering support (SSR)?\n\nIn order to render your app on the server, you'll need to wrap your app with top-level Router and\nspecify `ssrPath` prop (usually, derived from current request). Optionally, `Router` accepts `ssrSearch` parameter if need to have access to a search string on a server.\n\n```js\nimport { renderToString } from \"react-dom/server\";\nimport { Router } from \"wouter\";\n\nconst handleRequest = (req, res) => {\n  // top-level Router is mandatory in SSR mode\n  // pass an optional context object to handle redirects on the server\n  const ssrContext = {};\n  const prerendered = renderToString(\n    <Router ssrPath={req.path} ssrSearch={req.search} ssrContext={ssrContext}>\n      <App />\n    </Router>\n  );\n\n  if (ssrContext.redirectTo) {\n    // encountered redirect\n    res.redirect(ssrContext.redirectTo);\n  } else {\n    // respond with prerendered html\n  }\n};\n```\n\nTip: wouter can pre-fill `ssrSearch`, if `ssrPath` contains the `?` character. So these are equivalent:\n\n```jsx\n<Router ssrPath=\"/goods?sort=asc\" />;\n\n// is the same as\n<Router ssrPath=\"/goods\" ssrSearch=\"sort=asc\" />;\n```\n\nOn the client, the static markup must be hydrated in order for your app to become interactive. Note\nthat to avoid having hydration warnings, the JSX rendered on the client must match the one used by\nthe server, so the `Router` component must be present.\n\n```js\nimport { hydrateRoot } from \"react-dom/client\";\n\nconst root = hydrateRoot(\n  domNode,\n  // during hydration, `ssrPath` is set to `location.pathname`,\n  // `ssrSearch` set to `location.search` accordingly\n  // so there is no need to explicitly specify them\n  <Router>\n    <App />\n  </Router>\n);\n```\n\n**[▶ Demo](https://github.com/molefrog/wultra)**\n\n### How do I configure the router to render a specific route in tests?\n\nTesting with wouter is no different from testing regular React apps. You often need a way to provide a fixture for the current location to render a specific route. This can be easily done by swapping the normal location hook with `memoryLocation`. It is an initializer function that returns a hook that you can then specify in a top-level `Router`.\n\n```jsx\nimport { render } from \"@testing-library/react\";\nimport { memoryLocation } from \"wouter/memory-location\";\n\nit(\"renders a user page\", () => {\n  // `static` option makes it immutable\n  // even if you call `navigate` somewhere in the app location won't change\n  const { hook, searchHook } = memoryLocation({ path: \"/user/2\", static: true });\n\n  const { container } = render(\n    <Router hook={hook} searchHook={searchHook}>\n      <Route path=\"/user/:id\">{(params) => <>User ID: {params.id}</>}</Route>\n    </Router>\n  );\n\n  expect(container.innerHTML).toBe(\"User ID: 2\");\n});\n```\n\n**Note:** When you pass a `hook` prop to `Router`, it will automatically inherit the `searchHook` from the hook if available (via `hook.searchHook`). This means you don't need to explicitly pass both `hook` and `searchHook` when using `memoryLocation` - just passing `hook` is enough for `useSearch()` to work correctly with query parameters.\n\n```jsx\nit(\"works with query parameters\", () => {\n  const { hook } = memoryLocation({ path: \"/products?sort=price&order=asc\" });\n\n  const { result } = renderHook(() => useSearch(), {\n    wrapper: ({ children }) => <Router hook={hook}>{children}</Router>,\n  });\n\n  expect(result.current).toBe(\"sort=price&order=asc\");\n});\n```\n\nThe hook can be configured to record navigation history. Additionally, it comes with a `navigate` function for external navigation.\n\n```jsx\nit(\"performs a redirect\", () => {\n  const { hook, history, navigate } = memoryLocation({\n    path: \"/\",\n    // will store navigation history in `history`\n    record: true,\n  });\n\n  const { container } = render(\n    <Router hook={hook}>\n      <Switch>\n        <Route path=\"/\">Index</Route>\n        <Route path=\"/orders\">Orders</Route>\n\n        <Route>\n          <Redirect to=\"/orders\" />\n        </Route>\n      </Switch>\n    </Router>\n  );\n\n  expect(history).toStrictEqual([\"/\"]);\n\n  navigate(\"/unknown/route\");\n\n  expect(container.innerHTML).toBe(\"Orders\");\n  expect(history).toStrictEqual([\"/\", \"/unknown/route\", \"/orders\"]);\n});\n```\n\n### 1KB is too much, I can't afford it!\n\nWe've got some great news for you! If you're a minimalist bundle-size nomad and you need a damn\nsimple routing in your app, you can just use bare location hooks. For example, `useBrowserLocation` hook which is only **650 bytes gzipped**\nand manually match the current location with it:\n\n```js\nimport { useBrowserLocation } from \"wouter/use-browser-location\";\n\nconst UsersRoute = () => {\n  const [location] = useBrowserLocation();\n\n  if (location !== \"/users\") return null;\n\n  // render the route\n};\n```\n\nWouter's motto is **\"Minimalist-friendly\"**.\n\n## Contributing\n\n**Architecture principles:**\n\n- All code is written in JavaScript for full control over size optimization\n- TypeScript definitions are maintained separately in `types/` directories\n- `wouter-preact` reuses the same source except for `react-deps.js` (Preact-specific hooks)\n- Type definitions are duplicated between packages (not ideal, but works for now)\n\n**Development:** Tests run directly from source files (no build required). Run `npm run test` for interactive mode or `npm run test -- --run` for a single run. Use `npm run build` to build the distributable package before publishing.\n\n## Acknowledgements\n\nWouter illustrations and logos were made by [Katya Simacheva](https://simachevakatya.com/) and\n[Katya Vakulenko](https://katyavakulenko.com/). Thank you to **[@jeetiss](https://github.com/jeetiss)**\nand all the amazing [contributors](https://github.com/molefrog/wouter/graphs/contributors) for\nhelping with the development.\n"
  },
  {
    "path": "bunfig.toml",
    "content": "[test]\npreload = [\"./packages/wouter/test/setup.ts\"]\ncoverageSkipTestFiles = true\ncoveragePathIgnorePatterns = [\"**/test/**\"]\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"monorepo\",\n  \"private\": true,\n  \"description\": \"A minimalistic routing for React and Preact. Monorepo package.\",\n  \"type\": \"module\",\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"scripts\": {\n    \"fix:p\": \"prettier --write \\\"./**/*.(js|ts){x,}\\\"\",\n    \"test\": \"bun test\",\n    \"test-types\": \"tsc --noEmit\",\n    \"size\": \"size-limit\",\n    \"lint\": \"eslint packages/**/*.js\",\n    \"build\": \":\"\n  },\n  \"author\": \"Alexey Taktarov <molefrog@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/molefrog/wouter.git\"\n  },\n  \"license\": \"ISC\",\n  \"sideEffects\": false,\n  \"prettier\": {\n    \"tabWidth\": 2,\n    \"semi\": true,\n    \"singleQuote\": false,\n    \"printWidth\": 80\n  },\n  \"size-limit\": [\n    {\n      \"path\": \"packages/wouter/src/index.js\",\n      \"limit\": \"2500 B\",\n      \"ignore\": [\n        \"react\",\n        \"use-sync-external-store\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter/src/use-browser-location.js\",\n      \"limit\": \"1000 B\",\n      \"import\": \"{ useBrowserLocation }\",\n      \"ignore\": [\n        \"react\",\n        \"use-sync-external-store\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter/src/memory-location.js\",\n      \"limit\": \"1000 B\",\n      \"ignore\": [\n        \"react\",\n        \"use-sync-external-store\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter/src/use-hash-location.js\",\n      \"limit\": \"1000 B\",\n      \"ignore\": [\n        \"react\",\n        \"use-sync-external-store\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter-preact/src/index.js\",\n      \"limit\": \"2500 B\",\n      \"ignore\": [\n        \"preact\",\n        \"preact/hooks\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter-preact/src/use-browser-location.js\",\n      \"limit\": \"1000 B\",\n      \"import\": \"{ useBrowserLocation }\",\n      \"ignore\": [\n        \"preact\",\n        \"preact/hooks\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter-preact/src/use-hash-location.js\",\n      \"limit\": \"1000 B\",\n      \"ignore\": [\n        \"preact\",\n        \"preact/hooks\"\n      ]\n    },\n    {\n      \"path\": \"packages/wouter-preact/src/memory-location.js\",\n      \"limit\": \"1000 B\",\n      \"ignore\": [\n        \"preact\",\n        \"preact/hooks\"\n      ]\n    }\n  ],\n  \"husky\": {\n    \"hooks\": {\n      \"commit-msg\": \"npm run fix:p\"\n    }\n  },\n  \"eslintConfig\": {\n    \"extends\": \"eslint:recommended\",\n    \"parserOptions\": {\n      \"sourceType\": \"module\",\n      \"ecmaFeatures\": {\n        \"jsx\": true\n      }\n    },\n    \"env\": {\n      \"es2020\": true,\n      \"browser\": true,\n      \"node\": true\n    },\n    \"rules\": {\n      \"no-unused-vars\": [\n        \"error\",\n        {\n          \"varsIgnorePattern\": \"^_\",\n          \"argsIgnorePattern\": \"^_\"\n        }\n      ],\n      \"react-hooks/rules-of-hooks\": \"error\",\n      \"react-hooks/exhaustive-deps\": \"warn\"\n    },\n    \"plugins\": [\n      \"react-hooks\"\n    ],\n    \"ignorePatterns\": [\n      \"types/**\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@happy-dom/global-registrator\": \"^20.0.10\",\n    \"@size-limit/preset-small-lib\": \"^11.2.0\",\n    \"@testing-library/dom\": \"^10.4.0\",\n    \"@testing-library/jest-dom\": \"^6.1.4\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@types/bun\": \"1.3.3\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"copyfiles\": \"^2.4.1\",\n    \"eslint\": \"^7.19.0\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"happy-dom\": \"^20.0.10\",\n    \"husky\": \"^4.3.0\",\n    \"path-to-regexp\": \"^6.2.1\",\n    \"preact\": \"^10.23.2\",\n    \"preact-render-to-string\": \"^6.5.9\",\n    \"prettier\": \"^2.4.1\",\n    \"react\": \"^19\",\n    \"react-dom\": \"^19\",\n    \"size-limit\": \"^11.2.0\",\n    \"typescript\": \"^5.8.0\"\n  }\n}\n"
  },
  {
    "path": "packages/magazin/.gitignore",
    "content": "# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n"
  },
  {
    "path": "packages/magazin/App.tsx",
    "content": "import { Route, Switch, Redirect } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\nimport { HomePage } from \"@/routes/home.tsx\";\nimport { AboutPage } from \"@/routes/about.tsx\";\nimport { NotFoundPage } from \"@/routes/404.tsx\";\nimport { ProductPage } from \"@/routes/products/[slug].tsx\";\nimport { CartPage } from \"@/routes/cart.tsx\";\nimport { WithStatusCode } from \"@/components/with-status-code.tsx\";\nimport { Navbar } from \"@/components/navbar.tsx\";\nimport { Footer } from \"@/components/footer.tsx\";\n\nexport function App() {\n  return (\n    <div className=\"min-h-screen bg-white flex flex-col\">\n      <Helmet titleTemplate=\"%s · Magazin by wouter\" />\n\n      <Navbar />\n\n      <div className=\"min-h-[85vh] pt-12\">\n        <main className=\"max-w-4xl mx-auto px-6 py-28\">\n          <Switch>\n            <Route path=\"/\">\n              <HomePage />\n            </Route>\n\n            <Route path=\"/about\">\n              <AboutPage />\n            </Route>\n\n            <Route path=\"/products/:slug\">\n              {(params) => <ProductPage slug={params.slug} />}\n            </Route>\n\n            <Route path=\"/cart\">\n              <CartPage />\n            </Route>\n\n            <Route path=\"/featured\">\n              <Redirect to=\"/products/hook-keyring-rvst\" />\n            </Route>\n\n            <Route>\n              <WithStatusCode code={404}>\n                <NotFoundPage />\n              </WithStatusCode>\n            </Route>\n          </Switch>\n        </main>\n      </div>\n\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/client.tsx",
    "content": "import { hydrateRoot } from \"react-dom/client\";\nimport { flushSync } from \"react-dom\";\nimport { Router, type AroundNavHandler } from \"wouter\";\nimport { HelmetProvider } from \"@dr.pogodin/react-helmet\";\nimport { App } from \"./App\";\n\n// Enable view transitions for navigation\nconst aroundNav: AroundNavHandler = (navigate, to, options) => {\n  // Feature detection for browsers that don't support View Transitions\n  if (!document.startViewTransition) {\n    navigate(to, options);\n    return;\n  }\n\n  // Only use view transitions if explicitly requested\n  if (options?.transition) {\n    document.startViewTransition(() => {\n      flushSync(() => {\n        navigate(to, options);\n      });\n    });\n  } else {\n    navigate(to, options);\n  }\n};\n\nhydrateRoot(\n  document.body,\n  <HelmetProvider>\n    <Router aroundNav={aroundNav}>\n      <App />\n    </Router>\n  </HelmetProvider>\n);\n"
  },
  {
    "path": "packages/magazin/components/footer.tsx",
    "content": "import { Link } from \"wouter\";\n\nexport function Footer() {\n  return (\n    <div className=\"max-w-4xl w-full px-6 self-center\">\n      <footer className=\"max-w-4xl mx-auto pt-12 pb-20 border-t border-gray-100\">\n        <div className=\"w-full md:flex md:justify-between gap-8\">\n          <div className=\"space-y-3 flex-1\">\n            <div className=\"flex items-center gap-2\">\n              <span className=\"text-base font-medium text-neutral-700\">\n                magazin\n              </span>\n            </div>\n            <p className=\"text-sm text-neutral-400 text-pretty\">\n              A modern e-commerce demo built with wouter and Bun.\n              <br /> No rights reserved.\n            </p>\n          </div>\n\n          <div className=\"pt-6\">\n            <a\n              href=\"https://github.com/molefrog/wouter/tree/v3/packages/magazin\"\n              className=\"inline-flex items-center gap-2 px-3 py-1 border border-neutral-200 rounded-lg hover:bg-neutral-50 transition-colors\"\n            >\n              <i className=\"iconoir-git-fork text-base\" />\n              <span className=\"text-sm font-medium text-gray-900\">\n                View Source\n              </span>\n            </a>\n          </div>\n        </div>\n      </footer>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/components/navbar.tsx",
    "content": "import { Link } from \"wouter\";\n\nfunction Logo() {\n  return <i className=\"iconoir-spark-solid text-2xl text-indigo-500\" />;\n}\n\nfunction NavLink({\n  href,\n  children,\n}: {\n  href: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <Link\n      href={href}\n      transition\n      className={(active) =>\n        `text-sm font-medium ${\n          active ? \"text-gray-900\" : \"text-gray-500 hover:text-gray-900\"\n        }`\n      }\n    >\n      {children}\n    </Link>\n  );\n}\n\nexport function Navbar() {\n  return (\n    <nav className=\"fixed top-0 left-0 right-0 z-50 border-b border-gray-200 bg-white py-1.5\">\n      <div className=\"max-w-4xl mx-auto flex items-center justify-between px-6\">\n        <Link\n          href=\"/\"\n          transition\n          className=\"flex items-center gap-2 hover:bg-neutral-200/50 rounded-md p-1\"\n        >\n          <Logo />\n        </Link>\n\n        <div className=\"flex items-center gap-8\">\n          <NavLink href=\"/\">Home</NavLink>\n          <NavLink href=\"/about\">About</NavLink>\n        </div>\n\n        <Link\n          href=\"/cart\"\n          transition\n          className=\"relative flex items-center hover:bg-neutral-200/50 rounded-md p-1\"\n        >\n          <i className=\"iconoir-cart text-xl\" />\n          <span className=\"absolute -top-1.5 -right-2 flex h-4 w-4 items-center justify-center rounded-full bg-gray-900 text-[10px] font-semibold text-white\">\n            7\n          </span>\n        </Link>\n      </div>\n    </nav>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/components/star-wouter.tsx",
    "content": "import { useState, useEffect } from \"react\";\n\nexport function StarWouter() {\n  const [stars, setStars] = useState<number | null>(null);\n\n  useEffect(() => {\n    fetch(\"https://api.github.com/repos/molefrog/wouter\")\n      .then((res) => res.json())\n      .then((data) => {\n        if (data.stargazers_count) {\n          setStars(data.stargazers_count);\n        }\n      })\n      .catch(() => {\n        // If fetch fails, we just won't show the count\n        setStars(null);\n      });\n  }, []);\n\n  return (\n    <a\n      href=\"https://github.com/molefrog/wouter\"\n      className=\"inline-flex items-center gap-2 px-3 h-9 border border-neutral-200 rounded-xl hover:bg-neutral-50 transition-colors select-none whitespace-nowrap flex-shrink-0\"\n    >\n      <i className=\"iconoir-star-solid text-base text-yellow-500\" />\n      <span className=\"text-sm font-medium\">Star Wouter</span>\n      <div className=\"bg-neutral-200 w-px h-3.5 mx-1 self-center\"></div>\n      <div className=\"flex items-center gap-1.5\">\n        <span className=\"text-sm text-gray-900\">\n          {stars !== null ? stars.toLocaleString() : \"\\u221E\"}\n        </span>\n      </div>\n    </a>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/components/with-status-code.tsx",
    "content": "import { useRouter } from \"wouter\";\n\nexport function WithStatusCode({\n  code,\n  children,\n}: {\n  code: number;\n  children: React.ReactNode;\n}) {\n  const router = useRouter();\n\n  // Set status code on SSR context if available\n  if (router.ssrContext) {\n    router.ssrContext.statusCode = code;\n  }\n\n  return <>{children}</>;\n}\n"
  },
  {
    "path": "packages/magazin/db/products.ts",
    "content": "export interface Product {\n  slug: string;\n  name: string;\n  price: number;\n  brand: string;\n  category: string;\n  image: string;\n  description: string;\n}\n\nexport const products: Product[] = [\n  {\n    image: \"/products/carabiner.webp\",\n    slug: \"hook-keyring-rvst\",\n    brand: \"RVST\",\n    category: \"Accessories\",\n    name: \"Hook Keyring\",\n    price: 65,\n    description:\n      \"Premium carabiner keyring crafted with attention to detail and designed for everyday carry.\",\n  },\n  {\n    image: \"/products/ring.webp\",\n    slug: \"silver-ok-ring\",\n    brand: \"Rick Woens\",\n    category: \"Jewelry\",\n    name: \"Silver OK Ring\",\n    price: 99,\n    description:\n      \"Handcrafted sterling silver ring with a unique OK gesture design.\",\n  },\n  {\n    image: \"/products/navigator-cap.webp\",\n    slug: \"navigator-baseball-cap\",\n    brand: \"Rendr\",\n    category: \"Accessories\",\n    name: \"Navigator Baseball Cap\",\n    price: 179,\n    description:\n      \"Premium baseball cap with embroidered branding and adjustable fit.\",\n  },\n  {\n    image: \"/products/sizelimited-tshirt.webp\",\n    slug: \"size-limited-tshirt\",\n    brand: \"Rendr\",\n    category: \"Clothing\",\n    name: \"Size Limited T-Shirt\",\n    price: 65,\n    description:\n      \"Comfortable cotton t-shirt with minimalist branding and premium fit.\",\n  },\n  {\n    image: \"/products/wouter-glasses.webp\",\n    slug: \"wouter-cult-glasses\",\n    brand: \"Wouter\",\n    category: \"Accessories\",\n    name: \"Wouter Glasses\",\n    price: 129,\n    description:\n      \"Cult glasses worn by wouter. Minimalist design with premium frames and crystal-clear lenses.\",\n  },\n  {\n    image: \"/products/parka.webp\",\n    slug: \"route-breaker-windbreaker\",\n    brand: \"Wouter\",\n    category: \"Clothing\",\n    name: \"Route Breaker Windbreaker\",\n    price: 249,\n    description:\n      \"Navigate any weather with the Route Breaker. Lightweight, water-resistant, and built for those who hook into adventure.\",\n  },\n  {\n    image: \"/products/react-pendant.webp\",\n    slug: \"react-state-pendant\",\n    brand: \"Wouter\",\n    category: \"Jewelry\",\n    name: \"React State Pendant\",\n    price: 159,\n    description:\n      \"A declarative pendant for those who embrace the component lifecycle. Hooks perfectly with any chain.\",\n  },\n  {\n    image: \"/products/scarf.webp\",\n    slug: \"nested-routes-silk-scarf\",\n    brand: \"Wouter\",\n    category: \"Accessories\",\n    name: \"Nested Routes Silk Scarf\",\n    price: 189,\n    description:\n      \"Luxurious silk scarf featuring an intricate wouter pattern. Each layer wraps seamlessly into the next, just like your favorite routes.\",\n  },\n  {\n    image: \"/products/poster-a.webp\",\n    slug: \"keep-routing-poster\",\n    brand: \"Wouter\",\n    category: \"Art\",\n    name: \"Keep Routing Poster\",\n    price: 45,\n    description:\n      \"Minimalist poster with a bold message for developers. Museum-quality print that reminds you to stay on the path.\",\n  },\n];\n\nexport function getProductBySlug(slug: string): Product | undefined {\n  return products.find((p) => p.slug === slug);\n}\n"
  },
  {
    "path": "packages/magazin/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" type=\"image/webp\" href=\"/public/favicon.webp\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css\"\n    />\n    <link rel=\"stylesheet\" href=\"./styles.css\" />\n    <title><!-- injected during SSR --></title>\n    <script src=\"./client.tsx\" type=\"module\"></script>\n  </head>\n  <body></body>\n</html>\n"
  },
  {
    "path": "packages/magazin/index.tsx",
    "content": "import { renderToReadableStream } from \"react-dom/server\";\nimport { Router } from \"wouter\";\nimport {\n  HelmetProvider,\n  type HelmetDataContext,\n} from \"@dr.pogodin/react-helmet\";\nimport { App } from \"./App.tsx\";\nimport tailwind from \"bun-plugin-tailwind\";\n\n// Build the HTML and all its assets before starting the server\nconst isProduction = process.env.NODE_ENV === \"production\";\n\nconst build = await Bun.build({\n  entrypoints: [\"./index.html\"],\n  // No outdir = files are kept in memory, not written to disk\n  minify: isProduction,\n  publicPath: \"/\",\n  sourcemap: \"linked\",\n  plugins: [tailwind],\n  define: {\n    \"process.env.NODE_ENV\": JSON.stringify(\n      process.env.NODE_ENV || \"development\"\n    ),\n  },\n});\n\nif (!build.success) {\n  console.error(\"Build failed:\", build.logs);\n  process.exit(1);\n}\n\n// Create a map of assets by their path for quick lookup\nconst assets = new Map<string, (typeof build.outputs)[number]>();\nlet htmlTemplate: string | null = null;\n\nfor (const output of build.outputs) {\n  // The HTML file will be used as template for SSR\n  if (output.path.endsWith(\".html\")) {\n    htmlTemplate = await output.text();\n  } else {\n    // Store other assets (JS, CSS, etc.) by their basename\n    const basename = \"/\" + output.path.split(\"/\").pop()!;\n    assets.set(basename, output);\n  }\n}\n\nif (!htmlTemplate) {\n  console.error(\"No HTML template found in build outputs\");\n  process.exit(1);\n}\n\nconst port = process.env.PORT ? parseInt(process.env.PORT) : 3002;\n\nBun.serve({\n  port,\n  async fetch(req) {\n    const url = new URL(req.url);\n\n    // Check if this is a request for a built asset\n    const asset = assets.get(url.pathname);\n    if (asset) {\n      const headers = isProduction\n        ? {\n            // Built assets have content hashes, so they can be cached indefinitely\n            \"Cache-Control\": \"public, max-age=31536000, immutable\",\n          }\n        : {};\n\n      return new Response(asset, { headers });\n    }\n\n    // Check if this is a request for a static file from public/\n    const publicFile = Bun.file(`./public${url.pathname}`);\n    if (await publicFile.exists()) {\n      const headers = new Headers();\n\n      // Add 24h caching for static assets in production\n      if (isProduction) {\n        headers.set(\"Cache-Control\", \"public, max-age=86400\");\n      }\n\n      return new Response(publicFile, { headers });\n    }\n\n    // Otherwise, it's a page request - render with SSR\n    // ssrPath accepts full path with search, e.g. \"/foo?bar=1\"\n    // ssrContext is used to handle redirects and status codes on the server\n    const ssrContext: { redirectTo?: string; statusCode?: number } = {};\n    const helmetContext: HelmetDataContext = {};\n\n    const stream = await renderToReadableStream(\n      <HelmetProvider context={helmetContext}>\n        <Router ssrPath={url.pathname + url.search} ssrContext={ssrContext}>\n          <App />\n        </Router>\n      </HelmetProvider>\n    );\n\n    // Check if a redirect occurred during SSR\n    if (ssrContext.redirectTo) {\n      return Response.redirect(\n        new URL(ssrContext.redirectTo, url.origin).toString(),\n        302\n      );\n    }\n\n    // Get status code from context, default to 200\n    const statusCode = ssrContext.statusCode || 200;\n\n    // Convert stream to string\n    const appHtml = await new Response(stream).text();\n\n    const helmet = helmetContext.helmet;\n\n    // Use HTMLRewriter to inject the SSR content into body and title\n    const rewriter = new HTMLRewriter()\n      .on(\"body\", {\n        element(element) {\n          element.setInnerContent(appHtml, { html: true });\n        },\n      })\n      .on(\"title\", {\n        element(element) {\n          if (!helmet) return;\n          // Remove the existing title tag and let helmet's title be appended to head\n          element.remove();\n        },\n      })\n      .on(\"head\", {\n        element(element) {\n          if (!helmet) return;\n\n          const headContent = [\n            helmet.title?.toString(),\n            helmet.priority?.toString(),\n            helmet.meta?.toString(),\n            helmet.link?.toString(),\n            helmet.script?.toString(),\n          ]\n            .filter(Boolean)\n            .join(\"\\n\");\n\n          if (headContent) {\n            element.append(headContent, { html: true });\n          }\n        },\n      });\n\n    const transformedResponse = rewriter.transform(new Response(htmlTemplate));\n\n    return new Response(transformedResponse.body, {\n      status: statusCode,\n      headers: { \"Content-Type\": \"text/html\" },\n    });\n  },\n});\n\nconsole.log(`Server running at http://localhost:${port}`);\n"
  },
  {
    "path": "packages/magazin/package.json",
    "content": "{\n  \"name\": \"magazin\",\n  \"module\": \"index.ts\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"bun --watch index.tsx\",\n    \"start\": \"bun index.tsx\",\n    \"prod\": \"NODE_ENV=production bun index.tsx\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"latest\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \"^5\"\n  },\n  \"dependencies\": {\n    \"@dr.pogodin/react-helmet\": \"^3.0.4\",\n    \"bun-plugin-tailwind\": \"^0.1.2\",\n    \"tailwindcss\": \"^4.1.17\",\n    \"wouter\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/magazin/routes/404.tsx",
    "content": "import { Link } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\n\nexport function NotFoundPage() {\n  return (\n    <div className=\"pt-12\">\n      <Helmet>\n        <title>Page Not Found</title>\n      </Helmet>\n      <div className=\"flex flex-col items-center justify-center  border border-neutral-300/75 rounded-lg p-4 w-9 h-9 font-medium text-neutral-400 mx-auto text-xs mb-6\">\n        404\n      </div>\n\n      <h1 className=\"text-3xl font-medium tracking-tight text-center mb-4\">\n        Not Found\n      </h1>\n      <p className=\"text-neutral-400 text-center max-w-md mx-auto\">\n        We are sorry, but the page you're looking for doesn't exist. Try going\n        back to the{\" \"}\n        <Link href=\"/\" className=\"underline hover:text-neutral-900\">\n          home page\n        </Link>\n        .\n      </p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/routes/about.tsx",
    "content": "import { Link } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\n\nfunction Feature({ children }: { children: React.ReactNode }) {\n  return (\n    <li className=\"text-neutral-800 flex items-center gap-3\">\n      <i className=\"iconoir-cable-tag-solid text-blue-500 text-xl\" />\n      {children}\n    </li>\n  );\n}\n\nexport function AboutPage() {\n  return (\n    <>\n      <Helmet>\n        <title>About</title>\n      </Helmet>\n\n      <h1 className=\"text-2xl font-semibold tracking-tight text-neutral-900 mb-2\">\n        What is this?\n      </h1>\n      <p className=\"text-neutral-500 max-w-lg mb-16\">\n        This is a simple SSR demo showcasing wouter v3.9.0, React 19 with\n        server-side rendering and client-side hydration running on Bun.\n      </p>\n\n      <div className=\"text-sm text-neutral-400 mb-2\">Features</div>\n      <ul className=\"mt-4 list-inside list-nonespace-y-1 grid grid-cols-1 sm:grid-cols-2 auto-rows-fr gap-3.5\">\n        <Feature>\n          <Link href=\"/products/hook-keyring-rvst\" className=\"hover:underline\">\n            Dynamic segments\n          </Link>\n        </Feature>\n        <Feature>\n          <Link href=\"/this-page-does-not-exist\" className=\"hover:underline\">\n            Default switch route (404)\n          </Link>\n        </Feature>\n        <Feature>\n          <Link\n            href=\"/?category=accessories&sort=price-desc\"\n            className=\"hover:underline\"\n          >\n            Search parameters\n          </Link>\n        </Feature>\n        <Feature>\n          <Link href=\"/featured\" className=\"hover:underline\">\n            Redirect with SSR support\n          </Link>\n        </Feature>\n        <Feature>\n          <Link href=\"/about\" className=\"hover:underline\">\n            Active links\n          </Link>\n        </Feature>\n        <Feature>\n          <Link\n            href=\"/cart\"\n            state={{ addedItem: \"Demo Product\" }}\n            className=\"hover:underline\"\n          >\n            Navigation with state\n          </Link>\n        </Feature>\n        <Feature>\n          <Link href=\"/this-page-does-not-exist\" className=\"hover:underline\">\n            Custom status codes (404)\n          </Link>\n        </Feature>\n        <Feature>\n          <Link href=\"/\" className=\"hover:underline\" transition>\n            View transitions\n          </Link>\n        </Feature>\n      </ul>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/routes/cart.tsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { useLocation } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\nimport { products, type Product } from \"@/db/products\";\n\nconst cartItems: Array<{ product: Product; quantity: number }> = [\n  { product: products[4]!, quantity: 1 }, // Wouter Glasses\n  { product: products[5]!, quantity: 1 }, // Route Breaker Windbreaker\n  { product: products[0]!, quantity: 2 }, // Hook Keyring\n  { product: products[7]!, quantity: 3 }, // Keep Routing Poster\n];\n\nfunction NotificationBanner({\n  show,\n  message,\n}: {\n  show: boolean;\n  message: string | null;\n}) {\n  if (!message) return null;\n\n  return (\n    <div\n      className={`fixed bottom-10 left-1/2 -translate-x-1/2 bg-white border border-neutral-200 text-neutral-900 px-3.5 py-2 rounded-lg shadow-lg transition-transform duration-300 starting:translate-y-32 ${\n        show ? \"translate-y-0\" : \"translate-y-32\"\n      }`}\n    >\n      <div className=\"flex items-center gap-2\">\n        <i className=\"iconoir-shopping-bag-plus text-lg\" />\n        <span className=\"text-sm font-medium\">{message} added to cart</span>\n      </div>\n    </div>\n  );\n}\n\nexport function CartPage() {\n  const [location, navigate] = useLocation();\n  const [showNotification, setShowNotification] = useState(false);\n  const [addedItem, setAddedItem] = useState<string | null>(null);\n\n  useEffect(() => {\n    const state = history.state as { addedItem?: string } | null;\n    if (state?.addedItem) {\n      setAddedItem(state.addedItem);\n      setShowNotification(true);\n\n      // Clear the state so it doesn't show again on refresh\n      navigate(location, { replace: true, state: null });\n\n      // Hide notification after 3 seconds\n      const timer = setTimeout(() => {\n        setShowNotification(false);\n      }, 3000);\n\n      return () => clearTimeout(timer);\n    }\n  }, [location, navigate]);\n\n  return (\n    <>\n      <Helmet>\n        <title>Cart</title>\n      </Helmet>\n\n      <h1 className=\"text-2xl font-semibold tracking-tight text-neutral-900 mb-6\">\n        Shopping Cart\n      </h1>\n\n      <div className=\"space-y-3\">\n        {cartItems.map((item, index) => (\n          <div\n            key={index}\n            className=\"flex items-start justify-between border-b border-gray-100 pb-4\"\n          >\n            <div className=\"flex gap-4\">\n              <div className=\"w-12 h-12 bg-stone-100 rounded-md shrink-0 p-2\">\n                <img\n                  src={item.product.image}\n                  alt={item.product.name}\n                  className=\"w-full h-full object-cover\"\n                />\n              </div>\n              <div className=\"flex flex-col\">\n                <span className=\"text-neutral-900\">{item.product.name}</span>\n                <span className=\"text-sm text-gray-500 mt-1\">\n                  {item.quantity} × ${item.product.price}\n                </span>\n              </div>\n            </div>\n            <span className=\"text-neutral-900 text-sm\">\n              ${item.product.price}\n            </span>\n          </div>\n        ))}\n      </div>\n\n      <div className=\"text-right mt-4\">\n        <div className=\"text-sm text-right text-neutral-500\">Total</div>\n        <div className=\"text-base font-semibold text-neutral-900\">$643</div>\n      </div>\n\n      <NotificationBanner show={showNotification} message={addedItem} />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/routes/home.tsx",
    "content": "import { useSearchParams, Link } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\nimport { products, type Product } from \"@/db/products\";\nimport { StarWouter } from \"@/components/star-wouter\";\n\nfunction ProductCard({ slug, brand, category, name, price, image }: Product) {\n  return (\n    <Link\n      href={`/products/${slug}`}\n      transition\n      className=\"overflow-hidden group flex flex-col h-full\"\n    >\n      <div\n        className=\"w-full aspect-square p-12 bg-stone-100/75 group-hover:bg-stone-200/75 transition-colors rounded-t-lg\"\n        style={{ viewTransitionName: `product-image-${slug}` }}\n      >\n        <img src={image} alt={name} className=\"object-contain w-full h-full\" />\n      </div>\n      <div className=\"p-4 bg-stone-100/75 rounded-b-lg group-hover:bg-stone-200/75 transition-colors flex-1 flex flex-col justify-between\">\n        <div className=\"text-sm text-neutral-400/75\">\n          {brand} · {category}\n        </div>\n        <div className=\"mt-1 flex items-center justify-between\">\n          <span className=\"font-medium text-sm\">{name}</span>\n          <span className=\"\">${price.toLocaleString()}</span>\n        </div>\n      </div>\n    </Link>\n  );\n}\n\nconst categories = [\n  { value: \"all\", label: \"All\" },\n  { value: \"accessories\", label: \"Accessories\" },\n  { value: \"clothing\", label: \"Clothing\" },\n  { value: \"jewelry\", label: \"Jewelry\" },\n  { value: \"art\", label: \"Art\" },\n];\n\nconst sortOptions = [\n  { value: \"newest\", label: \"Newest\" },\n  { value: \"price-asc\", label: \"Price: Low to High\" },\n  { value: \"price-desc\", label: \"Price: High to Low\" },\n  { value: \"name\", label: \"Name\" },\n];\n\nfunction CategoryFilter({\n  value,\n  onChange,\n}: {\n  value: string;\n  onChange: (value: string) => void;\n}) {\n  return (\n    <div className=\"flex items-center gap-4\">\n      {categories.map((cat) => (\n        <button\n          key={cat.value}\n          onClick={() => onChange(cat.value)}\n          className={`text-sm cursor-pointer ${\n            value === cat.value\n              ? \"text-neutral-900 underline underline-offset-4\"\n              : \"text-neutral-500 hover:text-neutral-900\"\n          }`}\n        >\n          {cat.label}\n        </button>\n      ))}\n    </div>\n  );\n}\n\nfunction SortSelect({\n  value,\n  onChange,\n}: {\n  value: string;\n  onChange: (value: string) => void;\n}) {\n  return (\n    <div className=\"relative flex md:inline-flex items-center cursor-pointer w-full md:w-auto\">\n      <select\n        value={value}\n        onChange={(e) => onChange(e.target.value)}\n        className=\"appearance-none bg-transparent text-sm text-neutral-500 pr-4 cursor-pointer hover:text-neutral-900 focus:outline-none text-left md:text-right w-full md:w-auto\"\n      >\n        {sortOptions.map((opt) => (\n          <option key={opt.value} value={opt.value}>\n            {opt.label}\n          </option>\n        ))}\n      </select>\n      <i className=\"iconoir-nav-arrow-down absolute right-0 text-xs pointer-events-none text-neutral-500 cursor-pointer ml-1\" />\n    </div>\n  );\n}\n\nexport function HomePage() {\n  const [searchParams, setSearchParams] = useSearchParams();\n  const category = searchParams.get(\"category\") || \"all\";\n  const sort = searchParams.get(\"sort\") || \"newest\";\n\n  const handleFilterChange = (key: string, value: string) => {\n    setSearchParams((params) => {\n      const newParams = new URLSearchParams(params);\n      if (value === \"all\" || value === \"newest\") {\n        newParams.delete(key);\n      } else {\n        newParams.set(key, value);\n      }\n      return newParams;\n    });\n  };\n\n  // Filter products by category\n  let filteredProducts = products;\n  if (category !== \"all\") {\n    filteredProducts = products.filter(\n      (p) => p.category.toLowerCase() === category.toLowerCase()\n    );\n  }\n\n  // Sort products\n  const sortedProducts = [...filteredProducts].sort((a, b) => {\n    switch (sort) {\n      case \"price-asc\":\n        return a.price - b.price;\n      case \"price-desc\":\n        return b.price - a.price;\n      case \"name\":\n        return a.name.localeCompare(b.name);\n      case \"newest\":\n      default:\n        return 0; // Keep original order\n    }\n  });\n\n  return (\n    <>\n      <Helmet>\n        <title>Magazin by wouter</title>\n      </Helmet>\n\n      <div className=\"mb-20\">\n        <h1 className=\"text-2xl font-semibold tracking-tight text-neutral-900 mb-2\">\n          Welcome to our shop\n        </h1>\n        <p className=\"text-lg text-neutral-500 mb-4 max-w-2xl text-pretty\">\n          Exclusive merch for hardcore wouter fans. You can't buy these yet, so\n          go star the repo to increase our chances of becoming a billion dollar\n          company.\n        </p>\n        <div className=\"flex items-center gap-3 overflow-x-auto flex-nowrap -mx-6 px-6 md:mx-0 md:px-0\">\n          <button\n            onClick={() =>\n              document\n                .getElementById(\"products\")\n                ?.scrollIntoView({ behavior: \"smooth\" })\n            }\n            className=\"bg-black text-white px-3 text-sm font-medium py-2 rounded-xl hover:bg-neutral-800 transition-colors shadow-sm cursor-pointer whitespace-nowrap flex-shrink-0\"\n          >\n            Start shopping →\n          </button>\n          <StarWouter />\n        </div>\n      </div>\n\n      <div\n        className=\"flex flex-col md:flex-row md:items-center md:justify-between gap-4 py-6 scroll-mt-16\"\n        id=\"products\"\n      >\n        <CategoryFilter\n          value={category}\n          onChange={(v) => handleFilterChange(\"category\", v)}\n        />\n        <SortSelect\n          value={sort}\n          onChange={(v) => handleFilterChange(\"sort\", v)}\n        />\n      </div>\n\n      <div className=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 auto-rows-max gap-2.5\">\n        {sortedProducts.map((product) => (\n          <ProductCard key={product.slug} {...product} />\n        ))}\n      </div>\n\n      {sortedProducts.length === 0 && (\n        <div className=\"text-center py-12 text-neutral-500\">\n          No products found in this category.\n        </div>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/routes/products/[slug].tsx",
    "content": "import { Link } from \"wouter\";\nimport { Helmet } from \"@dr.pogodin/react-helmet\";\nimport { getProductBySlug } from \"@/db/products\";\n\nexport function ProductPage({ slug }: { slug: string }) {\n  const product = getProductBySlug(slug);\n\n  if (!product) {\n    return (\n      <div className=\"text-center py-12\">\n        <Helmet>\n          <title>Product Not Found</title>\n        </Helmet>\n        <h1 className=\"text-2xl font-semibold text-neutral-900 mb-2\">\n          Product not found\n        </h1>\n        <Link href=\"/\" className=\"text-sm text-neutral-500 hover:underline\">\n          Back to home\n        </Link>\n      </div>\n    );\n  }\n\n  return (\n    <>\n      <Helmet>\n        <title>{product.name}</title>\n      </Helmet>\n      <Link\n        href=\"/\"\n        transition\n        className=\" inline-flex items-center gap-2  hover:bg-neutral-100/75 rounded-md p-1.5 hover:text-neutral-900 mb-2\"\n      >\n        <i className=\"iconoir-reply text-base\" />\n      </Link>\n      <div className=\"grid grid-cols-2 md:grid-cols-3 md:gap-12 gap-6\">\n        <div\n          className=\"bg-stone-100/75 rounded-lg aspect-square md:col-span-2 p-12\"\n          style={{ viewTransitionName: `product-image-${product.slug}` }}\n        >\n          <img\n            src={product.image}\n            alt={product.name}\n            className=\"object-contain w-full h-full\"\n          />\n        </div>\n        <div className=\"pt-4\">\n          <div className=\"text-sm text-neutral-400 mb-2\">\n            {product.brand} · {product.category}\n          </div>\n          <h1 className=\"text-xl tracking-tight text-neutral-900 mb-2\">\n            {product.name}\n          </h1>\n          <p className=\"text-neutral-500 text-sm\">{product.description}</p>\n\n          <div className=\"mt-4\">\n            <span className=\"text-sm\">${product.price}</span>\n          </div>\n          <div className=\"mt-8\">\n            <Link\n              href=\"/cart\"\n              state={{ addedItem: product.name }}\n              className=\"bg-black text-white px-3 text-sm font-medium py-2 rounded-xl hover:bg-neutral-800 transition-colors shadow-sm cursor-pointer w-full inline-block text-center\"\n            >\n              Add to Cart\n            </Link>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/magazin/styles.css",
    "content": "@import \"tailwindcss\";\n\n/* View Transitions */\n@view-transition {\n  navigation: auto;\n}\n\n/* Default: simple 0.25s cross-fade */\n::view-transition-old(root),\n::view-transition-new(root) {\n  animation-duration: 0.2s;\n  animation-timing-function: ease;\n}\n"
  },
  {
    "path": "packages/magazin/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    // Environment setup & latest features\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"target\": \"ESNext\",\n    \"module\": \"Preserve\",\n    \"moduleDetection\": \"force\",\n    \"jsx\": \"react-jsx\",\n    \"allowJs\": true,\n\n    // Bundler mode\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n\n    // Path aliases\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    },\n\n    // Best practices\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n\n    // Some stricter flags (disabled by default)\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noPropertyAccessFromIndexSignature\": false\n  }\n}\n"
  },
  {
    "path": "packages/wouter/package.json",
    "content": "{\n  \"name\": \"wouter\",\n  \"version\": \"3.9.0\",\n  \"description\": \"Minimalist-friendly ~1.5KB router for React\",\n  \"type\": \"module\",\n  \"keywords\": [\n    \"react\",\n    \"preact\",\n    \"router\",\n    \"tiny\",\n    \"routing\",\n    \"hooks\",\n    \"useLocation\"\n  ],\n  \"files\": [\n    \"src\",\n    \"types/**/*.d.ts\",\n    \"types/*.d.ts\"\n  ],\n  \"main\": \"src/index.js\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./types/index.d.ts\",\n      \"default\": \"./src/index.js\"\n    },\n    \"./use-browser-location\": {\n      \"types\": \"./types/use-browser-location.d.ts\",\n      \"default\": \"./src/use-browser-location.js\"\n    },\n    \"./use-hash-location\": {\n      \"types\": \"./types/use-hash-location.d.ts\",\n      \"default\": \"./src/use-hash-location.js\"\n    },\n    \"./memory-location\": {\n      \"types\": \"./types/memory-location.d.ts\",\n      \"default\": \"./src/memory-location.js\"\n    }\n  },\n  \"types\": \"types/index.d.ts\",\n  \"typesVersions\": {\n    \">=4.1\": {\n      \"types/index.d.ts\": [\n        \"types/index.d.ts\"\n      ],\n      \"use-browser-location\": [\n        \"types/use-browser-location.d.ts\"\n      ],\n      \"use-hash-location\": [\n        \"types/use-hash-location.d.ts\"\n      ],\n      \"memory-location\": [\n        \"types/memory-location.d.ts\"\n      ]\n    }\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \"cp ../../README.md .\"\n  },\n  \"author\": \"Alexey Taktarov <molefrog@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/molefrog/wouter.git\"\n  },\n  \"license\": \"Unlicense\",\n  \"peerDependencies\": {\n    \"react\": \">=16.8.0\"\n  },\n  \"dependencies\": {\n    \"mitt\": \"^3.0.1\",\n    \"regexparam\": \"^3.0.0\",\n    \"use-sync-external-store\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/wouter/src/index.d.ts",
    "content": "export * from \"../types/index.js\";\n"
  },
  {
    "path": "packages/wouter/src/index.js",
    "content": "import { parse as parsePattern } from \"regexparam\";\n\nimport {\n  useBrowserLocation,\n  useSearch as useBrowserSearch,\n} from \"./use-browser-location.js\";\n\nimport {\n  useRef,\n  useContext,\n  createContext,\n  isValidElement,\n  cloneElement,\n  createElement as h,\n  Fragment,\n  forwardRef,\n  useIsomorphicLayoutEffect,\n  useEvent,\n  useMemo,\n} from \"./react-deps.js\";\nimport { absolutePath, relativePath, sanitizeSearch } from \"./paths.js\";\n\n/*\n * Router and router context. Router is a lightweight object that represents the current\n * routing options: how location is managed, base path etc.\n *\n * There is a default router present for most of the use cases, however it can be overridden\n * via the <Router /> component.\n */\n\nconst defaultRouter = {\n  hook: useBrowserLocation,\n  searchHook: useBrowserSearch,\n  parser: parsePattern,\n  base: \"\",\n  // this option is used to override the current location during SSR\n  ssrPath: undefined,\n  ssrSearch: undefined,\n  // optional context to track render state during SSR\n  ssrContext: undefined,\n  // customizes how `href` props are transformed for <Link />\n  hrefs: (x) => x,\n  // wraps navigate calls, useful for view transitions\n  aroundNav: (n, t, o) => n(t, o),\n};\n\nconst RouterCtx = createContext(defaultRouter);\n\n// gets the closest parent router from the context\nexport const useRouter = () => useContext(RouterCtx);\n\n/**\n * Parameters context. Used by `useParams()` to get the\n * matched params from the innermost `Route` component.\n */\n\nconst Params0 = {},\n  ParamsCtx = createContext(Params0);\n\nexport const useParams = () => useContext(ParamsCtx);\n\n/*\n * Part 1, Hooks API: useRoute and useLocation\n */\n\n// Internal version of useLocation to avoid redundant useRouter calls\n\nconst useLocationFromRouter = (router) => {\n  const [location, navigate] = router.hook(router);\n\n  // the function reference should stay the same between re-renders, so that\n  // it can be passed down as an element prop without any performance concerns.\n  // (This is achieved via `useEvent`.)\n  return [\n    relativePath(router.base, location),\n    useEvent((to, opts) =>\n      router.aroundNav(navigate, absolutePath(to, router.base), opts)\n    ),\n  ];\n};\n\nexport const useLocation = () => useLocationFromRouter(useRouter());\n\nexport const useSearch = () => {\n  const router = useRouter();\n  return sanitizeSearch(router.searchHook(router));\n};\n\nexport const matchRoute = (parser, route, path, loose) => {\n  // if the input is a regexp, skip parsing\n  const { pattern, keys } =\n    route instanceof RegExp\n      ? { keys: false, pattern: route }\n      : parser(route || \"*\", loose);\n\n  // array destructuring loses keys, so this is done in two steps\n  const result = pattern.exec(path) || [];\n\n  // when parser is in \"loose\" mode, `$base` is equal to the\n  // first part of the route that matches the pattern\n  // (e.g. for pattern `/a/:b` and path `/a/1/2/3` the `$base` is `a/1`)\n  // we use this for route nesting\n  const [$base, ...matches] = result;\n\n  return $base !== undefined\n    ? [\n        true,\n\n        (() => {\n          // for regex paths, `keys` will always be false\n\n          // an object with parameters matched, e.g. { foo: \"bar\" } for \"/:foo\"\n          // we \"zip\" two arrays here to construct the object\n          // [\"foo\"], [\"bar\"] → { foo: \"bar\" }\n          const groups =\n            keys !== false\n              ? Object.fromEntries(keys.map((key, i) => [key, matches[i]]))\n              : result.groups;\n\n          // convert the array to an instance of object\n          // this makes it easier to integrate with the existing param implementation\n          let obj = { ...matches };\n\n          // merge named capture groups with matches array\n          groups && Object.assign(obj, groups);\n\n          return obj;\n        })(),\n\n        // the third value if only present when parser is in \"loose\" mode,\n        // so that we can extract the base path for nested routes\n        ...(loose ? [$base] : []),\n      ]\n    : [false, null];\n};\n\nexport const useRoute = (pattern) =>\n  matchRoute(useRouter().parser, pattern, useLocation()[0]);\n\n/*\n * Part 2, Low Carb Router API: Router, Route, Link, Switch\n */\n\nexport const Router = ({ children, ...props }) => {\n  // the router we will inherit from - it is the closest router in the tree,\n  // unless the custom `hook` is provided (in that case it's the default one)\n  const parent_ = useRouter();\n  const parent = props.hook ? defaultRouter : parent_;\n\n  // holds to the context value: the router object\n  let value = parent;\n\n  // when `ssrPath` contains a `?` character, we can extract the search from it.\n  // also, ensure ssrSearch is always defined when ssrPath is provided, so that\n  // useSearch behavior matches usePathname (proper SSR hydration when client\n  // renders <Router> without props after server rendered with ssrPath/ssrSearch)\n  const [path, search = props.ssrSearch ?? \"\"] =\n    props.ssrPath?.split(\"?\") ?? [];\n  if (path) (props.ssrSearch = search), (props.ssrPath = path);\n\n  // hooks can define their own `href` formatter (e.g. for hash location)\n  props.hrefs = props.hrefs ?? props.hook?.hrefs;\n\n  // hooks can define their own search hook (e.g. for memory location)\n  props.searchHook = props.searchHook ?? props.hook?.searchHook;\n\n  // what is happening below: to avoid unnecessary rerenders in child components,\n  // we ensure that the router object reference is stable, unless there are any\n  // changes that require reload (e.g. `base` prop changes -> all components that\n  // get the router from the context should rerender, even if the component is memoized).\n  // the expected behaviour is:\n  //\n  //   1) when the resulted router is no different from the parent, use parent\n  //   2) if the custom `hook` prop is provided, we always inherit from the\n  //      default router instead. this resets all previously overridden options.\n  //   3) when the router is customized here, it should stay stable between renders\n  let ref = useRef({}),\n    prev = ref.current,\n    next = prev;\n\n  for (let k in parent) {\n    const option =\n      k === \"base\"\n        ? /* base is special case, it is appended to the parent's base */\n          parent[k] + (props[k] ?? \"\")\n        : props[k] ?? parent[k];\n\n    if (prev === next && option !== next[k]) {\n      ref.current = next = { ...next };\n    }\n\n    next[k] = option;\n\n    // the new router is no different from the parent or from the memoized value, use parent\n    if (option !== parent[k] || option !== value[k]) value = next;\n  }\n\n  return h(RouterCtx.Provider, { value, children });\n};\n\nconst h_route = ({ children, component }, params) => {\n  // React-Router style `component` prop\n  if (component) return h(component, { params });\n\n  // support render prop or plain children\n  return typeof children === \"function\" ? children(params) : children;\n};\n\n// Cache params object between renders if values are shallow equal\nconst useCachedParams = (value) => {\n  let prev = useRef(Params0);\n  const curr = prev.current;\n  return (prev.current =\n    // Update cache if number of params changed or any value changed\n    Object.keys(value).length !== Object.keys(curr).length ||\n    Object.entries(value).some(([k, v]) => v !== curr[k])\n      ? value // Return new value if there are changes\n      : curr); // Return cached value if nothing changed\n};\n\nexport function useSearchParams() {\n  const [location, navigate] = useLocation();\n\n  const search = useSearch();\n  const searchParams = useMemo(() => new URLSearchParams(search), [search]);\n\n  // cached value before next render, so you can call setSearchParams multiple times\n  let tempSearchParams = searchParams;\n\n  const setSearchParams = useEvent((nextInit, options) => {\n    tempSearchParams = new URLSearchParams(\n      typeof nextInit === \"function\" ? nextInit(tempSearchParams) : nextInit\n    );\n    navigate(location + \"?\" + tempSearchParams, options);\n  });\n\n  return [searchParams, setSearchParams];\n}\n\nexport const Route = ({ path, nest, match, ...renderProps }) => {\n  const router = useRouter();\n  const [location] = useLocationFromRouter(router);\n\n  const [matches, routeParams, base] =\n    // `match` is a special prop to give up control to the parent,\n    // it is used by the `Switch` to avoid double matching\n    match ?? matchRoute(router.parser, path, location, nest);\n\n  // when `routeParams` is `null` (there was no match), the argument\n  // below becomes {...null} = {}, see the Object Spread specs\n  // https://tc39.es/proposal-object-rest-spread/#AbstractOperations-CopyDataProperties\n  const params = useCachedParams({ ...useParams(), ...routeParams });\n\n  if (!matches) return null;\n\n  const children = base\n    ? h(Router, { base }, h_route(renderProps, params))\n    : h_route(renderProps, params);\n\n  return h(ParamsCtx.Provider, { value: params, children });\n};\n\nexport const Link = forwardRef((props, ref) => {\n  const router = useRouter();\n  const [currentPath, navigate] = useLocationFromRouter(router);\n\n  const {\n    to = \"\",\n    href: targetPath = to,\n    onClick: _onClick,\n    asChild,\n    children,\n    className: cls,\n    /* eslint-disable no-unused-vars */\n    replace /* ignore nav props */,\n    state /* ignore nav props */,\n    transition /* ignore nav props */,\n    /* eslint-enable no-unused-vars */\n\n    ...restProps\n  } = props;\n\n  const onClick = useEvent((event) => {\n    // ignores the navigation when clicked using right mouse button or\n    // by holding a special modifier key: ctrl, command, win, alt, shift\n    if (\n      event.ctrlKey ||\n      event.metaKey ||\n      event.altKey ||\n      event.shiftKey ||\n      event.button !== 0\n    )\n      return;\n\n    _onClick?.(event);\n    if (!event.defaultPrevented) {\n      event.preventDefault();\n      navigate(targetPath, props);\n    }\n  });\n\n  // handle nested routers and absolute paths\n  const href = router.hrefs(\n    targetPath[0] === \"~\" ? targetPath.slice(1) : router.base + targetPath,\n    router // pass router as a second argument for convinience\n  );\n\n  return asChild && isValidElement(children)\n    ? cloneElement(children, { onClick, href })\n    : h(\"a\", {\n        ...restProps,\n        onClick,\n        href,\n        // `className` can be a function to apply the class if this link is active\n        className: cls?.call ? cls(currentPath === targetPath) : cls,\n        children,\n        ref,\n      });\n});\n\nconst flattenChildren = (children) =>\n  Array.isArray(children)\n    ? children.flatMap((c) =>\n        flattenChildren(c && c.type === Fragment ? c.props.children : c)\n      )\n    : [children];\n\nexport const Switch = ({ children, location }) => {\n  const router = useRouter();\n  const [originalLocation] = useLocationFromRouter(router);\n\n  for (const element of flattenChildren(children)) {\n    let match = 0;\n\n    if (\n      isValidElement(element) &&\n      // we don't require an element to be of type Route,\n      // but we do require it to contain a truthy `path` prop.\n      // this allows to use different components that wrap Route\n      // inside of a switch, for example <AnimatedRoute />.\n      (match = matchRoute(\n        router.parser,\n        element.props.path,\n        location || originalLocation,\n        element.props.nest\n      ))[0]\n    )\n      return cloneElement(element, { match });\n  }\n\n  return null;\n};\n\nexport const Redirect = (props) => {\n  const { to, href = to } = props;\n  const router = useRouter();\n  const [, navigate] = useLocationFromRouter(router);\n  const redirect = useEvent(() => navigate(to || href, props));\n  const { ssrContext } = router;\n\n  // redirect is guaranteed to be stable since it is returned from useEvent\n  useIsomorphicLayoutEffect(() => {\n    redirect();\n  }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n  if (ssrContext) {\n    ssrContext.redirectTo = to;\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "packages/wouter/src/memory-location.d.ts",
    "content": "export * from \"../types/memory-location.js\";\n"
  },
  {
    "path": "packages/wouter/src/memory-location.js",
    "content": "import mitt from \"mitt\";\nimport { useSyncExternalStore } from \"./react-deps.js\";\n\n/**\n * In-memory location that supports navigation\n */\n\nexport const memoryLocation = ({\n  path = \"/\",\n  searchPath = \"\",\n  static: staticLocation,\n  record,\n} = {}) => {\n  let initialPath = path;\n  if (searchPath) {\n    // join with & if path contains search query, and ? otherwise\n    initialPath += path.split(\"?\")[1] ? \"&\" : \"?\";\n    initialPath += searchPath;\n  }\n\n  let [currentPath, currentSearch = \"\"] = initialPath.split(\"?\");\n  const history = [initialPath];\n  const emitter = mitt();\n\n  const navigateImplementation = (path, { replace = false } = {}) => {\n    if (record) {\n      if (replace) {\n        history.splice(history.length - 1, 1, path);\n      } else {\n        history.push(path);\n      }\n    }\n\n    [currentPath, currentSearch = \"\"] = path.split(\"?\");\n    emitter.emit(\"navigate\", path);\n  };\n\n  const navigate = !staticLocation ? navigateImplementation : () => null;\n\n  const subscribe = (cb) => {\n    emitter.on(\"navigate\", cb);\n    return () => emitter.off(\"navigate\", cb);\n  };\n\n  const useMemoryLocation = () => [\n    useSyncExternalStore(subscribe, () => currentPath),\n    navigate,\n  ];\n\n  const useMemoryQuery = () =>\n    useSyncExternalStore(subscribe, () => currentSearch);\n\n  // Attach searchHook to the location hook for auto-inheritance in Router\n  useMemoryLocation.searchHook = useMemoryQuery;\n\n  function reset() {\n    // clean history array with mutation to preserve link\n    history.splice(0, history.length);\n\n    navigateImplementation(initialPath);\n  }\n\n  return {\n    hook: useMemoryLocation,\n    searchHook: useMemoryQuery,\n    navigate,\n    history: record ? history : undefined,\n    reset: record ? reset : undefined,\n  };\n};\n"
  },
  {
    "path": "packages/wouter/src/paths.js",
    "content": "/*\n * Transforms `path` into its relative `base` version\n * If base isn't part of the path provided returns absolute path e.g. `~/app`\n */\nconst _relativePath = (base, path) =>\n  !path.toLowerCase().indexOf(base.toLowerCase())\n    ? path.slice(base.length) || \"/\"\n    : \"~\" + path;\n\n/**\n * When basepath is `undefined` or '/' it is ignored (we assume it's empty string)\n */\nconst baseDefaults = (base = \"\") => (base === \"/\" ? \"\" : base);\n\nexport const absolutePath = (to, base) =>\n  to[0] === \"~\" ? to.slice(1) : baseDefaults(base) + to;\n\nexport const relativePath = (base = \"\", path) =>\n  _relativePath(unescape(baseDefaults(base)), unescape(path));\n\n/*\n * Removes leading question mark\n */\nconst stripQm = (str) => (str[0] === \"?\" ? str.slice(1) : str);\n\n/*\n * decodes escape sequences such as %20\n */\nconst unescape = (str) => {\n  try {\n    return decodeURI(str);\n  } catch (_e) {\n    // fail-safe mode: if string can't be decoded do nothing\n    return str;\n  }\n};\n\nexport const sanitizeSearch = (search) => unescape(stripQm(search));\n"
  },
  {
    "path": "packages/wouter/src/react-deps.js",
    "content": "import * as React from \"react\";\n\n// React.useInsertionEffect is not available in React <18\n// This hack fixes a transpilation issue on some apps\nconst useBuiltinInsertionEffect = React[\"useInsertion\" + \"Effect\"];\n\nexport {\n  useMemo,\n  useRef,\n  useState,\n  useContext,\n  createContext,\n  isValidElement,\n  cloneElement,\n  createElement,\n  Fragment,\n  forwardRef,\n} from \"react\";\n\n// To resolve webpack 5 errors, while not presenting problems for native,\n// we copy the approaches from https://github.com/TanStack/query/pull/3561\n// and https://github.com/TanStack/query/pull/3601\n// ~ Show this aging PR some love to remove the need for this hack:\n//   https://github.com/facebook/react/pull/25231 ~\nexport { useSyncExternalStore } from \"./use-sync-external-store.js\";\n\n// Copied from:\n// https://github.com/facebook/react/blob/main/packages/shared/ExecutionEnvironment.js\nconst canUseDOM = !!(\n  typeof window !== \"undefined\" &&\n  typeof window.document !== \"undefined\" &&\n  typeof window.document.createElement !== \"undefined\"\n);\n\n// Copied from:\n// https://github.com/reduxjs/react-redux/blob/master/src/utils/useIsomorphicLayoutEffect.ts\n// \"React currently throws a warning when using useLayoutEffect on the server.\n// To get around it, we can conditionally useEffect on the server (no-op) and\n// useLayoutEffect in the browser.\"\nexport const useIsomorphicLayoutEffect = canUseDOM\n  ? React.useLayoutEffect\n  : React.useEffect;\n\n// useInsertionEffect is already a noop on the server.\n// See: https://github.com/facebook/react/blob/main/packages/react-server/src/ReactFizzHooks.js\nexport const useInsertionEffect =\n  useBuiltinInsertionEffect || useIsomorphicLayoutEffect;\n\n// Userland polyfill while we wait for the forthcoming\n// https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md\n// Note: \"A high-fidelity polyfill for useEvent is not possible because\n// there is no lifecycle or Hook in React that we can use to switch\n// .current at the right timing.\"\n// So we will have to make do with this \"close enough\" approach for now.\nexport const useEvent = (fn) => {\n  const ref = React.useRef([fn, (...args) => ref[0](...args)]).current;\n  // Per Dan Abramov: useInsertionEffect executes marginally closer to the\n  // correct timing for ref synchronization than useLayoutEffect on React 18.\n  // See: https://github.com/facebook/react/pull/25881#issuecomment-1356244360\n  useInsertionEffect(() => {\n    ref[0] = fn;\n  });\n  return ref[1];\n};\n"
  },
  {
    "path": "packages/wouter/src/use-browser-location.d.ts",
    "content": "export * from \"../types/use-browser-location.js\";\n"
  },
  {
    "path": "packages/wouter/src/use-browser-location.js",
    "content": "import { useSyncExternalStore } from \"./react-deps.js\";\n\n/**\n * History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History\n */\nconst eventPopstate = \"popstate\";\nconst eventPushState = \"pushState\";\nconst eventReplaceState = \"replaceState\";\nconst eventHashchange = \"hashchange\";\nconst events = [\n  eventPopstate,\n  eventPushState,\n  eventReplaceState,\n  eventHashchange,\n];\n\nconst subscribeToLocationUpdates = (callback) => {\n  for (const event of events) {\n    addEventListener(event, callback);\n  }\n  return () => {\n    for (const event of events) {\n      removeEventListener(event, callback);\n    }\n  };\n};\n\nexport const useLocationProperty = (fn, ssrFn) =>\n  useSyncExternalStore(subscribeToLocationUpdates, fn, ssrFn);\n\nconst currentSearch = () => location.search;\n\nexport const useSearch = ({ ssrSearch } = {}) =>\n  useLocationProperty(\n    currentSearch,\n    // != null checks for both null and undefined, but allows empty string \"\"\n    // This allows proper hydration: server renders with ssrSearch=\"?foo\",\n    // client hydrates with just <Router /> and reads from location.search\n    ssrSearch != null ? () => ssrSearch : currentSearch\n  );\n\nconst currentPathname = () => location.pathname;\n\nexport const usePathname = ({ ssrPath } = {}) =>\n  useLocationProperty(\n    currentPathname,\n    // != null checks for both null and undefined, but allows empty string \"\"\n    // This allows proper hydration: server renders with ssrPath=\"/foo\",\n    // client hydrates with just <Router /> and reads from location.pathname\n    ssrPath != null ? () => ssrPath : currentPathname\n  );\n\nconst currentHistoryState = () => history.state;\nexport const useHistoryState = () =>\n  useLocationProperty(currentHistoryState, () => null);\n\nexport const navigate = (to, { replace = false, state = null } = {}) =>\n  history[replace ? eventReplaceState : eventPushState](state, \"\", to);\n\n// the 2nd argument of the `useBrowserLocation` return value is a function\n// that allows to perform a navigation.\nexport const useBrowserLocation = (opts = {}) => [usePathname(opts), navigate];\n\nconst patchKey = Symbol.for(\"wouter_v3\");\n\n// While History API does have `popstate` event, the only\n// proper way to listen to changes via `push/replaceState`\n// is to monkey-patch these methods.\n//\n// See https://stackoverflow.com/a/4585031\nif (typeof history !== \"undefined\" && typeof window[patchKey] === \"undefined\") {\n  for (const type of [eventPushState, eventReplaceState]) {\n    const original = history[type];\n    // TODO: we should be using unstable_batchedUpdates to avoid multiple re-renders,\n    // however that will require an additional peer dependency on react-dom.\n    // See: https://github.com/reactwg/react-18/discussions/86#discussioncomment-1567149\n    history[type] = function () {\n      const result = original.apply(this, arguments);\n      const event = new Event(type);\n      event.arguments = arguments;\n\n      dispatchEvent(event);\n      return result;\n    };\n  }\n\n  // patch history object only once\n  // See: https://github.com/molefrog/wouter/issues/167\n  Object.defineProperty(window, patchKey, { value: true });\n}\n"
  },
  {
    "path": "packages/wouter/src/use-hash-location.d.ts",
    "content": "export * from \"../types/use-hash-location.js\";\n"
  },
  {
    "path": "packages/wouter/src/use-hash-location.js",
    "content": "import { useSyncExternalStore } from \"./react-deps.js\";\n\n// array of callback subscribed to hash updates\nconst listeners = {\n  v: [],\n};\n\nconst onHashChange = () => listeners.v.forEach((cb) => cb());\n\n// we subscribe to `hashchange` only once when needed to guarantee that\n// all listeners are called synchronously\nconst subscribeToHashUpdates = (callback) => {\n  if (listeners.v.push(callback) === 1)\n    addEventListener(\"hashchange\", onHashChange);\n\n  return () => {\n    listeners.v = listeners.v.filter((i) => i !== callback);\n    if (!listeners.v.length) removeEventListener(\"hashchange\", onHashChange);\n  };\n};\n\n// leading '#' is ignored, leading '/' is optional\nconst currentHashLocation = () => \"/\" + location.hash.replace(/^#?\\/?/, \"\");\n\nexport const navigate = (to, { state = null, replace = false } = {}) => {\n  const oldURL = location.href;\n\n  const [hash, search] = to.replace(/^#?\\/?/, \"\").split(\"?\");\n\n  // Works for ALL protocols including data:\n  const url = new URL(location.href);\n  url.hash = `/${hash}`;\n  if (search) url.search = search;\n  const newURL = url.href;\n\n  if (replace) {\n    history.replaceState(state, \"\", newURL);\n  } else {\n    history.pushState(state, \"\", newURL);\n  }\n\n  const event =\n    typeof HashChangeEvent !== \"undefined\"\n      ? new HashChangeEvent(\"hashchange\", { oldURL, newURL })\n      : new Event(\"hashchange\", { detail: { oldURL, newURL } });\n\n  dispatchEvent(event);\n};\n\nexport const useHashLocation = ({ ssrPath = \"/\" } = {}) => [\n  useSyncExternalStore(\n    subscribeToHashUpdates,\n    currentHashLocation,\n    () => ssrPath\n  ),\n  navigate,\n];\n\nuseHashLocation.hrefs = (href) => \"#\" + href;\n"
  },
  {
    "path": "packages/wouter/src/use-sync-external-store.js",
    "content": "export { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n"
  },
  {
    "path": "packages/wouter/src/use-sync-external-store.native.js",
    "content": "export { useSyncExternalStore } from \"use-sync-external-store/shim/index.native.js\";\n"
  },
  {
    "path": "packages/wouter/test/history-patch.test.ts",
    "content": "import { useLocation as reactHook } from \"../src/index.js\";\nimport { useLocation as preactHook } from \"../src/index.js\";\nimport { renderHook, act } from \"@testing-library/react\";\n\nimport { mock, test, expect, describe } from \"bun:test\";\n\ndescribe(\"history patch\", () => {\n  test(\"exports should exists\", () => {\n    expect(reactHook).toBeDefined();\n    expect(preactHook).toBeDefined();\n  });\n\n  test(\"history should be patched once\", () => {\n    const fn = mock();\n    const { result, unmount } = renderHook(() => reactHook());\n\n    addEventListener(\"pushState\", (e) => {\n      fn();\n    });\n\n    expect(result.current[0]).toBe(\"/\");\n    expect(fn).toHaveBeenCalledTimes(0);\n\n    act(() => result.current[1](\"/hello\"));\n    act(() => result.current[1](\"/world\"));\n\n    expect(result.current[0]).toBe(\"/world\");\n    expect(fn).toHaveBeenCalledTimes(2);\n\n    unmount();\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/jest-dom.d.ts",
    "content": "import type { TestingLibraryMatchers } from \"@testing-library/jest-dom/matchers\";\n\ndeclare module \"bun:test\" {\n  interface Matchers<T = unknown>\n    extends TestingLibraryMatchers<typeof expect.stringContaining, T> {}\n}\n"
  },
  {
    "path": "packages/wouter/test/link.test-d.tsx",
    "content": "import { describe, expectTypeOf, test } from \"bun:test\";\nimport { Link, LinkProps, type Path } from \"../src/index.js\";\nimport * as React from \"react\";\n\ntype NetworkLocationHook = () => [\n  Path,\n  (path: string, options: { host: string; retries?: number }) => void\n];\n\ndescribe(\"<Link /> types\", () => {\n  test(\"should have required prop href\", () => {\n    // @ts-expect-error\n    <Link>test</Link>;\n    <Link href=\"/\">test</Link>;\n  });\n\n  test(\"does not allow `to` and `href` props to be used at the same time\", () => {\n    // @ts-expect-error\n    <Link to=\"/hello\" href=\"/world\">\n      Hello\n    </Link>;\n  });\n\n  test(\"should inherit props from `HTMLAnchorElement`\", () => {\n    <Link to=\"/hello\" className=\"hello\">\n      Hello\n    </Link>;\n\n    <Link to=\"/hello\" style={{}}>\n      Hello\n    </Link>;\n\n    <Link to=\"/hello\" target=\"_blank\">\n      Hello\n    </Link>;\n\n    <Link to=\"/hello\" download ping=\"he-he\">\n      Hello\n    </Link>;\n  });\n\n  test(\"can accept function as `className`\", () => {\n    <Link\n      href=\"/\"\n      className={(isActive) => (isActive ? \"active\" : \"non-active\")}\n    />;\n\n    <Link\n      href=\"/\"\n      className={(isActive) => (isActive ? \"active\" : undefined)}\n    />;\n  });\n\n  test(\"should support other navigation params\", () => {\n    <Link href=\"/\" state={{ a: \"foo\" }}>\n      test\n    </Link>;\n\n    <Link href=\"/\" replace>\n      test\n    </Link>;\n\n    // @ts-expect-error\n    <Link to=\"/\" replace={{ nope: 1 }}>\n      Hello\n    </Link>;\n\n    <Link href=\"/\" state={undefined}>\n      test\n    </Link>;\n  });\n\n  test(\"should work with generic type\", () => {\n    <Link<NetworkLocationHook> href=\"/\" host=\"wouter.com\">\n      test\n    </Link>;\n\n    // @ts-expect-error\n    <Link<NetworkLocationHook> href=\"/\">test</Link>;\n\n    <Link<NetworkLocationHook> href=\"/\" host=\"wouter.com\" retries={4}>\n      test\n    </Link>;\n  });\n});\n\ndescribe(\"<Link /> with ref\", () => {\n  test(\"should work\", () => {\n    const ref = React.useRef<HTMLAnchorElement>(null);\n\n    <Link to=\"/\" ref={ref}>\n      Hello\n    </Link>;\n  });\n\n  test(\"should have error when type is miss matched\", () => {\n    const ref = React.useRef<HTMLAreaElement>(null);\n\n    // @ts-expect-error\n    <Link to=\"/\" ref={ref}>\n      Hello\n    </Link>;\n  });\n});\n\ndescribe(\"<Link /> with `asChild` prop\", () => {\n  test(\"should work\", () => {\n    <Link to=\"/\" asChild>\n      <a>Hello</a>\n    </Link>;\n  });\n\n  test(\"does not allow `to` and `href` props to be used at the same time\", () => {\n    // @ts-expect-error\n    <Link to=\"/hello\" href=\"/world\" asChild>\n      <a>Hello</a>\n    </Link>;\n  });\n\n  test(\"can only have valid element as a child\", () => {\n    // @ts-expect-error strings are not valid children\n    <Link to=\"/\" asChild>\n      {true ? \"Hello\" : \"World\"}\n    </Link>;\n\n    // @ts-expect-error can't use multiple nodes as children\n    <Link to=\"/\" asChild>\n      <a>Link</a>\n      <div>icon</div>\n    </Link>;\n  });\n\n  test(\"does not allow other props\", () => {\n    // @ts-expect-error\n    <Link to=\"/\" asChild className=\"\">\n      <a>Hello</a>\n    </Link>;\n\n    // @ts-expect-error\n    <Link to=\"/\" asChild style={{}}>\n      <a>Hello</a>\n    </Link>;\n\n    // @ts-expect-error\n    <Link to=\"/\" asChild unknown={10}>\n      <a>Hello</a>\n    </Link>;\n\n    // @ts-expect-error\n    <Link to=\"/\" asChild ref={null}>\n      <a>Hello</a>\n    </Link>;\n  });\n\n  test(\"should support other navigation params\", () => {\n    <Link to=\"/\" asChild replace>\n      <a>Hello</a>\n    </Link>;\n\n    // @ts-expect-error\n    <Link to=\"/\" asChild replace={12}>\n      <a>Hello</a>\n    </Link>;\n\n    <Link to=\"/\" asChild state={{ hello: \"world\" }}>\n      <a>Hello</a>\n    </Link>;\n  });\n\n  test(\"should work with generic type\", () => {\n    <Link<NetworkLocationHook> asChild to=\"/\" host=\"wouter.com\">\n      <div>test</div>\n    </Link>;\n\n    // @ts-expect-error\n    <Link<NetworkLocationHook> asChild to=\"/\">\n      <div>test</div>\n    </Link>;\n\n    <Link<NetworkLocationHook> asChild to=\"/\" host=\"wouter.com\" retries={4}>\n      <div>test</div>\n    </Link>;\n  });\n\n  test(\"accepts `onClick` prop that overwrites child's handler\", () => {\n    <Link\n      to=\"/\"\n      asChild\n      onClick={(e) => {\n        expectTypeOf(e).toEqualTypeOf<React.MouseEvent>();\n      }}\n    >\n      <a>Hello</a>\n    </Link>;\n  });\n\n  test(\"should work with `ComponentProps`\", () => {\n    type LinkComponentProps = React.ComponentProps<typeof Link>;\n\n    // Because Link is a generic component, the props\n    // cant't contain navigation options of the default generic\n    // parameter `BrowserLocationHook`.\n    // So the best we can get are the props such as `href` etc.\n    expectTypeOf<LinkComponentProps>().toMatchTypeOf<LinkProps>();\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/link.test.tsx",
    "content": "import { type MouseEventHandler } from \"react\";\nimport { test, expect, afterEach, mock, describe } from \"bun:test\";\nimport { render, cleanup, fireEvent, act } from \"@testing-library/react\";\n\nimport { Router, Link } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nafterEach(cleanup);\n\ndescribe(\"<Link />\", () => {\n  test(\"renders a link with proper attributes\", () => {\n    const { getByText } = render(\n      <Link href=\"/about\" className=\"link--active\">\n        Click Me\n      </Link>\n    );\n\n    const element = getByText(\"Click Me\");\n\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveAttribute(\"href\", \"/about\");\n    expect(element).toHaveClass(\"link--active\");\n  });\n\n  test(\"passes ref to <a />\", () => {\n    const refCallback = mock<(element: HTMLAnchorElement) => void>();\n    const { getByText } = render(\n      <Link href=\"/\" ref={refCallback}>\n        Testing\n      </Link>\n    );\n\n    const element = getByText(\"Testing\");\n\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveAttribute(\"href\", \"/\");\n\n    expect(refCallback).toHaveBeenCalledTimes(1);\n    expect(refCallback).toHaveBeenCalledWith(element);\n  });\n\n  test(\"still creates a plain link when nothing is passed\", () => {\n    const { getByTestId } = render(<Link href=\"/about\" data-testid=\"link\" />);\n\n    const element = getByTestId(\"link\");\n\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveAttribute(\"href\", \"/about\");\n    expect(element).toBeEmptyDOMElement();\n  });\n\n  test(\"supports `to` prop as an alias to `href`\", () => {\n    const { getByText } = render(<Link to=\"/about\">Hello</Link>);\n    const element = getByText(\"Hello\");\n\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveAttribute(\"href\", \"/about\");\n  });\n\n  test(\"performs a navigation when the link is clicked\", () => {\n    const { getByTestId } = render(\n      <Link href=\"/goo-baz\" data-testid=\"link\">\n        link\n      </Link>\n    );\n\n    fireEvent.click(getByTestId(\"link\"));\n\n    expect(location.pathname).toBe(\"/goo-baz\");\n  });\n\n  test(\"supports replace navigation\", () => {\n    const { getByTestId } = render(\n      <Link href=\"/goo-baz\" replace data-testid=\"link\">\n        link\n      </Link>\n    );\n\n    const histBefore = history.length;\n\n    fireEvent.click(getByTestId(\"link\"));\n\n    expect(location.pathname).toBe(\"/goo-baz\");\n    expect(history.length).toBe(histBefore);\n  });\n\n  test(\"ignores the navigation when clicked with modifiers\", () => {\n    const { getByTestId } = render(\n      <Link href=\"/users\" data-testid=\"link\">\n        click\n      </Link>\n    );\n    const clickEvt = new MouseEvent(\"click\", {\n      bubbles: true,\n      cancelable: true,\n      button: 0,\n      ctrlKey: true,\n    });\n\n    // js-dom doesn't implement browser navigation (e.g. changing location\n    // when a link is clicked) so we need just ingore it to avoid warnings\n    clickEvt.preventDefault();\n\n    fireEvent(getByTestId(\"link\"), clickEvt);\n    expect(location.pathname).not.toBe(\"/users\");\n  });\n\n  test(\"ignores the navigation when event is cancelled\", () => {\n    const clickHandler: MouseEventHandler = (e) => {\n      e.preventDefault();\n    };\n\n    const { getByTestId } = render(\n      <Link href=\"/users\" data-testid=\"link\" onClick={clickHandler}>\n        click\n      </Link>\n    );\n\n    fireEvent.click(getByTestId(\"link\"));\n    expect(location.pathname).not.toBe(\"/users\");\n  });\n\n  test(\"accepts an `onClick` prop, fired before the navigation\", () => {\n    const clickHandler = mock();\n\n    const { getByTestId } = render(\n      <Link href=\"/\" onClick={clickHandler} data-testid=\"link\" />\n    );\n\n    fireEvent.click(getByTestId(\"link\"));\n    expect(clickHandler).toHaveBeenCalledTimes(1);\n  });\n\n  test(\"renders `href` with basepath\", () => {\n    const { getByTestId } = render(\n      <Router base=\"/app\">\n        <Link href=\"/dashboard\" data-testid=\"link\" />\n      </Router>\n    );\n\n    const link = getByTestId(\"link\");\n    expect(link.getAttribute(\"href\")).toBe(\"/app/dashboard\");\n  });\n\n  test(\"renders `href` with absolute links\", () => {\n    const { getByTestId } = render(\n      <Router base=\"/app\">\n        <Link href=\"~/home\" data-testid=\"link\" />\n      </Router>\n    );\n\n    const element = getByTestId(\"link\");\n    expect(element).toHaveAttribute(\"href\", \"/home\");\n  });\n\n  test(\"supports history state\", () => {\n    const testState = { hello: \"world\" };\n    const { getByTestId } = render(\n      <Link href=\"/goo-baz\" state={testState} data-testid=\"link\">\n        link\n      </Link>\n    );\n\n    fireEvent.click(getByTestId(\"link\"));\n    expect(location.pathname).toBe(\"/goo-baz\");\n    expect(history.state).toStrictEqual(testState);\n  });\n\n  test(\"can be configured to use custom href formatting\", () => {\n    const formatter = (href: string) => `#${href}`;\n\n    const { getByTestId } = render(\n      <>\n        <Router hrefs={formatter}>\n          <Link href=\"/\" data-testid=\"root\" />\n          <Link href=\"/home\" data-testid=\"home\" />\n        </Router>\n\n        <Router base=\"/app\" hrefs={formatter}>\n          <Link href=\"~/home\" data-testid=\"absolute\" />\n        </Router>\n      </>\n    );\n\n    expect(getByTestId(\"root\")).toHaveAttribute(\"href\", \"#/\");\n    expect(getByTestId(\"home\")).toHaveAttribute(\"href\", \"#/home\");\n    expect(getByTestId(\"absolute\")).toHaveAttribute(\"href\", \"#/home\");\n  });\n});\n\ndescribe(\"active links\", () => {\n  test(\"proxies `className` when it is a string\", () => {\n    const { getByText } = render(\n      <Link href=\"/\" className=\"link--active warning\">\n        Click Me\n      </Link>\n    );\n\n    const element = getByText(\"Click Me\");\n    expect(element).toHaveAttribute(\"class\", \"link--active warning\");\n  });\n\n  test(\"calls the `className` function with active link flag\", () => {\n    const { navigate, hook } = memoryLocation({ path: \"/\" });\n\n    const { getByText } = render(\n      <Router hook={hook}>\n        <Link\n          href=\"/\"\n          className={(isActive) => {\n            return [isActive ? \"active\" : \"\", \"link\"].join(\" \");\n          }}\n        >\n          Click Me\n        </Link>\n      </Router>\n    );\n\n    const element = getByText(\"Click Me\");\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveClass(\"active\");\n    expect(element).toHaveClass(\"link\");\n\n    act(() => navigate(\"/about\"));\n\n    expect(element).not.toHaveClass(\"active\");\n    expect(element).toHaveClass(\"link\");\n  });\n\n  test(\"correctly highlights active links when using custom href formatting\", () => {\n    const formatter = (href: string) => `#${href}`;\n    const { navigate, hook } = memoryLocation({ path: \"/\" });\n\n    const { getByText } = render(\n      <Router hook={hook} hrefs={formatter}>\n        <Link\n          href=\"/\"\n          className={(isActive) => {\n            return [isActive ? \"active\" : \"\", \"link\"].join(\" \");\n          }}\n        >\n          Click Me\n        </Link>\n      </Router>\n    );\n\n    const element = getByText(\"Click Me\");\n    expect(element).toBeInTheDocument();\n    expect(element).toHaveClass(\"active\");\n    expect(element).toHaveClass(\"link\");\n\n    act(() => navigate(\"/about\"));\n\n    expect(element).not.toHaveClass(\"active\");\n    expect(element).toHaveClass(\"link\");\n  });\n});\n\ndescribe(\"<Link /> with `asChild` prop\", () => {\n  test(\"when `asChild` is not specified, wraps the children in an <a />\", () => {\n    const { getByText } = render(\n      <Link href=\"/about\">\n        <div className=\"link--wannabe\">Click Me</div>\n      </Link>\n    );\n\n    const link = getByText(\"Click Me\");\n\n    expect(link.tagName).toBe(\"DIV\");\n    expect(link).not.toHaveAttribute(\"href\");\n    expect(link).toHaveClass(\"link--wannabe\");\n    expect(link).toHaveTextContent(\"Click Me\");\n\n    expect(link.parentElement?.tagName).toBe(\"A\");\n    expect(link.parentElement).toHaveAttribute(\"href\", \"/about\");\n  });\n\n  test(\"when invalid element is provided, wraps the children in an <a />\", () => {\n    const { getByText } = render(\n      /* @ts-expect-error */\n      <Link href=\"/about\" asChild>\n        Click Me\n      </Link>\n    );\n\n    const link = getByText(\"Click Me\");\n\n    expect(link.tagName).toBe(\"A\");\n    expect(link).toHaveAttribute(\"href\", \"/about\");\n    expect(link).toHaveTextContent(\"Click Me\");\n  });\n\n  test(\"when more than one element is provided, wraps the children in an <a />\", async () => {\n    const { getByText } = render(\n      /* @ts-expect-error */\n      <Link href=\"/about\" asChild>\n        <span>1</span>\n        <span>2</span>\n        <span>3</span>\n      </Link>\n    );\n\n    const span = getByText(\"1\");\n\n    expect(span.parentElement?.tagName).toBe(\"A\");\n\n    expect(span.parentElement).toHaveAttribute(\"href\", \"/about\");\n    expect(span.parentElement).toHaveTextContent(\"123\");\n  });\n\n  test(\"injects href prop when rendered with `asChild`\", () => {\n    const { getByText } = render(\n      <Link href=\"/about\" asChild>\n        <div className=\"link--wannabe\">Click Me</div>\n      </Link>\n    );\n\n    const link = getByText(\"Click Me\");\n\n    expect(link.tagName).toBe(\"DIV\");\n    expect(link).toHaveClass(\"link--wannabe\");\n    expect(link).toHaveAttribute(\"href\", \"/about\");\n    expect(link).toHaveTextContent(\"Click Me\");\n  });\n\n  test(\"missing href or to won't crash\", () => {\n    const { getByText } = render(\n      /* @ts-expect-error */\n      <Link>Click Me</Link>\n    );\n\n    const link = getByText(\"Click Me\");\n\n    expect(link.tagName).toBe(\"A\");\n    expect(link).toHaveAttribute(\"href\", undefined);\n    expect(link).toHaveTextContent(\"Click Me\");\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/location-hook.test-d.ts",
    "content": "import { test, expectTypeOf, describe } from \"bun:test\";\nimport { BaseLocationHook, HookNavigationOptions } from \"../src/index.js\";\n\ndescribe(\"`HookNavigationOptions` utility type\", () => {\n  test(\"should return empty interface for hooks with no nav options\", () => {\n    const hook = (): [string, (path: string) => void] => {\n      return [\"stub\", (path: string) => {}];\n    };\n\n    type Options = HookNavigationOptions<typeof hook>;\n\n    expectTypeOf<Options>().toEqualTypeOf<{}>();\n\n    const optionsExt: Options | { a: 1 } = { a: 1, b: 2 };\n  });\n\n  test(\"should return object with required navigation params\", () => {\n    const hook = (): [\n      string,\n      (path: string, options: { replace: boolean; optional?: number }) => void\n    ] => {\n      return [\"stub\", () => {}];\n    };\n\n    type Options = HookNavigationOptions<typeof hook>;\n\n    // @ts-expect-error\n    expectTypeOf<Options>().toEqualTypeOf<{\n      replace: boolean;\n      foo: string;\n    }>();\n\n    expectTypeOf<Options>().toEqualTypeOf<{\n      replace: boolean;\n      optional?: number;\n    }>();\n  });\n\n  test(\"should not contain never when options are optional\", () => {\n    const hook = (\n      param: string\n    ): [string, (path: string, options?: { replace: boolean }) => void] => {\n      return [\"stub\", () => {}];\n    };\n\n    type Options = HookNavigationOptions<typeof hook>;\n\n    expectTypeOf<Options>().toEqualTypeOf<{\n      replace: boolean;\n    }>();\n  });\n\n  test(\"should only support valid hooks\", () => {\n    // @ts-expect-error\n    type A = HookNavigationOptions<string>;\n    // @ts-expect-error\n    type B = HookNavigationOptions<{}>;\n    // @ts-expect-error\n    type C = HookNavigationOptions<() => []>;\n  });\n\n  test(\"should return empty object when `BaseLocationHook` is given\", () => {\n    type Options = HookNavigationOptions<BaseLocationHook>;\n    expectTypeOf<Options>().toEqualTypeOf<{}>();\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/match-route.test-d.ts",
    "content": "import { test, expectTypeOf } from \"bun:test\";\nimport { matchRoute, useRouter } from \"../src/index.js\";\n\nconst assertType = <T>(_value: T): void => {};\nconst { parser } = useRouter();\n\ntest(\"should only accept strings\", () => {\n  // @ts-expect-error\n  assertType(matchRoute(parser, Symbol(), \"\"));\n  // @ts-expect-error\n  assertType(matchRoute(parser, undefined, \"\"));\n  assertType(matchRoute(parser, \"/\", \"\"));\n});\n\ntest('has a boolean \"match\" result as a first returned value', () => {\n  const [match] = matchRoute(parser, \"/\", \"\");\n  expectTypeOf(match).toEqualTypeOf<boolean>();\n});\n\ntest(\"returns null as parameters when there was no match\", () => {\n  const [match, params] = matchRoute(parser, \"/foo\", \"\");\n\n  if (!match) {\n    expectTypeOf(params).toEqualTypeOf<null>();\n  }\n});\n\ntest(\"accepts the type of parameters as a generic argument\", () => {\n  const [match, params] = matchRoute<{ id: string; name: string | undefined }>(\n    parser,\n    \"/app/users/:name?/:id\",\n    \"\"\n  );\n\n  if (match) {\n    expectTypeOf(params).toEqualTypeOf<{\n      id: string;\n      name: string | undefined;\n    }>();\n  }\n});\n\ntest(\"infers parameters from the route path\", () => {\n  const [, inferedParams] = matchRoute(parser, \"/app/users/:name?/:id/*?\", \"\");\n\n  if (inferedParams) {\n    expectTypeOf(inferedParams).toMatchTypeOf<{\n      0?: string;\n      1?: string;\n      2?: string;\n      name?: string;\n      id: string;\n      wildcard?: string;\n    }>();\n  }\n});\n"
  },
  {
    "path": "packages/wouter/test/memory-location.test-d.ts",
    "content": "import { test, expectTypeOf } from \"bun:test\";\nimport { memoryLocation } from \"../src/memory-location.js\";\nimport { BaseLocationHook } from \"../src/index.js\";\n\nconst assertType = <T>(_value: T): void => {};\n\ntest(\"should return hook that supports location spec\", () => {\n  const { hook } = memoryLocation();\n\n  expectTypeOf(hook).toMatchTypeOf<BaseLocationHook>();\n\n  const [location, navigate] = hook();\n\n  assertType<string>(location);\n  assertType<Function>(navigate);\n});\n\ntest(\"should return `navigate` method for navigating outside of components\", () => {\n  const { navigate } = memoryLocation();\n\n  assertType<Function>(navigate);\n});\n\ntest(\"should support `record` option for saving the navigation history\", () => {\n  const { history, reset } = memoryLocation({ record: true });\n\n  assertType<string[]>(history);\n  assertType<Function>(reset);\n});\n\ntest(\"should have history only wheen record is true\", () => {\n  // @ts-expect-error\n  const { history, reset } = memoryLocation({ record: false });\n  assertType(history);\n  assertType(reset);\n});\n\ntest(\"should support initial path\", () => {\n  const { hook } = memoryLocation({ path: \"/initial-path\" });\n\n  expectTypeOf(hook).toMatchTypeOf<BaseLocationHook>();\n});\n\ntest(\"should support `static` option\", () => {\n  const { hook } = memoryLocation({ static: true });\n\n  expectTypeOf(hook).toMatchTypeOf<BaseLocationHook>();\n});\n"
  },
  {
    "path": "packages/wouter/test/memory-location.test.ts",
    "content": "import { test, expect } from \"bun:test\";\nimport { renderHook, act } from \"@testing-library/react\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\ntest(\"returns a hook that is compatible with location spec\", () => {\n  const { hook } = memoryLocation();\n\n  const { result, unmount } = renderHook(() => hook());\n  const [value, update] = result.current;\n\n  expect(typeof value).toBe(\"string\");\n  expect(typeof update).toBe(\"function\");\n  unmount();\n});\n\ntest(\"should support initial path\", () => {\n  const { hook } = memoryLocation({ path: \"/test-case\" });\n\n  const { result, unmount } = renderHook(() => hook());\n  const [value] = result.current;\n\n  expect(value).toBe(\"/test-case\");\n  unmount();\n});\n\ntest(\"should support initial path with query\", () => {\n  const { searchHook } = memoryLocation({ path: \"/test-case?foo=bar\" });\n\n  const { result, unmount } = renderHook(() => searchHook());\n  const value = result.current;\n\n  expect(value).toBe(\"foo=bar\");\n  unmount();\n});\n\ntest(\"should support search path as parameter\", () => {\n  const { searchHook } = memoryLocation({\n    path: \"/test-case?foo=bar\",\n    searchPath: \"key=value\",\n  });\n\n  const { result, unmount } = renderHook(() => searchHook());\n  const value = result.current;\n\n  expect(value).toBe(\"foo=bar&key=value\");\n  unmount();\n});\n\ntest('should return location hook that has initial path \"/\" by default', () => {\n  const { hook } = memoryLocation();\n\n  const { result, unmount } = renderHook(() => hook());\n  const [value] = result.current;\n\n  expect(value).toBe(\"/\");\n  unmount();\n});\n\ntest('should return search hook that has initial query \"\" by default', () => {\n  const { searchHook } = memoryLocation();\n\n  const { result, unmount } = renderHook(() => searchHook());\n  const value = result.current;\n\n  expect(value).toBe(\"\");\n  unmount();\n});\n\ntest(\"should return standalone `navigate` method\", () => {\n  const { hook, navigate } = memoryLocation();\n\n  const { result, unmount } = renderHook(() => hook());\n\n  act(() => navigate(\"/standalone\"));\n\n  const [value] = result.current;\n  expect(value).toBe(\"/standalone\");\n  unmount();\n});\n\ntest(\"should return location hook that supports navigation\", () => {\n  const { hook } = memoryLocation();\n\n  const { result, unmount } = renderHook(() => hook());\n\n  act(() => result.current[1](\"/location\"));\n\n  const [value] = result.current;\n  expect(value).toBe(\"/location\");\n  unmount();\n});\n\ntest(\"should record all history when `record` option is provided\", () => {\n  const {\n    hook,\n    history,\n    navigate: standalone,\n  } = memoryLocation({ record: true, path: \"/test\" });\n\n  const { result, unmount } = renderHook(() => hook());\n\n  act(() => standalone(\"/standalone\"));\n  act(() => result.current[1](\"/location\"));\n\n  expect(result.current[0]).toBe(\"/location\");\n\n  expect(history).toStrictEqual([\"/test\", \"/standalone\", \"/location\"]);\n\n  act(() => standalone(\"/standalone\", { replace: true }));\n\n  expect(history).toStrictEqual([\"/test\", \"/standalone\", \"/standalone\"]);\n\n  act(() => result.current[1](\"/location\", { replace: true }));\n\n  expect(history).toStrictEqual([\"/test\", \"/standalone\", \"/location\"]);\n\n  unmount();\n});\n\ntest(\"should not have history when `record` option is falsy\", () => {\n  // @ts-expect-error\n  const { history, reset } = memoryLocation();\n  expect(history).not.toBeDefined();\n  expect(reset).not.toBeDefined();\n});\n\ntest(\"should have reset method when `record` option is provided\", () => {\n  const { history, reset, navigate } = memoryLocation({\n    path: \"/initial\",\n    record: true,\n  });\n  expect(history).toBeDefined();\n  expect(reset).toBeDefined();\n\n  navigate(\"test-1\");\n  navigate(\"test-2\");\n\n  reset();\n\n  expect(history).toStrictEqual([\"/initial\"]);\n});\n\ntest(\"should have reset method that reset hook location\", () => {\n  const { hook, history, navigate, reset } = memoryLocation({\n    record: true,\n    path: \"/test\",\n  });\n  const { result, unmount } = renderHook(() => hook());\n\n  act(() => navigate(\"/location\"));\n\n  expect(result.current[0]).toBe(\"/location\");\n\n  expect(history).toStrictEqual([\"/test\", \"/location\"]);\n\n  act(() => reset());\n\n  expect(history).toStrictEqual([\"/test\"]);\n\n  expect(result.current[0]).toBe(\"/test\");\n\n  unmount();\n});\n"
  },
  {
    "path": "packages/wouter/test/nested-route.test.tsx",
    "content": "import { test, expect, describe } from \"bun:test\";\nimport { act, render, renderHook } from \"@testing-library/react\";\n\nimport { Route, Router, Switch, useRouter } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\ndescribe(\"when `nest` prop is given\", () => {\n  test(\"renders by default\", () => {\n    const { container } = render(<Route nest>matched!</Route>);\n    expect(container.innerHTML).toBe(\"matched!\");\n  });\n\n  test(\"matches the pattern loosely\", () => {\n    const { hook, navigate } = memoryLocation();\n\n    const { container } = render(\n      <Router hook={hook}>\n        <Route path=\"/posts/:slug\" nest>\n          matched!\n        </Route>\n      </Router>\n    );\n\n    expect(container.innerHTML).toBe(\"\");\n\n    act(() => navigate(\"/posts/all\")); // full match\n    expect(container.innerHTML).toBe(\"matched!\");\n\n    act(() => navigate(\"/users\"));\n    expect(container.innerHTML).toBe(\"\");\n\n    act(() => navigate(\"/posts/10-react-tricks/table-of-contents\"));\n    expect(container.innerHTML).toBe(\"matched!\");\n  });\n\n  test(\"can be used inside a Switch\", () => {\n    const { container } = render(\n      <Router\n        hook={\n          memoryLocation({ path: \"/posts/13/2012/sort\", static: true }).hook\n        }\n      >\n        <Switch>\n          <Route path=\"/about\">about</Route>\n          <Route path=\"/posts/:slug\" nest>\n            nested\n          </Route>\n          <Route>default</Route>\n        </Switch>\n      </Router>\n    );\n\n    expect(container.innerHTML).toBe(\"nested\");\n  });\n\n  test(\"sets the base to the matched segment\", () => {\n    const { result } = renderHook(() => useRouter().base, {\n      wrapper: (props) => (\n        <Router\n          hook={memoryLocation({ path: \"/2012/04/posts\", static: true }).hook}\n        >\n          <Route path=\"/:year/:month\" nest>\n            <Route path=\"/posts\">{props.children}</Route>\n          </Route>\n        </Router>\n      ),\n    });\n\n    expect(result.current).toBe(\"/2012/04\");\n  });\n\n  test(\"can be nested in another nested `Route` or `Router`\", () => {\n    const { container } = render(\n      <Router\n        base=\"/app\"\n        hook={\n          memoryLocation({\n            path: \"/app/users/alexey/settings/all\",\n            static: true,\n          }).hook\n        }\n      >\n        <Route path=\"/users/:name\" nest>\n          <Route path=\"/settings\">should not be rendered</Route>\n\n          <Route path=\"/settings\" nest>\n            <Route path=\"/all\">All settings</Route>\n          </Route>\n        </Route>\n      </Router>\n    );\n\n    expect(container.innerHTML).toBe(\"All settings\");\n  });\n\n  test(\"reacts to `nest` updates\", () => {\n    const { hook } = memoryLocation({\n      path: \"/app/apple/products\",\n      static: true,\n    });\n\n    const App = ({ nested }: { nested: boolean }) => {\n      return (\n        <Router hook={hook}>\n          <Route path=\"/app/:company\" nest={nested}>\n            matched!\n          </Route>\n        </Router>\n      );\n    };\n\n    const { container, rerender } = render(<App nested={true} />);\n    expect(container.innerHTML).toBe(\"matched!\");\n\n    rerender(<App nested={false} />);\n    expect(container.innerHTML).toBe(\"\");\n  });\n\n  test(\"works with one optional segment\", () => {\n    const { hook, navigate } = memoryLocation({\n      path: \"/\",\n    });\n\n    const App = () => {\n      return (\n        <Router hook={hook}>\n          <Route path=\"/:version?\" nest>\n            {({ version }) => version ?? \"default\"}\n          </Route>\n        </Router>\n      );\n    };\n\n    const { container } = render(<App />);\n    expect(container.innerHTML).toBe(\"default\");\n\n    act(() => navigate(\"/v1\"));\n    expect(container.innerHTML).toBe(\"v1\");\n\n    act(() => navigate(\"/v2/dashboard\"));\n    expect(container.innerHTML).toBe(\"v2\");\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/parser.test.tsx",
    "content": "import { test, expect } from \"bun:test\";\n\nimport { pathToRegexp, Key } from \"path-to-regexp\";\nimport { renderHook } from \"@testing-library/react\";\n\nimport { Router, useRouter, useRoute, Parser } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\n// Custom parser that uses `path-to-regexp` instead of `regexparam`\nconst pathToRegexpParser: Parser = (route: string) => {\n  const keys: Key[] = [];\n  const pattern = pathToRegexp(route, keys);\n\n  return { pattern, keys: keys.map((k) => String(k.name)) };\n};\n\ntest(\"overrides the `parser` prop on the current router\", () => {\n  const { result } = renderHook(() => useRouter(), {\n    wrapper: ({ children }) => (\n      <Router parser={pathToRegexpParser}>{children}</Router>\n    ),\n  });\n\n  const router = result.current;\n  expect(router.parser).toBe(pathToRegexpParser);\n});\n\ntest(\"allows to change the behaviour of route matching\", () => {\n  const { result } = renderHook(\n    () => useRoute(\"/(home|dashboard)/:pages?/users/:rest*\"),\n    {\n      wrapper: ({ children }) => (\n        <Router\n          hook={memoryLocation({ path: \"/home/users/10/bio\" }).hook}\n          parser={pathToRegexpParser}\n        >\n          {children}\n        </Router>\n      ),\n    }\n  );\n\n  expect(result.current).toStrictEqual([\n    true,\n    {\n      0: \"home\",\n      1: undefined,\n      2: \"10/bio\",\n      pages: undefined,\n      rest: \"10/bio\",\n    } as any,\n  ]);\n});\n"
  },
  {
    "path": "packages/wouter/test/redirect.test-d.tsx",
    "content": "import { describe, test } from \"bun:test\";\nimport { Redirect } from \"../src/index.js\";\n\nconst assertType = <T,>(_value: T): void => {};\n\ndescribe(\"Redirect types\", () => {\n  test(\"should have required prop href\", () => {\n    // @ts-expect-error\n    assertType(<Redirect />);\n    assertType(<Redirect href=\"/\" />);\n  });\n\n  test(\"should support state prop\", () => {\n    assertType(<Redirect href=\"/\" state={{ a: \"foo\" }} />);\n    assertType(<Redirect href=\"/\" state={null} />);\n    assertType(<Redirect href=\"/\" state={undefined} />);\n    assertType(<Redirect href=\"/\" state=\"string\" />);\n  });\n\n  test(\"always renders nothing\", () => {\n    // can be used in JSX\n    <div>\n      <Redirect href=\"/\" />\n    </div>;\n\n    assertType<null>(Redirect({ href: \"/\" }));\n  });\n\n  test(\"can not accept children\", () => {\n    // @ts-expect-error\n    <Redirect href=\"/\">hi!</Redirect>;\n\n    // prettier-ignore\n    // @ts-expect-error\n    <Redirect href=\"/\"><><div>Fragment</div></></Redirect>;\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/redirect.test.tsx",
    "content": "import { test, expect } from \"bun:test\";\nimport { render } from \"@testing-library/react\";\nimport { useState } from \"react\";\n\nimport { Redirect, Router } from \"../src/index.js\";\n\nexport const customHookWithReturn =\n  (initialPath = \"/\") =>\n  () => {\n    const [path, updatePath] = useState(initialPath);\n    const navigate = (path: string) => {\n      updatePath(path);\n      return \"foo\";\n    };\n\n    return [path, navigate];\n  };\n\ntest(\"renders nothing\", () => {\n  const { container, unmount } = render(<Redirect to=\"/users\" />);\n  expect(container.childNodes.length).toBe(0);\n  unmount();\n});\n\ntest(\"results in change of current location\", () => {\n  const { unmount } = render(<Redirect to=\"/users\" />);\n\n  expect(location.pathname).toBe(\"/users\");\n  unmount();\n});\n\ntest(\"supports `base` routers with relative path\", () => {\n  const { unmount } = render(\n    <Router base=\"/app\">\n      <Redirect to=\"/nested\" />\n    </Router>\n  );\n\n  expect(location.pathname).toBe(\"/app/nested\");\n  unmount();\n});\n\ntest(\"supports `base` routers with absolute path\", () => {\n  const { unmount } = render(\n    <Router base=\"/app\">\n      <Redirect to=\"~/absolute\" />\n    </Router>\n  );\n\n  expect(location.pathname).toBe(\"/absolute\");\n  unmount();\n});\n\ntest(\"supports replace navigation\", () => {\n  const histBefore = history.length;\n\n  const { unmount } = render(<Redirect to=\"/users\" replace />);\n\n  expect(location.pathname).toBe(\"/users\");\n  expect(history.length).toBe(histBefore);\n  unmount();\n});\n\ntest(\"supports history state\", () => {\n  const testState = { hello: \"world\" };\n  const { unmount } = render(<Redirect to=\"/users\" state={testState} />);\n\n  expect(location.pathname).toBe(\"/users\");\n  expect(history.state).toStrictEqual(testState);\n  unmount();\n});\n\ntest(\"useLayoutEffect should return nothing\", () => {\n  const { unmount } = render(\n    // @ts-expect-error\n    <Router hook={customHookWithReturn()}>\n      <Redirect to=\"/users\" replace />\n    </Router>\n  );\n\n  expect(location.pathname).toBe(\"/users\");\n  unmount();\n});\n"
  },
  {
    "path": "packages/wouter/test/route.test-d.tsx",
    "content": "import { test, describe, expectTypeOf } from \"bun:test\";\nimport { Route } from \"../src/index.js\";\nimport { ComponentProps } from \"react\";\nimport * as React from \"react\";\n\nconst assertType = <T,>(_value: T): void => {};\n\ndescribe(\"`path` prop\", () => {\n  test(\"is optional\", () => {\n    assertType(<Route />);\n  });\n\n  test(\"should be a string or RegExp\", () => {\n    let a: ComponentProps<typeof Route>[\"path\"];\n    expectTypeOf(a).toMatchTypeOf<string | RegExp | undefined>();\n  });\n});\n\ntest(\"accepts the optional boolean `nest` prop\", () => {\n  assertType(<Route nest />);\n  assertType(<Route nest={false} />);\n\n  // @ts-expect-error - should be boolean\n  assertType(<Route nest={\"true\"} />);\n});\n\ntest(\"renders a component provided in the `component` prop\", () => {\n  const Header = () => <div />;\n  const Profile = () => null;\n\n  <Route path=\"/header\" component={Header} />;\n  <Route path=\"/profile/:id\" component={Profile} />;\n\n  // @ts-expect-error must be a component, not JSX\n  <Route path=\"/header\" component={<a />} />;\n});\n\ntest(\"accepts class components in the `component` prop\", () => {\n  class A extends React.Component<{ params: {} }> {\n    render() {\n      return <div />;\n    }\n  }\n\n  <Route path=\"/app\" component={A} />;\n});\n\ntest(\"accepts ForwardRefExoticComponent in the `component` prop\", () => {\n  // Simulates components wrapped with HOCs like withErrorBoundary\n  const MyComponent = React.forwardRef<HTMLDivElement, { params: {} }>(\n    ({ params }) => <div />\n  );\n\n  <Route path=\"/app\" component={MyComponent} />;\n});\n\ntest(\"accepts children\", () => {\n  <Route path=\"/app\">\n    <div />\n  </Route>;\n\n  <Route path=\"/app\">\n    This is a <b>mixed</b> content\n  </Route>;\n\n  <Route>\n    <>\n      <div />\n    </>\n  </Route>;\n});\n\ntest(\"supports functions as children\", () => {\n  <Route path=\"/users/:id\">\n    {(params) => {\n      expectTypeOf(params).toMatchTypeOf<{}>();\n      return <div />;\n    }}\n  </Route>;\n\n  <Route path=\"/users/:id\">{({ id }) => `User id: ${id}`}</Route>;\n\n  <Route path=\"/users/:id\">\n    {({ age }: { age: string }) => `User age: ${age}`}\n  </Route>;\n\n  // @ts-expect-error function should return valid JSX\n  <Route path=\"/app\">{() => {}}</Route>;\n\n  // prettier-ignore\n  // @ts-expect-error you can't use JSX together with render function\n  <Route path=\"/\">{() => <div />}<a>Link</a></Route>;\n});\n\ndescribe(\"parameter inference\", () => {\n  test(\"can infer type of params from the path given\", () => {\n    <Route path=\"/path/:first/:second/another/:third\">\n      {({ first, second, third }) => {\n        expectTypeOf(first).toEqualTypeOf<string>();\n        return <div>{`${first}, ${second}, ${third}`}</div>;\n      }}\n    </Route>;\n\n    <Route path=\"/users/:name/\">\n      {/* @ts-expect-error - `age` param is not present in the pattern */}\n      {({ name, age }) => {\n        return <div>{`Hello, ${name}`}</div>;\n      }}\n    </Route>;\n  });\n\n  test(\"extract wildcard params into `wild` property\", () => {\n    <Route path=\"/users/*/settings\">\n      {({ wild }) => {\n        expectTypeOf(wild).toEqualTypeOf<string>();\n        return <div>The path is {wild}</div>;\n      }}\n    </Route>;\n  });\n\n  test(\"allows to customize type of params via generic parameter\", () => {\n    <Route<{ name: string; lastName: string }> path=\"/users/:name/:age\">\n      {(params) => {\n        expectTypeOf(params.lastName).toEqualTypeOf<string>();\n        return <div>This really is undefined {params.lastName}</div>;\n      }}\n    </Route>;\n  });\n\n  test(\"can't infer the type when the path isn't known at compile time\", () => {\n    <Route path={JSON.parse('\"/home/:section\"')}>\n      {(params) => {\n        // @ts-expect-error\n        params.section;\n        return <div />;\n      }}\n    </Route>;\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/route.test.tsx",
    "content": "import { it, expect, afterEach } from \"bun:test\";\nimport { render, act, cleanup } from \"@testing-library/react\";\n\nimport { Router, Route } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\nimport { ReactElement } from \"react\";\n\n// Clean up after each test to avoid DOM pollution\nafterEach(cleanup);\n\nconst testRouteRender = (initialPath: string, jsx: ReactElement) => {\n  return render(\n    <Router hook={memoryLocation({ path: initialPath }).hook}>{jsx}</Router>\n  );\n};\n\nit(\"always renders its content when `path` is empty\", () => {\n  const { container } = testRouteRender(\n    \"/nothing\",\n    <Route>\n      <h1>Hello!</h1>\n    </Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"Hello!\");\n});\n\nit(\"accepts plain children\", () => {\n  const { container } = testRouteRender(\n    \"/foo\",\n    <Route path=\"/foo\">\n      <h1>Hello!</h1>\n    </Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"Hello!\");\n});\n\nit(\"works with render props\", () => {\n  const { container } = testRouteRender(\n    \"/foo\",\n    <Route path=\"/foo\">{() => <h1>Hello!</h1>}</Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"Hello!\");\n});\n\nit(\"passes a match param object to the render function\", () => {\n  const { container } = testRouteRender(\n    \"/users/alex\",\n    <Route path=\"/users/:name\">{(params) => <h1>{params.name}</h1>}</Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"alex\");\n});\n\nit(\"renders nothing when there is not match\", () => {\n  const { container } = testRouteRender(\n    \"/bar\",\n    <Route path=\"/foo\">\n      <div>Hi!</div>\n    </Route>\n  );\n\n  expect(container.querySelector(\"div\")).not.toBeInTheDocument();\n});\n\nit(\"supports `component` prop similar to React-Router\", () => {\n  const Users = () => <h2>All users</h2>;\n\n  const { container } = testRouteRender(\n    \"/foo\",\n    <Route path=\"/foo\" component={Users} />\n  );\n\n  const heading = container.querySelector(\"h2\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"All users\");\n});\n\nit(\"supports `base` routers with relative path\", () => {\n  const { container, unmount } = render(\n    <Router base=\"/app\">\n      <Route path=\"/nested\">\n        <h1>Nested</h1>\n      </Route>\n      <Route path=\"~/absolute\">\n        <h2>Absolute</h2>\n      </Route>\n    </Router>\n  );\n\n  act(() => history.replaceState(null, \"\", \"/app/nested\"));\n\n  expect(container.children).toHaveLength(1);\n  expect(container.firstChild).toHaveProperty(\"tagName\", \"H1\");\n\n  unmount();\n});\n\nit(\"supports `path` prop with regex\", () => {\n  const { container } = testRouteRender(\n    \"/foo\",\n    <Route path={/[/]foo/}>\n      <h1>Hello!</h1>\n    </Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"Hello!\");\n});\n\nit(\"supports regex path named params\", () => {\n  const { container } = testRouteRender(\n    \"/users/alex\",\n    <Route path={/[/]users[/](?<name>[a-z]+)/}>\n      {(params) => <h1>{params.name}</h1>}\n    </Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"alex\");\n});\n\nit(\"supports regex path anonymous params\", () => {\n  const { container } = testRouteRender(\n    \"/users/alex\",\n    <Route path={/[/]users[/]([a-z]+)/}>\n      {(params) => <h1>{params[0]}</h1>}\n    </Route>\n  );\n\n  const heading = container.querySelector(\"h1\");\n  expect(heading).toBeInTheDocument();\n  expect(heading).toHaveTextContent(\"alex\");\n});\n\nit(\"rejects when a path does not match the regex\", () => {\n  const { container } = testRouteRender(\n    \"/users/1234\",\n    <Route path={/[/]users[/](?<name>[a-z]+)/}>\n      {(params) => <h1>{params.name}</h1>}\n    </Route>\n  );\n\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n});\n"
  },
  {
    "path": "packages/wouter/test/router.test-d.tsx",
    "content": "import { ComponentProps } from \"react\";\nimport { test, expectTypeOf } from \"bun:test\";\nimport {\n  Router,\n  Route,\n  BaseLocationHook,\n  useRouter,\n  Parser,\n  Path,\n} from \"../src/index.js\";\n\ntest(\"should have at least one child\", () => {\n  // @ts-expect-error\n  <Router />;\n});\n\ntest(\"accepts valid elements as children\", () => {\n  const Header = ({ title }: { title: string }) => <h1>{title}</h1>;\n\n  <Router>\n    <Route path=\"/\" />\n    <b>Hello!</b>\n  </Router>;\n\n  <Router>\n    Hello, we have <Header title=\"foo\" /> and some {1337} numbers here.\n  </Router>;\n\n  <Router>\n    <>Fragments!</>\n  </Router>;\n\n  <Router>\n    {/* @ts-expect-error should be a valid element */}\n    {() => <div />}\n  </Router>;\n});\n\ntest(\"can be customized with router properties passed as props\", () => {\n  // @ts-expect-error\n  <Router hook=\"wat?\" />;\n\n  const useFakeLocation: BaseLocationHook = () => [\"/foo\", () => {}];\n  <Router hook={useFakeLocation}>this is a valid router</Router>;\n\n  let fn: ComponentProps<typeof Router>[\"hook\"];\n  expectTypeOf(fn).exclude<undefined>().toBeFunction();\n\n  <Router base=\"/app\">Hello World!</Router>;\n\n  <Router ssrPath=\"/foo\">SSR</Router>;\n\n  <Router base=\"/users\" ssrPath=\"/users/all\" hook={useFakeLocation}>\n    Custom\n  </Router>;\n});\n\ntest(\"accepts `hrefs` function for transforming href strings\", () => {\n  const router = useRouter();\n  expectTypeOf(router.hrefs).toBeFunction();\n\n  <Router hrefs={(href: string) => href + \"1\"}>0</Router>;\n\n  <Router\n    hrefs={(href, router) => {\n      expectTypeOf(router).toEqualTypeOf<typeof router>();\n      return href + router.base;\n    }}\n  >\n    routers as a second argument\n  </Router>;\n});\n\ntest(\"accepts `parser` function for generating regular expressions\", () => {\n  const parser: Parser = (path: Path, loose?: boolean) => {\n    return {\n      pattern: new RegExp(`^${path}${loose === true ? \"(?=$|[/])\" : \"[/]$\"}`),\n      keys: [],\n    };\n  };\n\n  <Router parser={parser}>this is a valid router</Router>;\n});\n\ntest(\"does not accept other props\", () => {\n  const router = useRouter();\n\n  // @ts-expect-error `parent` prop isn't defined\n  <Router parent={router}>Parent router</Router>;\n});\n"
  },
  {
    "path": "packages/wouter/test/router.test.tsx",
    "content": "import { memo, ReactElement, cloneElement, ComponentProps } from \"react\";\nimport { renderHook, render } from \"@testing-library/react\";\nimport { it, expect, describe } from \"bun:test\";\nimport {\n  Router,\n  DefaultParams,\n  useRouter,\n  Parser,\n  BaseLocationHook,\n} from \"../src/index.js\";\n\nit(\"creates a router object on demand\", () => {\n  const { result } = renderHook(() => useRouter());\n  expect(result.current).toBeInstanceOf(Object);\n});\n\nit(\"creates a router object only once\", () => {\n  const { result, rerender } = renderHook(() => useRouter());\n  const router = result.current;\n\n  rerender();\n  expect(result.current).toBe(router);\n});\n\nit(\"does not create new router when <Router /> rerenders\", () => {\n  const { result, rerender } = renderHook(() => useRouter(), {\n    wrapper: (props) => <Router>{props.children}</Router>,\n  });\n  const router = result.current;\n\n  rerender();\n  expect(result.current).toBe(router);\n});\n\nit(\"alters the current router with `parser` and `hook` options\", () => {\n  const newParser: Parser = () => ({ pattern: /(.*)/, keys: [] });\n  const hook: BaseLocationHook = () => [\"/foo\", () => {}];\n\n  const { result } = renderHook(() => useRouter(), {\n    wrapper: (props) => (\n      <Router parser={newParser} hook={hook}>\n        {props.children}\n      </Router>\n    ),\n  });\n  const router = result.current;\n\n  expect(router).toBeInstanceOf(Object);\n  expect(router.parser).toBe(newParser);\n  expect(router.hook).toBe(hook);\n});\n\nit(\"accepts `ssrPath` and `ssrSearch` params\", () => {\n  const { result } = renderHook(() => useRouter(), {\n    wrapper: (props) => (\n      <Router ssrPath=\"/users\" ssrSearch=\"a=b&c=d\">\n        {props.children}\n      </Router>\n    ),\n  });\n\n  expect(result.current.ssrPath).toBe(\"/users\");\n  expect(result.current.ssrSearch).toBe(\"a=b&c=d\");\n});\n\nit(\"can extract `ssrSearch` from `ssrPath` after the '?' symbol\", () => {\n  let ssrPath: string | undefined = \"/no-search\";\n  let ssrSearch: string | undefined = undefined;\n\n  const { result, rerender } = renderHook(() => useRouter(), {\n    wrapper: (props) => (\n      <Router ssrPath={ssrPath} ssrSearch={ssrSearch}>\n        {props.children}\n      </Router>\n    ),\n  });\n\n  expect(result.current.ssrPath).toBe(\"/no-search\");\n  expect(result.current.ssrSearch).toBe(\"\");\n\n  ssrPath = \"/with-search?a=b&c=d\";\n  rerender();\n\n  expect(result.current.ssrPath).toBe(\"/with-search\");\n  expect(result.current.ssrSearch).toBe(\"a=b&c=d\");\n\n  ssrSearch = \"x=y&z=w\";\n  rerender();\n  expect(result.current.ssrSearch).toBe(\"a=b&c=d\");\n});\n\nit(\"keeps the ssrSearch undefined if not in SSR mode\", () => {\n  const { result } = renderHook(() => useRouter(), {\n    wrapper: (props) => <Router>{props.children}</Router>,\n  });\n\n  expect(result.current.ssrPath).toBe(undefined);\n  expect(result.current.ssrSearch).toBe(undefined);\n});\n\nit(\"shares one router instance between components\", () => {\n  const routers: any[] = [];\n\n  const RouterGetter = ({ index }: { index: number }) => {\n    const router = useRouter();\n    routers[index] = router;\n    return <div data-testid={`router-${index}`} />;\n  };\n\n  render(\n    <>\n      <RouterGetter index={0} />\n      <RouterGetter index={1} />\n      <RouterGetter index={2} />\n      <RouterGetter index={3} />\n    </>\n  );\n\n  const uniqRouters = [...new Set<DefaultParams>(routers)];\n  expect(uniqRouters.length).toBe(1);\n});\n\ndescribe(\"`base` prop\", () => {\n  it(\"is an empty string by default\", () => {\n    const { result } = renderHook(() => useRouter());\n    expect(result.current.base).toBe(\"\");\n  });\n\n  it(\"can be customized via the `base` prop\", () => {\n    const { result } = renderHook(() => useRouter(), {\n      wrapper: (props) => <Router base=\"/foo\">{props.children}</Router>,\n    });\n    expect(result.current.base).toBe(\"/foo\");\n  });\n\n  it(\"appends provided path to the parent router's base\", () => {\n    const { result } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router base=\"/baz\">\n          <Router base=\"/foo\">\n            <Router base=\"/bar\">{props.children}</Router>\n          </Router>\n        </Router>\n      ),\n    });\n    expect(result.current.base).toBe(\"/baz/foo/bar\");\n  });\n});\n\ndescribe(\"`hook` prop\", () => {\n  it(\"when provided, the router isn't inherited from the parent\", () => {\n    const customHook: BaseLocationHook = () => [\"/foo\", () => {}];\n    const newParser: Parser = () => ({ pattern: /(.*)/, keys: [] });\n\n    const {\n      result: { current: router },\n    } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router base=\"/app\" parser={newParser}>\n          <Router hook={customHook} base=\"/bar\">\n            {props.children}\n          </Router>\n        </Router>\n      ),\n    });\n\n    expect(router.hook).toBe(customHook);\n    expect(router.parser).not.toBe(newParser);\n    expect(router.base).toBe(\"/bar\");\n  });\n});\n\ndescribe(\"`hrefs` prop\", () => {\n  it(\"sets the router's `hrefs` property\", () => {\n    const formatter = () => \"noop\";\n\n    const {\n      result: { current: router },\n    } = renderHook(() => useRouter(), {\n      wrapper: (props) => <Router hrefs={formatter}>{props.children}</Router>,\n    });\n\n    expect(router.hrefs).toBe(formatter);\n  });\n\n  it(\"can infer `hrefs` from the `hook`\", () => {\n    const hookHrefs = () => \"noop\";\n    const hook = (): [string, (v: string) => void] => {\n      return [\"/foo\", () => {}];\n    };\n\n    hook.hrefs = hookHrefs;\n\n    let hrefsRouterOption: ((href: string) => string) | undefined;\n\n    const { rerender, result } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router hook={hook} hrefs={hrefsRouterOption}>\n          {props.children}\n        </Router>\n      ),\n    });\n\n    expect(result.current.hrefs).toBe(hookHrefs);\n\n    // `hrefs` passed directly to the router should take precedence\n    hrefsRouterOption = (href) => \"custom formatter\";\n    rerender();\n\n    expect(result.current.hrefs).toBe(hrefsRouterOption);\n  });\n});\n\ndescribe(\"`aroundNav` prop\", () => {\n  it(\"sets the router's `aroundNav` property\", () => {\n    const aroundNav = () => {};\n\n    const { result } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router aroundNav={aroundNav}>{props.children}</Router>\n      ),\n    });\n\n    expect(result.current.aroundNav).toBe(aroundNav);\n  });\n\n  it(\"is inherited from parent router\", () => {\n    const aroundNav = () => {};\n\n    const { result } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router aroundNav={aroundNav}>\n          <Router base=\"/nested\">{props.children}</Router>\n        </Router>\n      ),\n    });\n\n    expect(result.current.aroundNav).toBe(aroundNav);\n  });\n\n  it(\"can be overridden in nested router\", () => {\n    const parentAroundNav = () => {};\n    const childAroundNav = () => {};\n\n    const { result } = renderHook(() => useRouter(), {\n      wrapper: (props) => (\n        <Router aroundNav={parentAroundNav}>\n          <Router aroundNav={childAroundNav}>{props.children}</Router>\n        </Router>\n      ),\n    });\n\n    expect(result.current.aroundNav).toBe(childAroundNav);\n  });\n});\n\nit(\"updates the context when settings are changed\", () => {\n  const state: { renders: number } & Partial<ComponentProps<typeof Router>> = {\n    renders: 0,\n  };\n\n  const Memoized = memo((props) => {\n    const router = useRouter();\n    state.renders++;\n\n    state.hook = router.hook;\n    state.base = router.base;\n\n    return <></>;\n  });\n\n  const { rerender } = render(\n    <Router base=\"/app\">\n      <Memoized />\n    </Router>\n  );\n\n  expect(state.renders).toEqual(1);\n  expect(state.base).toBe(\"/app\");\n\n  rerender(\n    <Router base=\"/app\">\n      <Memoized />\n    </Router>\n  );\n  expect(state.renders).toEqual(1); // nothing changed\n\n  // should re-render the hook\n  const newHook: BaseLocationHook = () => [\"/location\", () => {}];\n  rerender(\n    <Router hook={newHook} base=\"/app\">\n      <Memoized />\n    </Router>\n  );\n  expect(state.renders).toEqual(2);\n  expect(state.base).toEqual(\"/app\");\n  expect(state.hook).toEqual(newHook);\n\n  // should update the context when the base changes as well\n  rerender(\n    <Router hook={newHook} base=\"\">\n      <Memoized />\n    </Router>\n  );\n  expect(state.renders).toEqual(3);\n  expect(state.base).toEqual(\"\");\n  expect(state.hook).toEqual(newHook);\n\n  // the last check that the router context is stable during re-renders\n  rerender(\n    <Router hook={newHook} base=\"\">\n      <Memoized />\n    </Router>\n  );\n  expect(state.renders).toEqual(3); // nothing changed\n});\n"
  },
  {
    "path": "packages/wouter/test/setup.ts",
    "content": "import { GlobalRegistrator } from \"@happy-dom/global-registrator\";\nimport { expect } from \"bun:test\";\nimport * as matchers from \"@testing-library/jest-dom/matchers\";\n\n// Register happy-dom globals (document, window, etc.)\nGlobalRegistrator.register({\n  url: \"https://wouter.dev\",\n  width: 1024,\n  height: 768,\n});\n\n// Extend Bun's expect with jest-dom matchers\n(expect as any).extend(matchers);\n\n/**\n * Runs a function with `location` temporarily removed from globalThis.\n * Simulates pure Node.js SSR environment for testing.\n */\nexport const withoutLocation = <T>(fn: () => T): T => {\n  const original = globalThis.location;\n  // @ts-expect-error - intentionally removing location\n  delete globalThis.location;\n  try {\n    return fn();\n  } finally {\n    globalThis.location = original;\n  }\n};\n"
  },
  {
    "path": "packages/wouter/test/ssr.test.tsx",
    "content": "import { test, expect, describe } from \"bun:test\";\nimport { renderToStaticMarkup } from \"react-dom/server\";\nimport {\n  Route,\n  Router,\n  useRoute,\n  Link,\n  Redirect,\n  useSearch,\n  useLocation,\n  SsrContext,\n} from \"../src/index.js\";\nimport { withoutLocation } from \"./setup.js\";\n\ndescribe(\"server-side rendering\", () => {\n  test(\"works via `ssrPath` prop\", () => {\n    const App = () => (\n      <Router ssrPath=\"/users/baz\">\n        <Route path=\"/users/baz\">foo</Route>\n        <Route path=\"/users/:any*\">bar</Route>\n        <Route path=\"/users/:id\">{(params) => params.id}</Route>\n        <Route path=\"/about\">should not be rendered</Route>\n      </Router>\n    );\n\n    const rendered = renderToStaticMarkup(<App />);\n    expect(rendered).toBe(\"foobarbaz\");\n  });\n\n  test(\"supports hook-based routes\", () => {\n    const HookRoute = () => {\n      const [match, params] = useRoute(\"/pages/:name\");\n      return <>{match ? `Welcome to ${params.name}!` : \"Not Found!\"}</>;\n    };\n\n    const App = () => (\n      <Router ssrPath=\"/pages/intro\">\n        <HookRoute />\n      </Router>\n    );\n\n    const rendered = renderToStaticMarkup(<App />);\n    expect(rendered).toBe(\"Welcome to intro!\");\n  });\n\n  test(\"renders valid and accessible link elements\", () => {\n    const App = () => (\n      <Router ssrPath=\"/\">\n        <Link href=\"/users/1\" title=\"Profile\">\n          Mark\n        </Link>\n      </Router>\n    );\n\n    const rendered = renderToStaticMarkup(<App />);\n    expect(rendered).toBe(`<a title=\"Profile\" href=\"/users/1\">Mark</a>`);\n  });\n\n  test(\"renders redirects however they have effect only on a client-side\", () => {\n    const App = () => (\n      <Router ssrPath=\"/\">\n        <Route path=\"/\">\n          <Redirect to=\"/foo\" />\n        </Route>\n\n        <Route path=\"/foo\">You won't see that in SSR page</Route>\n      </Router>\n    );\n\n    const rendered = renderToStaticMarkup(<App />);\n    expect(rendered).toBe(\"\");\n  });\n\n  test(\"update ssr context\", () => {\n    const context: SsrContext = {};\n    const App = () => (\n      <Router ssrPath=\"/\" ssrContext={context}>\n        <Route path=\"/\">\n          <Redirect to=\"/foo\" />\n        </Route>\n      </Router>\n    );\n\n    renderToStaticMarkup(<App />);\n    expect(context.redirectTo).toBe(\"/foo\");\n\n    // Clean up - reset context to prevent state leakage\n    delete context.redirectTo;\n  });\n\n  describe(\"rendering with given search string\", () => {\n    test(\"allows to override search string\", () => {\n      const App = () => {\n        const search = useSearch();\n        const [location] = useLocation();\n\n        return (\n          <>\n            {location} filter by {search}\n          </>\n        );\n      };\n\n      const rendered = renderToStaticMarkup(\n        <Router ssrPath=\"/catalog\" ssrSearch=\"sort=created_at\">\n          <App />\n        </Router>\n      );\n\n      expect(rendered).toBe(\"/catalog filter by sort=created_at\");\n    });\n\n    test(\"doesn't break useSearch hook if not specified\", () => {\n      const PrintSearch = () => <>{useSearch()}</>;\n\n      const rendered = withoutLocation(() =>\n        renderToStaticMarkup(\n          <Router ssrPath=\"/\">\n            <PrintSearch />\n          </Router>\n        )\n      );\n\n      expect(rendered).toBe(\"\");\n    });\n\n    test(\"works with empty ssrSearch\", () => {\n      const PrintSearch = () => <>{useSearch()}</>;\n\n      const rendered = withoutLocation(() =>\n        renderToStaticMarkup(\n          <Router ssrPath=\"/\" ssrSearch=\"\">\n            <PrintSearch />\n          </Router>\n        )\n      );\n\n      expect(rendered).toBe(\"\");\n    });\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/switch.test.tsx",
    "content": "import { it, expect, afterEach } from \"bun:test\";\n\nimport { Router, Route, Switch } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nimport { render, act, cleanup } from \"@testing-library/react\";\nimport { PropsWithChildren, ReactElement, JSX } from \"react\";\n\n// Clean up after each test to avoid DOM pollution\nafterEach(cleanup);\n\nconst raf = () => new Promise((resolve) => requestAnimationFrame(resolve));\n\nconst testRouteRender = (initialPath: string, jsx: ReactElement) => {\n  return render(\n    <Router hook={memoryLocation({ path: initialPath }).hook}>{jsx}</Router>\n  );\n};\n\nit(\"works well when nothing is provided\", () => {\n  const { container } = testRouteRender(\"/users/12\", <Switch>{null}</Switch>);\n  // When Switch has no matching children, it renders null, so container should be empty\n  expect(container).toBeEmptyDOMElement();\n});\n\nit(\"always renders no more than 1 matched children\", () => {\n  const { container } = testRouteRender(\n    \"/users/12\",\n    <Switch>\n      <Route path=\"/users/home\">\n        <h1 />\n      </Route>\n      <Route path=\"/users/:id\">\n        <h2 />\n      </Route>\n      <Route path=\"/users/:rest*\">\n        <h3 />\n      </Route>\n    </Switch>\n  );\n\n  // Should only render the h2 that matches /users/:id\n  expect(container.querySelectorAll(\"h1, h2, h3\")).toHaveLength(1);\n  expect(container.querySelector(\"h2\")).toBeInTheDocument();\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n  expect(container.querySelector(\"h3\")).not.toBeInTheDocument();\n});\n\nit(\"ignores mixed children\", () => {\n  const { container } = testRouteRender(\n    \"/users\",\n    <Switch>\n      Here is a<Route path=\"/users\">route</Route>\n      route\n    </Switch>\n  );\n\n  // Should only render the route content, ignoring text nodes\n  expect(container).toHaveTextContent(\"route\");\n  // The text \"Here is a\" and \"route\" outside the Route should be ignored\n  expect(container.textContent).toBe(\"route\");\n});\n\nit(\"ignores falsy children\", () => {\n  const { container } = testRouteRender(\n    \"/users\",\n    <Switch>\n      {\"\"}\n      {false}\n      {null}\n      {undefined}\n      <Route path=\"/users\">route</Route>\n    </Switch>\n  );\n\n  // Should only render the route content\n  expect(container).toHaveTextContent(\"route\");\n  expect(container.textContent).toBe(\"route\");\n});\n\nit(\"matches regular components as well\", () => {\n  const Dummy = (props: PropsWithChildren<{ path: string }>) => (\n    <>{props.children}</>\n  );\n\n  const { container } = testRouteRender(\n    \"/\",\n    <Switch>\n      <Dummy path=\"/\">Component</Dummy>\n      <b>Bold</b>\n    </Switch>\n  );\n\n  // Should render the Dummy component content\n  expect(container).toHaveTextContent(\"Component\");\n  expect(container.querySelector(\"b\")).not.toBeInTheDocument();\n});\n\nit(\"allows to specify which routes to render via `location` prop\", () => {\n  const { container } = testRouteRender(\n    \"/something-different\",\n    <Switch location=\"/users\">\n      <Route path=\"/users\">route</Route>\n    </Switch>\n  );\n\n  // Should render based on the location prop, not the actual path\n  expect(container).toHaveTextContent(\"route\");\n});\n\nit(\"always ensures the consistency of inner routes rendering\", async () => {\n  history.replaceState(null, \"\", \"/foo/bar\");\n\n  const { unmount } = render(\n    <Switch>\n      <Route path=\"/foo/:id\">\n        {(params) => {\n          if (!params)\n            throw new Error(\"Render prop is called with falsy params!\");\n          return null;\n        }}\n      </Route>\n    </Switch>\n  );\n\n  await act(async () => {\n    await raf();\n    history.pushState(null, \"\", \"/\");\n  });\n\n  unmount();\n});\n\nit(\"supports catch-all routes with wildcard segments\", async () => {\n  const { container } = testRouteRender(\n    \"/something-different\",\n    <Switch>\n      <Route path=\"/users\">\n        <h1 />\n      </Route>\n      <Route path=\"/:anything*\">\n        <h2 />\n      </Route>\n    </Switch>\n  );\n\n  // Should match the catch-all route\n  expect(container.querySelectorAll(\"h1, h2\")).toHaveLength(1);\n  expect(container.querySelector(\"h2\")).toBeInTheDocument();\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n});\n\nit(\"uses a route without a path prop as a fallback\", async () => {\n  const { container } = testRouteRender(\n    \"/something-different\",\n    <Switch>\n      <Route path=\"/users\">\n        <h1 />\n      </Route>\n      <Route>\n        <h2 />\n      </Route>\n    </Switch>\n  );\n\n  // Should match the fallback route (no path)\n  expect(container.querySelectorAll(\"h1, h2\")).toHaveLength(1);\n  expect(container.querySelector(\"h2\")).toBeInTheDocument();\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n});\n\nit(\"correctly handles arrays as children\", async () => {\n  const { container } = testRouteRender(\n    \"/in-array-3\",\n    <Switch>\n      {[1, 2, 3].map((i) => {\n        const H = `h${i}` as keyof JSX.IntrinsicElements;\n        return (\n          <Route key={i} path={\"/in-array-\" + i}>\n            <H />\n          </Route>\n        );\n      })}\n      <Route>\n        <h4 />\n      </Route>\n    </Switch>\n  );\n\n  // Should match the third route (/in-array-3)\n  expect(container.querySelectorAll(\"h1, h2, h3, h4\")).toHaveLength(1);\n  expect(container.querySelector(\"h3\")).toBeInTheDocument();\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n  expect(container.querySelector(\"h2\")).not.toBeInTheDocument();\n  expect(container.querySelector(\"h4\")).not.toBeInTheDocument();\n});\n\nit(\"correctly handles fragments as children\", async () => {\n  const { container } = testRouteRender(\n    \"/in-fragment-2\",\n    <Switch>\n      <>\n        {[1, 2, 3].map((i) => {\n          const H = `h${i}` as keyof JSX.IntrinsicElements;\n          return (\n            <Route key={i} path={\"/in-fragment-\" + i}>\n              <H />\n            </Route>\n          );\n        })}\n      </>\n      <Route>\n        <h4 />\n      </Route>\n    </Switch>\n  );\n\n  // Should match the second route (/in-fragment-2)\n  expect(container.querySelectorAll(\"h1, h2, h3, h4\")).toHaveLength(1);\n  expect(container.querySelector(\"h2\")).toBeInTheDocument();\n  expect(container.querySelector(\"h1\")).not.toBeInTheDocument();\n  expect(container.querySelector(\"h3\")).not.toBeInTheDocument();\n  expect(container.querySelector(\"h4\")).not.toBeInTheDocument();\n});\n"
  },
  {
    "path": "packages/wouter/test/test-utils.ts",
    "content": "/**\n * Executes a callback and returns a promise that resolve when `hashchange` event is fired.\n * Rejects after `throwAfter` milliseconds.\n */\nexport const waitForHashChangeEvent = async (\n  cb: () => void,\n  throwAfter = 1000\n) =>\n  new Promise<void>((resolve, reject) => {\n    let timeout: ReturnType<typeof setTimeout>;\n\n    const onChange = () => {\n      resolve();\n      clearTimeout(timeout);\n      window.removeEventListener(\"hashchange\", onChange);\n    };\n\n    window.addEventListener(\"hashchange\", onChange);\n    cb();\n\n    timeout = setTimeout(() => {\n      reject(new Error(\"Timed out: `hashchange` event did not fire!\"));\n      window.removeEventListener(\"hashchange\", onChange);\n    }, throwAfter);\n  });\n"
  },
  {
    "path": "packages/wouter/test/use-browser-location.test-d.ts",
    "content": "import { test, describe, expectTypeOf } from \"bun:test\";\nimport {\n  useBrowserLocation,\n  useSearch,\n  useHistoryState,\n} from \"../src/use-browser-location.js\";\n\nconst assertType = <T>(_value: T): void => {};\n\ndescribe(\"useBrowserLocation\", () => {\n  test(\"should return string, function tuple\", () => {\n    const [loc, navigate] = useBrowserLocation();\n\n    assertType<string>(loc);\n    assertType<Function>(navigate);\n  });\n\n  test(\"should return `navigate` function with `path` and `options` parameters\", () => {\n    const [, navigate] = useBrowserLocation();\n\n    assertType(navigate(\"/path\"));\n    assertType(navigate(\"\"));\n\n    // @ts-expect-error\n    assertType(navigate());\n    // @ts-expect-error\n    assertType(navigate(null));\n\n    assertType(navigate(\"/path\", { replace: true }));\n    // @ts-expect-error\n    assertType(navigate(\"/path\", { unknownOption: true }));\n  });\n\n  test(\"should support `ssrPath` option\", () => {\n    assertType(useBrowserLocation({ ssrPath: \"/something\" }));\n    // @ts-expect-error\n    assertType(useBrowserLocation({ foo: \"bar\" }));\n  });\n});\n\ndescribe(\"useSearch\", () => {\n  test(\"should return string\", () => {\n    type Search = ReturnType<typeof useSearch>;\n    const search = useSearch();\n\n    assertType<string>(search);\n    const allowedSearchValues: Search[] = [\"\", \"?leading\", \"no-?-sign\"];\n  });\n});\n\ndescribe(\"useHistoryState\", () => {\n  test(\"should support generics\", () => {\n    type TestCase = { hello: string };\n    const state = useHistoryState<TestCase>();\n\n    expectTypeOf(state).toEqualTypeOf<TestCase>();\n  });\n\n  test(\"should fallback to any when type doesn't provided\", () => {\n    const state = useHistoryState();\n\n    expectTypeOf(state).toEqualTypeOf<any>();\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/use-browser-location.test.tsx",
    "content": "import { useEffect } from \"react\";\nimport { test, expect, describe, beforeEach, afterEach } from \"bun:test\";\nimport { renderHook, act, waitFor, cleanup } from \"@testing-library/react\";\nimport {\n  useBrowserLocation,\n  navigate,\n  useSearch,\n  useHistoryState,\n} from \"../src/use-browser-location.js\";\n\nafterEach(cleanup);\n\ntest(\"returns a pair [value, update]\", () => {\n  const { result, unmount } = renderHook(() => useBrowserLocation());\n  const [value, update] = result.current;\n\n  expect(typeof value).toBe(\"string\");\n  expect(typeof update).toBe(\"function\");\n  unmount();\n});\n\ndescribe(\"`value` first argument\", () => {\n  beforeEach(() => history.replaceState(null, \"\", \"/\"));\n\n  test(\"reflects the current pathname\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    expect(result.current[0]).toBe(\"/\");\n    unmount();\n  });\n\n  test(\"reacts to `pushState` / `replaceState`\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n\n    act(() => history.pushState(null, \"\", \"/foo\"));\n    expect(result.current[0]).toBe(\"/foo\");\n\n    act(() => history.replaceState(null, \"\", \"/bar\"));\n    expect(result.current[0]).toBe(\"/bar\");\n    unmount();\n  });\n\n  test(\"supports history.back() navigation\", async () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n\n    act(() => history.pushState(null, \"\", \"/foo\"));\n    await waitFor(() => expect(result.current[0]).toBe(\"/foo\"));\n\n    act(() => {\n      history.back();\n    });\n\n    // Workaround for happy-dom: manually dispatch popstate event\n    // happy-dom doesn't fully implement history.back() popstate events\n    act(() => {\n      const popstateEvent = new PopStateEvent(\"popstate\", {\n        state: history.state,\n      });\n      window.dispatchEvent(popstateEvent);\n    });\n\n    await waitFor(() => expect(result.current[0]).toBe(\"/\"), { timeout: 1000 });\n    unmount();\n  });\n\n  test(\"supports history state\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    const { result: state, unmount: unmountState } = renderHook(() =>\n      useHistoryState()\n    );\n\n    const navigate = result.current[1];\n\n    act(() => navigate(\"/path\", { state: { hello: \"world\" } }));\n\n    expect(state.current).toStrictEqual({ hello: \"world\" });\n\n    unmount();\n    unmountState();\n  });\n\n  test(\"uses fail-safe escaping\", () => {\n    const { result } = renderHook(() => useBrowserLocation());\n    const navigate = result.current[1];\n\n    act(() => navigate(\"/%not-valid\"));\n    expect(result.current[0]).toBe(\"/%not-valid\");\n\n    act(() => navigate(\"/99%\"));\n    expect(result.current[0]).toBe(\"/99%\");\n  });\n});\n\ndescribe(\"`useSearch` hook\", () => {\n  beforeEach(() => history.replaceState(null, \"\", \"/\"));\n\n  test(\"allows to get current search string\", () => {\n    const { result: searchResult } = renderHook(() => useSearch());\n    act(() => navigate(\"/foo?hello=world&whats=up\"));\n\n    expect(searchResult.current).toBe(\"?hello=world&whats=up\");\n  });\n\n  test(\"returns empty string when there is no search string\", () => {\n    const { result: searchResult } = renderHook(() => useSearch());\n\n    expect(searchResult.current).toBe(\"\");\n\n    act(() => navigate(\"/foo\"));\n    expect(searchResult.current).toBe(\"\");\n\n    act(() => navigate(\"/foo? \"));\n    expect(searchResult.current).toBe(\"\");\n  });\n\n  test(\"does not re-render when only pathname is changed\", () => {\n    // count how many times each hook is rendered\n    const locationRenders = { current: 0 };\n    const searchRenders = { current: 0 };\n\n    // count number of rerenders for each hook\n    renderHook(() => {\n      useEffect(() => {\n        locationRenders.current += 1;\n      });\n      return useBrowserLocation();\n    });\n\n    renderHook(() => {\n      useEffect(() => {\n        searchRenders.current += 1;\n      });\n      return useSearch();\n    });\n\n    expect(locationRenders.current).toBe(1);\n    expect(searchRenders.current).toBe(1);\n\n    act(() => navigate(\"/foo\"));\n\n    expect(locationRenders.current).toBe(2);\n    expect(searchRenders.current).toBe(1);\n\n    act(() => navigate(\"/foo?bar\"));\n    expect(locationRenders.current).toBe(2); // no re-render\n    expect(searchRenders.current).toBe(2);\n\n    act(() => navigate(\"/baz?bar\"));\n    expect(locationRenders.current).toBe(3); // no re-render\n    expect(searchRenders.current).toBe(2);\n  });\n});\n\ndescribe(\"`update` second parameter\", () => {\n  test(\"rerenders the component\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    const update = result.current[1];\n\n    act(() => update(\"/about\"));\n    expect(result.current[0]).toBe(\"/about\");\n    unmount();\n  });\n\n  test(\"changes the current location\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    const update = result.current[1];\n\n    act(() => update(\"/about\"));\n    expect(location.pathname).toBe(\"/about\");\n    unmount();\n  });\n\n  test(\"saves a new entry in the History object\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    const update = result.current[1];\n\n    const histBefore = history.length;\n    act(() => update(\"/about\"));\n\n    expect(history.length).toBe(histBefore + 1);\n    unmount();\n  });\n\n  test(\"replaces last entry with a new entry in the History object\", () => {\n    const { result, unmount } = renderHook(() => useBrowserLocation());\n    const update = result.current[1];\n\n    const histBefore = history.length;\n    act(() => update(\"/foo\", { replace: true }));\n\n    expect(history.length).toBe(histBefore);\n    expect(location.pathname).toBe(\"/foo\");\n    unmount();\n  });\n\n  test(\"stays the same reference between re-renders (function ref)\", () => {\n    const { result, rerender, unmount } = renderHook(() =>\n      useBrowserLocation()\n    );\n\n    const updateWas = result.current[1];\n    rerender();\n    const updateNow = result.current[1];\n\n    expect(updateWas).toBe(updateNow);\n    unmount();\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/use-hash-location.test-d.ts",
    "content": "import { test, describe, expectTypeOf } from \"bun:test\";\nimport { useHashLocation, navigate } from \"../src/use-hash-location.js\";\nimport { BaseLocationHook } from \"../src/index.js\";\n\nconst assertType = <T>(_value: T): void => {};\n\ntest(\"is a location hook\", () => {\n  expectTypeOf(useHashLocation).toMatchTypeOf<BaseLocationHook>();\n  expectTypeOf(useHashLocation()).toMatchTypeOf<[string, Function]>();\n});\n\ntest(\"accepts a `ssrPath` path option\", () => {\n  useHashLocation({ ssrPath: \"/foo\" });\n  useHashLocation({ ssrPath: \"\" });\n\n  // @ts-expect-error\n  useHashLocation({ base: 123 });\n  // @ts-expect-error\n  useHashLocation({ unknown: \"/base\" });\n});\n\ndescribe(\"`navigate` function\", () => {\n  test(\"accepts an arbitrary `state` option\", () => {\n    navigate(\"/object\", { state: { foo: \"bar\" } });\n    navigate(\"/symbol\", { state: Symbol(\"foo\") });\n    navigate(\"/string\", { state: \"foo\" });\n    navigate(\"/undef\", { state: undefined });\n  });\n\n  test(\"returns nothing\", () => {\n    assertType<void>(navigate(\"/foo\"));\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/use-hash-location.test.tsx",
    "content": "import { test, expect, beforeEach, mock } from \"bun:test\";\nimport { renderHook, render, act } from \"@testing-library/react\";\nimport { renderToStaticMarkup } from \"react-dom/server\";\n\nimport { Router, Route, useLocation, Link } from \"../src/index.js\";\nimport { useHashLocation } from \"../src/use-hash-location.js\";\n\nimport { waitForHashChangeEvent } from \"./test-utils\";\nimport { ReactNode, useSyncExternalStore } from \"react\";\n\nbeforeEach(() => {\n  history.replaceState(null, \"\", \"/\");\n  location.hash = \"\";\n});\n\ntest(\"gets current location from `location.hash`\", () => {\n  location.hash = \"/app/users\";\n  const { result } = renderHook(() => useHashLocation());\n  const [path] = result.current;\n\n  expect(path).toBe(\"/app/users\");\n});\n\ntest(\"isn't sensitive to leading slash\", () => {\n  location.hash = \"app/users\";\n  const { result } = renderHook(() => useHashLocation());\n  const [path] = result.current;\n\n  expect(path).toBe(\"/app/users\");\n});\n\ntest(\"rerenders when hash changes\", async () => {\n  const { result } = renderHook(() => useHashLocation());\n\n  expect(result.current[0]).toBe(\"/\");\n\n  await act(async () => {\n    await waitForHashChangeEvent(() => {\n      location.hash = \"/app/users\";\n    });\n  });\n\n  expect(result.current[0]).toBe(\"/app/users\");\n});\n\ntest(\"changes current hash when navigation is performed\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  act(() => {\n    navigate(\"/app/users\");\n  });\n  expect(location.hash).toBe(\"#/app/users\");\n});\n\ntest(\"should not rerender when pathname changes\", () => {\n  let renderCount = 0;\n  location.hash = \"/app\";\n\n  const { result } = renderHook(() => {\n    useHashLocation();\n    return ++renderCount;\n  });\n\n  expect(result.current).toBe(1);\n  history.replaceState(null, \"\", \"/foo?bar#/app\");\n\n  expect(result.current).toBe(1);\n});\n\ntest(\"does not change anything besides the hash when doesn't contain ? symbol\", () => {\n  history.replaceState(null, \"\", \"/foo?bar#/app\");\n\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  act(() => {\n    navigate(\"/settings/general\");\n  });\n  expect(location.pathname).toBe(\"/foo\");\n  expect(location.search).toBe(\"?bar\");\n});\n\ntest(\"changes search and hash when contains ? symbol\", () => {\n  history.replaceState(null, \"\", \"/foo?bar#/app\");\n\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  act(() => {\n    navigate(\"/abc?def\");\n  });\n  expect(location.pathname).toBe(\"/foo\");\n  expect(location.search).toBe(\"?def\");\n  expect(location.hash).toBe(\"#/abc\");\n});\n\ntest(\"creates a new history entry when navigating\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  const initialLength = history.length;\n  act(() => {\n    navigate(\"/about\");\n  });\n  expect(history.length).toBe(initialLength + 1);\n});\n\ntest(\"supports `state` option when navigating\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  act(() => {\n    navigate(\"/app/users\", { state: { hello: \"world\" } });\n  });\n  expect(history.state).toStrictEqual({ hello: \"world\" });\n});\n\ntest(\"never changes reference to `navigate` between rerenders\", () => {\n  const { result, rerender } = renderHook(() => useHashLocation());\n\n  const updateWas = result.current[1];\n  rerender();\n\n  expect(result.current[1]).toBe(updateWas);\n});\n\ntest(\"uses `ssrPath` when rendered on the server\", () => {\n  const App = () => {\n    const [path] = useHashLocation({ ssrPath: \"/hello-from-server\" });\n    return <>{path}</>;\n  };\n\n  const rendered = renderToStaticMarkup(<App />);\n  expect(rendered).toBe(\"/hello-from-server\");\n});\n\ntest(\"is not sensitive to leading / or # when navigating\", async () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  await act(async () => {\n    await waitForHashChangeEvent(() => navigate(\"look-ma-no-slashes\"));\n  });\n  expect(location.hash).toBe(\"#/look-ma-no-slashes\");\n  expect(result.current[0]).toBe(\"/look-ma-no-slashes\");\n\n  await act(async () => {\n    await waitForHashChangeEvent(() => navigate(\"#/look-ma-no-hashes\"));\n  });\n  expect(location.hash).toBe(\"#/look-ma-no-hashes\");\n  expect(result.current[0]).toBe(\"/look-ma-no-hashes\");\n});\n\ntest(\"works even if `hashchange` listeners are called asynchronously \", async () => {\n  const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));\n\n  // we want `hashchange` to stop invoking listeners before it reaches the\n  // outer <Route path=\"/a\" />. this is done to simulate a situation when\n  // `hashchange` listeners are called asynchrounously\n  //\n  // per https://github.com/whatwg/html/issues/1792\n  // some browsers fire `hashchange` and `popstate` asynchronously, so\n  // when the event listeners are called, a microtask can be scheduled in between,\n  // and we may end up with a teared state. inner components subscribe to `hashchange`\n  // earlier so they may render even though their parent route does not match\n  const subscribeToHashchange = (cb: () => void) => {\n    const fn = (event: HashChangeEvent) => {\n      event.stopImmediatePropagation();\n      cb();\n    };\n\n    window.addEventListener(\"hashchange\", fn);\n    return () => window.removeEventListener(\"hashchange\", fn);\n  };\n\n  const InterceptAndStopHashchange = ({\n    children,\n  }: {\n    children: ReactNode;\n  }) => {\n    useSyncExternalStore(subscribeToHashchange, () => true);\n    return <>{children}</>;\n  };\n\n  const paths: string[] = [];\n\n  // keep track of rendered paths\n  const LogLocations = () => {\n    paths.push(useLocation()[0]);\n    return null;\n  };\n\n  act(() => {\n    location.hash = \"#/a\";\n  });\n\n  const { unmount } = render(\n    <Router hook={useHashLocation}>\n      <Route path=\"/a\">\n        <InterceptAndStopHashchange>\n          <LogLocations />\n        </InterceptAndStopHashchange>\n      </Route>\n    </Router>\n  );\n\n  await act(async () => {\n    location.hash = \"#/b\";\n    // wait for all `hashchange` listeners to be called\n    // can't use `waitForHashChangeEvent` here because it gets cancelled along the way\n    await nextTick();\n  });\n\n  // paths should not contain \"b\", because the outer route\n  // does not match, so inner component should not be rendered\n  expect(paths).toEqual([\"/a\"]);\n  unmount();\n});\n\ntest(\"defines a custom way of rendering link hrefs\", () => {\n  const { getByTestId } = render(\n    <Router hook={useHashLocation}>\n      <Link href=\"/app\" data-testid=\"link\" />\n    </Router>\n  );\n\n  expect(getByTestId(\"link\")).toHaveAttribute(\"href\", \"#/app\");\n});\n\ntest(\"handles navigation with data: protocol\", async () => {\n  const originalHref = location.href;\n  location.href = \"data:text/html,content\";\n\n  expect(location.protocol).toBe(\"data:\");\n\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n  const initialHistoryLength = history.length;\n\n  await waitForHashChangeEvent(() => {\n    navigate(\"/new-path\");\n  });\n\n  expect(location.hash).toBe(\"#/new-path\");\n  expect(history.length).toBe(initialHistoryLength + 1);\n\n  await waitForHashChangeEvent(() => {\n    navigate(\"/another-path\", { replace: true });\n  });\n\n  expect(location.hash).toBe(\"#/another-path\");\n  expect(history.length).toBe(initialHistoryLength + 1);\n\n  location.href = originalHref;\n});\n\ntest(\"interacts properly with the history stack\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  // case: replace, expect no history stack changes\n  const historyStackCountBeforeReplace = history.length;\n  act(() => {\n    navigate(\"/app/users\", { replace: true });\n  });\n  expect(location.hash).toBe(\"#/app/users\");\n  expect(history.length).toBe(historyStackCountBeforeReplace);\n\n  // case: push, expect history stack increase by 1\n  const historyStackCountBeforePush = history.length;\n  act(() => {\n    navigate(\"/app/users/2\");\n  });\n  expect(location.hash).toBe(\"#/app/users/2\");\n  expect(history.length).toBe(historyStackCountBeforePush + 1);\n});\n\ntest(\"dispatches hashchange event when options.replace is true\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  const hashChangeFn = mock();\n  addEventListener(\"hashchange\", hashChangeFn);\n\n  act(() => {\n    navigate(\"/foo/bar\", { replace: true });\n  });\n  expect(hashChangeFn).toBeCalled();\n\n  removeEventListener(\"hashchange\", hashChangeFn);\n});\n\ntest(\"detects history change when navigate with options.replace is called\", async () => {\n  const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));\n\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  const newPath = \"/foo/bar/baz\";\n  act(() => {\n    navigate(newPath, { replace: true });\n  });\n  await nextTick();\n  expect(result.current[0]).toBe(newPath);\n});\n\ntest(\"uses string URLs as hashchange event payload\", () => {\n  const { result } = renderHook(() => useHashLocation());\n  const [, navigate] = result.current;\n\n  const relativeOldPath = \"/foo\";\n  const relativeNewPath = \"/foo/bar/#hash\";\n  const baseURL = \"https://wouter.dev/#\";\n\n  act(() => {\n    navigate(relativeOldPath);\n  });\n\n  let changeEvent = new HashChangeEvent(\"hashchange\");\n  const hashChangeFn = (event: HashChangeEvent) => {\n    changeEvent = event;\n  };\n\n  addEventListener(\"hashchange\", hashChangeFn);\n\n  act(() => {\n    navigate(relativeNewPath);\n  });\n  expect(changeEvent?.newURL).toBe(`${baseURL}${relativeNewPath}`);\n  expect(changeEvent?.oldURL).toBe(`${baseURL}${relativeOldPath}`);\n\n  removeEventListener(\"hashchange\", hashChangeFn);\n});\n"
  },
  {
    "path": "packages/wouter/test/use-location.test.tsx",
    "content": "import { ComponentProps, ReactNode } from \"react\";\nimport { it, expect, describe, beforeEach } from \"bun:test\";\nimport { renderHook, act } from \"@testing-library/react\";\nimport { Router, useLocation } from \"../src/index.js\";\nimport {\n  useBrowserLocation,\n  navigate as browserNavigation,\n} from \"../src/use-browser-location.js\";\n\nimport {\n  useHashLocation,\n  navigate as hashNavigation,\n} from \"../src/use-hash-location.js\";\nimport { waitForHashChangeEvent } from \"./test-utils\";\n\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nfunction createContainer(\n  options: Omit<ComponentProps<typeof Router>, \"children\"> = {}\n) {\n  return ({ children }: { children: ReactNode }) => (\n    <Router {...options}>{children}</Router>\n  );\n}\n\nconst memory = memoryLocation({ record: true });\n\ndescribe.each([\n  {\n    name: \"useBrowserLocation\",\n    hook: useBrowserLocation,\n    location: () => location.pathname,\n    navigate: browserNavigation,\n    act,\n    clear: () => {\n      history.replaceState(null, \"\", \"/\");\n    },\n  },\n  {\n    name: \"useHashLocation\",\n    hook: useHashLocation,\n    location: () => \"/\" + location.hash.replace(/^#?\\/?/, \"\"),\n    navigate: hashNavigation,\n    act: (cb: () => void) => waitForHashChangeEvent(() => act(cb)),\n    clear: () => {\n      location.hash = \"\";\n      history.replaceState(null, \"\", \"/\");\n    },\n  },\n  {\n    name: \"memoryLocation\",\n    hook: memory.hook,\n    location: () => memory.history.at(-1) ?? \"\",\n    navigate: memory.navigate,\n    act,\n    clear: () => {\n      memory.reset();\n    },\n  },\n])(\"$name\", (stub) => {\n  beforeEach(() => stub.clear());\n\n  it(\"returns a pair [value, update]\", () => {\n    const { result, unmount } = renderHook(() => useLocation(), {\n      wrapper: createContainer({ hook: stub.hook }),\n    });\n    const [value, update] = result.current;\n\n    expect(typeof value).toBe(\"string\");\n    expect(typeof update).toBe(\"function\");\n    unmount();\n  });\n\n  describe(\"`value` first argument\", () => {\n    it(\"returns `/` when URL contains only a basepath\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/app\",\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() => stub.navigate(\"/app\"));\n      expect(result.current[0]).toBe(\"/\");\n      unmount();\n    });\n\n    it(\"basepath should be case-insensitive\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/MyApp\",\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() => stub.navigate(\"/myAPP/users/JohnDoe\"));\n      expect(result.current[0]).toBe(\"/users/JohnDoe\");\n      unmount();\n    });\n\n    it(\"returns an absolute path in case of unmatched base path\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/MyApp\",\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() => stub.navigate(\"/MyOtherApp/users/JohnDoe\"));\n      expect(result.current[0]).toBe(\"~/MyOtherApp/users/JohnDoe\");\n      unmount();\n    });\n\n    it(\"automatically unescapes specials characters\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() =>\n        stub.navigate(\"/пользователи/показать все/101/げんきです\")\n      );\n      expect(result.current[0]).toBe(\n        \"/пользователи/показать все/101/げんきです\"\n      );\n\n      await stub.act(() => stub.navigate(\"/%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"));\n      expect(result.current[0]).toBe(\"/шеллы\");\n      unmount();\n    });\n\n    it(\"can accept unescaped basepaths\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/hello мир\", // basepath is not escaped\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() => stub.navigate(\"/hello%20%D0%BC%D0%B8%D1%80/rel\"));\n      expect(result.current[0]).toBe(\"/rel\");\n\n      unmount();\n    });\n\n    it(\"can accept unescaped basepaths\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/hello%20%D0%BC%D0%B8%D1%80\", // basepath is already escaped\n          hook: stub.hook,\n        }),\n      });\n\n      await stub.act(() => stub.navigate(\"/hello мир/rel\"));\n      expect(result.current[0]).toBe(\"/rel\");\n\n      unmount();\n    });\n  });\n\n  describe(\"`update` second parameter\", () => {\n    it(\"rerenders the component\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({ hook: stub.hook }),\n      });\n      const update = result.current[1];\n\n      await stub.act(() => update(\"/about\"));\n      expect(stub.location()).toBe(\"/about\");\n      unmount();\n    });\n\n    it(\"stays the same reference between re-renders (function ref)\", () => {\n      const { result, rerender, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({ hook: stub.hook }),\n      });\n\n      const updateWas = result.current[1];\n      rerender();\n      const updateNow = result.current[1];\n\n      expect(updateWas).toBe(updateNow);\n      unmount();\n    });\n\n    it(\"supports a basepath\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/app\",\n          hook: stub.hook,\n        }),\n      });\n\n      const update = result.current[1];\n\n      await stub.act(() => update(\"/dashboard\"));\n      expect(stub.location()).toBe(\"/app/dashboard\");\n      unmount();\n    });\n\n    it(\"ignores the '/' basepath\", async () => {\n      const { result, unmount } = renderHook(() => useLocation(), {\n        wrapper: createContainer({\n          base: \"/\",\n          hook: stub.hook,\n        }),\n      });\n\n      const update = result.current[1];\n\n      await stub.act(() => update(\"/dashboard\"));\n      expect(stub.location()).toBe(\"/dashboard\");\n      unmount();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/use-params.test-d.ts",
    "content": "import { test, expectTypeOf } from \"bun:test\";\nimport { useParams } from \"../src/index.js\";\n\ntest(\"does not accept any arguments\", () => {\n  expectTypeOf<typeof useParams>().parameters.toEqualTypeOf<[]>();\n});\n\ntest(\"returns an object with arbitrary parameters\", () => {\n  const params = useParams();\n\n  expectTypeOf(params).toBeObject();\n  expectTypeOf(params.any).toEqualTypeOf<string | undefined>();\n  expectTypeOf(params[0]).toEqualTypeOf<string | undefined>();\n});\n\ntest(\"can infer the type of parameters from the route path\", () => {\n  const params = useParams<\"/app/users/:name?/:id\">();\n\n  expectTypeOf(params).toMatchTypeOf<{\n    0?: string;\n    1?: string;\n    id: string;\n    name?: string;\n  }>();\n});\n\ntest(\"can accept the custom type of parameters as a generic argument\", () => {\n  const params = useParams<{ foo: number; bar?: string }>();\n\n  expectTypeOf(params).toMatchTypeOf<{\n    foo: number;\n    bar?: string;\n  }>();\n\n  //@ts-expect-error\n  return params.notFound;\n});\n"
  },
  {
    "path": "packages/wouter/test/use-params.test.tsx",
    "content": "import { act, renderHook, cleanup } from \"@testing-library/react\";\nimport { test, expect, beforeEach, afterEach } from \"bun:test\";\nimport { useParams, Router, Route, Switch } from \"../src/index.js\";\n\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nbeforeEach(() => history.replaceState(null, \"\", \"/\"));\nafterEach(cleanup);\n\ntest(\"returns empty object when used outside of <Route />\", () => {\n  const { result } = renderHook(() => useParams());\n  expect(result.current).toEqual({});\n});\n\ntest(\"contains a * parameter when used inside an empty <Route />\", () => {\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={memoryLocation({ path: \"/app-2/goods/tees\" }).hook}>\n        <Route>{props.children}</Route>\n      </Router>\n    ),\n  });\n\n  expect(result.current).toEqual({\n    0: \"app-2/goods/tees\",\n    \"*\": \"app-2/goods/tees\",\n  });\n});\n\ntest(\"returns an empty object when there are no params\", () => {\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => <Route path=\"/\">{props.children}</Route>,\n  });\n\n  expect(result.current).toEqual({});\n});\n\ntest(\"contains parameters from the closest parent <Route />\", () => {\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={memoryLocation({ path: \"/app/users/1/maria\" }).hook}>\n        <Route path=\"/app/:foo/*\">\n          <Route path=\"/app/users/:id/:name\">{props.children}</Route>\n        </Route>\n      </Router>\n    ),\n  });\n\n  expect(result.current).toMatchObject({\n    0: \"1\",\n    1: \"maria\",\n    id: \"1\",\n    name: \"maria\",\n  });\n});\n\ntest(\"inherits parameters from parent nested routes\", () => {\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router\n        hook={\n          memoryLocation({ path: \"/dash/users/10/alex/bio/john/summary-1\" })\n            .hook\n        }\n      >\n        <Route path=\"/:page\" nest>\n          <Route path=\"/users/:id/:name\" nest>\n            <Route path=\"/bio/:name/*\">{props.children}</Route>\n          </Route>\n        </Route>\n      </Router>\n    ),\n  });\n\n  expect(result.current).toMatchObject({\n    name: \"john\", // name gets overriden\n    \"*\": \"summary-1\",\n    page: \"dash\",\n    id: \"10\",\n    // number params are overriden\n    0: \"john\",\n    1: \"summary-1\",\n  });\n});\n\ntest(\"rerenders with parameters change\", () => {\n  const { hook, navigate } = memoryLocation({ path: \"/\" });\n\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Route path=\"/:a/:b\">{props.children}</Route>\n      </Router>\n    ),\n  });\n\n  expect(result.current).toBeNull();\n\n  act(() => navigate(\"/posts/all\"));\n  expect(result.current).toMatchObject({\n    0: \"posts\",\n    1: \"all\",\n    a: \"posts\",\n    b: \"all\",\n  });\n\n  act(() => navigate(\"/posts/latest\"));\n  expect(result.current).toMatchObject({\n    0: \"posts\",\n    1: \"latest\",\n    a: \"posts\",\n    b: \"latest\",\n  });\n});\n\ntest(\"extracts parameters of the nested route\", () => {\n  const { hook } = memoryLocation({\n    path: \"/v2/eth/txns\",\n    static: true,\n  });\n\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Route path=\"/:version/:chain?\" nest>\n          {props.children}\n        </Route>\n      </Router>\n    ),\n  });\n\n  expect(result.current).toEqual({\n    0: \"v2\",\n    1: \"eth\",\n    version: \"v2\",\n    chain: \"eth\",\n  });\n});\n\ntest(\"keeps the object ref the same if params haven't changed\", () => {\n  const { hook } = memoryLocation({ path: \"/foo/bar\" });\n\n  const { result, rerender } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Route path=\"/:a/:b/*?\">{props.children}</Route>\n      </Router>\n    ),\n  });\n\n  const firstRenderedParams = result.current;\n  rerender();\n  expect(result.current).toBe(firstRenderedParams);\n});\n\ntest(\"works when the route becomes matching\", () => {\n  const { hook, navigate } = memoryLocation({ path: \"/\" });\n\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Route path=\"/:id\">{props.children}</Route>\n      </Router>\n    ),\n  });\n\n  act(() => navigate(\"/123\"));\n  expect(result.current).toMatchObject({ id: \"123\" });\n});\n\ntest(\"makes the params an empty object, when there are no path params\", () => {\n  const { hook, navigate } = memoryLocation({ path: \"/\" });\n\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Switch>\n          <Route path=\"/posts\">{props.children}</Route>\n          <Route path=\"/posts/:a\">{props.children}</Route>\n        </Switch>\n      </Router>\n    ),\n  });\n\n  act(() => navigate(\"/posts/all\"));\n  act(() => navigate(\"/posts\"));\n  expect(Object.keys(result.current).length).toBe(0);\n});\n\ntest(\"removes route parameters when no longer present in the path\", () => {\n  // Start at a route that has both 'category' and 'page' in its params\n  const { hook, navigate } = memoryLocation({\n    path: \"/products/categories/apple/page/1\",\n  });\n\n  // Render useParams within two routes: one with /page/:page, one without\n  const { result } = renderHook(() => useParams(), {\n    wrapper: (props) => (\n      <Router hook={hook}>\n        <Switch>\n          <Route path=\"/products/categories/:category\">{props.children}</Route>\n          <Route path=\"/products/categories/:category/page/:page\">\n            {props.children}\n          </Route>\n        </Switch>\n      </Router>\n    ),\n  });\n\n  // Initial params should include 'category' and 'page'\n  expect(result.current).toMatchObject({\n    0: \"apple\",\n    1: \"1\",\n    category: \"apple\",\n    page: \"1\",\n  });\n\n  // Navigate to a path that no longer contains the page param\n  act(() => navigate(\"/products/categories/apple\"));\n\n  // The 'page' param should now be removed\n  expect(result.current).toEqual({\n    0: \"apple\",\n    category: \"apple\",\n  });\n});\n"
  },
  {
    "path": "packages/wouter/test/use-route.test-d.ts",
    "content": "import { test, expectTypeOf } from \"bun:test\";\nimport { useRoute } from \"../src/index.js\";\n\nconst assertType = <T>(_value: T): void => {};\n\ntest(\"should only accept strings\", () => {\n  // @ts-expect-error\n  assertType(useRoute(Symbol()));\n  // @ts-expect-error\n  assertType(useRoute());\n  assertType(useRoute(\"/\"));\n});\n\ntest('has a boolean \"match\" result as a first returned value', () => {\n  const [match] = useRoute(\"/\");\n  expectTypeOf(match).toEqualTypeOf<boolean>();\n});\n\ntest(\"returns null as parameters when there was no match\", () => {\n  const [match, params] = useRoute(\"/foo\");\n\n  if (!match) {\n    expectTypeOf(params).toEqualTypeOf<null>();\n  }\n});\n\ntest(\"accepts the type of parameters as a generic argument\", () => {\n  const [match, params] = useRoute<{ id: string; name: string | undefined }>(\n    \"/app/users/:name?/:id\"\n  );\n\n  if (match) {\n    expectTypeOf(params).toEqualTypeOf<{\n      id: string;\n      name: string | undefined;\n    }>();\n  }\n});\n\ntest(\"infers parameters from the route path\", () => {\n  const [, inferedParams] = useRoute(\"/app/users/:name?/:id/*?\");\n\n  if (inferedParams) {\n    expectTypeOf(inferedParams).toMatchTypeOf<{\n      0?: string;\n      1?: string;\n      2?: string;\n      name?: string;\n      id: string;\n      wildcard?: string;\n    }>();\n  }\n});\n"
  },
  {
    "path": "packages/wouter/test/use-route.test.tsx",
    "content": "import { renderHook, act } from \"@testing-library/react\";\nimport { useRoute, Match, Router, RegexRouteParams } from \"../src/index.js\";\nimport { it, expect } from \"bun:test\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nit(\"is case insensitive\", () => {\n  assertRoute(\"/Users\", \"/users\", {});\n  assertRoute(\"/HomePage\", \"/Homepage\", {});\n  assertRoute(\"/Users/:Name\", \"/users/alex\", { 0: \"alex\", Name: \"alex\" });\n});\n\nit(\"supports required segments\", () => {\n  assertRoute(\"/:page\", \"/users\", { 0: \"users\", page: \"users\" });\n  assertRoute(\"/:page\", \"/users/all\", false);\n  assertRoute(\"/:page\", \"/1\", { 0: \"1\", page: \"1\" });\n\n  assertRoute(\"/home/:page/etc\", \"/home/users/etc\", {\n    0: \"users\",\n    page: \"users\",\n  });\n  assertRoute(\"/home/:page/etc\", \"/home/etc\", false);\n\n  assertRoute(\n    \"/root/payments/:id/refunds/:refId\",\n    \"/root/payments/1/refunds/2\",\n    [true, { 0: \"1\", 1: \"2\", id: \"1\", refId: \"2\" }]\n  );\n});\n\nit(\"ignores the trailing slash\", () => {\n  assertRoute(\"/home\", \"/home/\", {});\n  assertRoute(\"/home\", \"/home\", {});\n\n  assertRoute(\"/home/\", \"/home/\", {});\n  assertRoute(\"/home/\", \"/home\", {});\n\n  assertRoute(\"/:page\", \"/users/\", [true, { 0: \"users\", page: \"users\" }]);\n  assertRoute(\"/catalog/:section?\", \"/catalog/\", {\n    0: undefined,\n    section: undefined,\n  });\n});\n\nit(\"supports trailing wildcards\", () => {\n  assertRoute(\"/app/*\", \"/app/\", { 0: \"\", \"*\": \"\" });\n  assertRoute(\"/app/*\", \"/app/dashboard/intro\", {\n    0: \"dashboard/intro\",\n    \"*\": \"dashboard/intro\",\n  });\n  assertRoute(\"/app/*\", \"/app/charges/1\", { 0: \"charges/1\", \"*\": \"charges/1\" });\n});\n\nit(\"supports wildcards in the middle of the pattern\", () => {\n  assertRoute(\"/app/*/settings\", \"/app/users/settings\", {\n    0: \"users\",\n    \"*\": \"users\",\n  });\n  assertRoute(\"/app/*/settings\", \"/app/users/1/settings\", {\n    0: \"users/1\",\n    \"*\": \"users/1\",\n  });\n\n  assertRoute(\"/*/payments/:id\", \"/home/payments/1\", {\n    0: \"home\",\n    1: \"1\",\n    \"*\": \"home\",\n    id: \"1\",\n  });\n  assertRoute(\"/*/payments/:id?\", \"/home/payments\", {\n    0: \"home\",\n    1: undefined,\n    \"*\": \"home\",\n    id: undefined,\n  });\n});\n\nit(\"uses a question mark to define optional segments\", () => {\n  assertRoute(\"/books/:genre/:title?\", \"/books/scifi\", {\n    0: \"scifi\",\n    1: undefined,\n    genre: \"scifi\",\n    title: undefined,\n  });\n  assertRoute(\"/books/:genre/:title?\", \"/books/scifi/dune\", {\n    0: \"scifi\",\n    1: \"dune\",\n    genre: \"scifi\",\n    title: \"dune\",\n  });\n  assertRoute(\"/books/:genre/:title?\", \"/books/scifi/dune/all\", false);\n\n  assertRoute(\"/app/:company?/blog/:post\", \"/app/apple/blog/mac\", {\n    0: \"apple\",\n    1: \"mac\",\n    company: \"apple\",\n    post: \"mac\",\n  });\n\n  assertRoute(\"/app/:company?/blog/:post\", \"/app/blog/mac\", {\n    0: undefined,\n    1: \"mac\",\n    company: undefined,\n    post: \"mac\",\n  });\n});\n\nit(\"supports optional wildcards\", () => {\n  assertRoute(\"/app/*?\", \"/app/blog/mac\", { 0: \"blog/mac\", \"*\": \"blog/mac\" });\n  assertRoute(\"/app/*?\", \"/app\", { 0: undefined, \"*\": undefined });\n  assertRoute(\"/app/*?/dashboard\", \"/app/v1/dashboard\", { 0: \"v1\", \"*\": \"v1\" });\n  assertRoute(\"/app/*?/dashboard\", \"/app/dashboard\", {\n    0: undefined,\n    \"*\": undefined,\n  });\n  assertRoute(\"/app/*?/users/:name\", \"/app/users/karen\", {\n    0: undefined,\n    1: \"karen\",\n    \"*\": undefined,\n    name: \"karen\",\n  });\n});\n\nit(\"supports other characters in segments\", () => {\n  assertRoute(\"/users/:name\", \"/users/1-alex\", { 0: \"1-alex\", name: \"1-alex\" });\n  assertRoute(\"/staff/:name/:bio?\", \"/staff/John Doe 3\", {\n    0: \"John Doe 3\",\n    1: undefined,\n    name: \"John Doe 3\",\n    bio: undefined,\n  });\n  assertRoute(\"/staff/:name/:bio?\", \"/staff/John Doe 3/bio\", {\n    0: \"John Doe 3\",\n    1: \"bio\",\n    name: \"John Doe 3\",\n    bio: \"bio\",\n  });\n\n  assertRoute(\"/users/:name/bio\", \"/users/$102_Kathrine&/bio\", {\n    0: \"$102_Kathrine&\",\n    name: \"$102_Kathrine&\",\n  });\n});\n\nit(\"ignores escaped slashes\", () => {\n  assertRoute(\"/:param/bar\", \"/foo%2Fbar/bar\", {\n    0: \"foo%2Fbar\",\n    param: \"foo%2Fbar\",\n  });\n  assertRoute(\"/:param\", \"/foo%2Fbar%D1%81%D0%B0%D0%BD%D1%8F\", {\n    0: \"foo%2Fbarсаня\",\n    param: \"foo%2Fbarсаня\",\n  });\n});\n\nit(\"supports regex patterns\", () => {\n  assertRoute(/[/]foo/, \"/foo\", {});\n  assertRoute(/[/]([a-z]+)/, \"/bar\", { 0: \"bar\" });\n  assertRoute(/[/]([a-z]+)/, \"/123\", false);\n  assertRoute(/[/](?<param>[a-z]+)/, \"/bar\", { 0: \"bar\", param: \"bar\" });\n  assertRoute(/[/](?<param>[a-z]+)/, \"/123\", false);\n});\n\nit(\"reacts to pattern updates\", () => {\n  const { result, rerender } = renderHook(\n    ({ pattern }: { pattern: string }) => useRoute(pattern),\n    {\n      wrapper: (props) => (\n        <Router\n          hook={\n            memoryLocation({ path: \"/blog/products/40/read-all\", static: true })\n              .hook\n          }\n          {...props}\n        />\n      ),\n      initialProps: { pattern: \"/\" },\n    }\n  );\n\n  expect(result.current).toStrictEqual([false, null]);\n\n  rerender({ pattern: \"/blog/:category/:post/:action\" });\n  expect(result.current).toStrictEqual([\n    true,\n    {\n      0: \"products\",\n      1: \"40\",\n      2: \"read-all\",\n      category: \"products\",\n      post: \"40\",\n      action: \"read-all\",\n    } as any,\n  ]);\n\n  rerender({ pattern: \"/blog/products/:id?/read-all\" });\n  expect(result.current).toStrictEqual([true, { 0: \"40\", id: \"40\" } as any]);\n\n  rerender({ pattern: \"/blog/products/:name\" });\n  expect(result.current).toStrictEqual([false, null]);\n\n  rerender({ pattern: \"/blog/*\" });\n  expect(result.current).toStrictEqual([\n    true,\n    { 0: \"products/40/read-all\", \"*\": \"products/40/read-all\" } as any,\n  ]);\n});\n\nit(\"reacts to location updates\", () => {\n  const { hook, navigate } = memoryLocation();\n\n  const { result } = renderHook(() => useRoute(\"/cities/:city?\"), {\n    wrapper: (props) => <Router hook={hook} {...props} />,\n  });\n\n  expect(result.current).toStrictEqual([false, null]);\n\n  act(() => navigate(\"/cities/berlin\"));\n  expect(result.current).toStrictEqual([true, { 0: \"berlin\", city: \"berlin\" }]);\n\n  act(() => navigate(\"/cities/Tokyo\"));\n  expect(result.current).toStrictEqual([true, { 0: \"Tokyo\", city: \"Tokyo\" }]);\n\n  act(() => navigate(\"/about\"));\n  expect(result.current).toStrictEqual([false, null]);\n\n  act(() => navigate(\"/cities\"));\n  expect(result.current).toStrictEqual([\n    true,\n    { 0: undefined, city: undefined },\n  ]);\n});\n\n/**\n * Assertion helper to test useRoute() return values.\n */\n\nconst assertRoute = (\n  pattern: string | RegExp,\n  location: string,\n  rhs: false | Match | Record<string, string | undefined>\n) => {\n  const { result } = renderHook(() => useRoute(pattern), {\n    wrapper: (props) => (\n      <Router\n        hook={memoryLocation({ path: location, static: true }).hook}\n        {...props}\n      />\n    ),\n  });\n\n  if (rhs === false) {\n    expect(result.current).toStrictEqual([false, null]);\n  } else if (Array.isArray(rhs)) {\n    expect(result.current).toStrictEqual(rhs);\n  } else {\n    expect(result.current).toStrictEqual([true, rhs]);\n  }\n};\n"
  },
  {
    "path": "packages/wouter/test/use-search-params.test.tsx",
    "content": "import { renderHook, act } from \"@testing-library/react\";\nimport { useSearchParams, Router } from \"../src/index.js\";\nimport { navigate } from \"../src/use-browser-location.js\";\nimport { it, expect, beforeEach } from \"bun:test\";\n\nbeforeEach(() => history.replaceState(null, \"\", \"/\"));\n\nit(\"can return browser search params\", () => {\n  history.replaceState(null, \"\", \"/users?active=true\");\n  const { result } = renderHook(() => useSearchParams());\n\n  expect(result.current[0].get(\"active\")).toBe(\"true\");\n});\n\nit(\"can change browser search params\", () => {\n  history.replaceState(null, \"\", \"/users?active=true\");\n  const { result } = renderHook(() => useSearchParams());\n\n  expect(result.current[0].get(\"active\")).toBe(\"true\");\n\n  act(() =>\n    result.current[1]((prev) => {\n      prev.set(\"active\", \"false\");\n      return prev;\n    })\n  );\n\n  expect(result.current[0].get(\"active\")).toBe(\"false\");\n});\n\nit(\"can be customized in the Router\", () => {\n  const customSearchHook = ({ customOption = \"unused\" }) => \"none\";\n\n  const { result } = renderHook(() => useSearchParams(), {\n    wrapper: (props) => {\n      return <Router searchHook={customSearchHook}>{props.children}</Router>;\n    },\n  });\n\n  expect(Array.from(result.current[0].keys())).toEqual([\"none\"]);\n});\n\nit(\"unescapes search string\", () => {\n  const { result: searchResult } = renderHook(() => useSearchParams());\n\n  expect(Array.from(searchResult.current[0].keys()).length).toBe(0);\n\n  act(() => navigate(\"/?nonce=not Found&country=საქართველო\"));\n  expect(searchResult.current[0].get(\"nonce\")).toBe(\"not Found\");\n  expect(searchResult.current[0].get(\"country\")).toBe(\"საქართველო\");\n\n  // question marks\n  act(() => navigate(\"/?вопрос=как дела?\"));\n  expect(searchResult.current[0].get(\"вопрос\")).toBe(\"как дела?\");\n});\n\nit(\"is safe against parameter injection\", () => {\n  history.replaceState(null, \"\", \"/?search=foo%26parameter_injection%3Dbar\");\n  const { result } = renderHook(() => useSearchParams());\n\n  expect(result.current[0].get(\"search\")).toBe(\"foo&parameter_injection=bar\");\n});\n"
  },
  {
    "path": "packages/wouter/test/use-search.test.tsx",
    "content": "import { renderHook, act, cleanup } from \"@testing-library/react\";\nimport { useSearch, Router } from \"../src/index.js\";\nimport { navigate } from \"../src/use-browser-location.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\nimport { test, expect, beforeEach, afterEach } from \"bun:test\";\n\nbeforeEach(() => history.replaceState(null, \"\", \"/\"));\nafterEach(cleanup);\n\ntest(\"returns browser search string\", () => {\n  history.replaceState(null, \"\", \"/users?active=true\");\n  const { result } = renderHook(() => useSearch());\n\n  expect(result.current).toEqual(\"active=true\");\n});\n\ntest(\"can be customized in the Router\", () => {\n  const customSearchHook = ({ customOption = \"unused\" }) => \"none\";\n\n  const { result } = renderHook(() => useSearch(), {\n    wrapper: (props) => {\n      return <Router searchHook={customSearchHook}>{props.children}</Router>;\n    },\n  });\n\n  expect(result.current).toEqual(\"none\");\n});\n\ntest(\"can be customized with memoryLocation\", () => {\n  const { searchHook } = memoryLocation({ path: \"/foo?key=value\" });\n\n  const { result } = renderHook(() => useSearch(), {\n    wrapper: (props) => {\n      return <Router searchHook={searchHook}>{props.children}</Router>;\n    },\n  });\n\n  expect(result.current).toEqual(\"key=value\");\n});\n\ntest(\"can be customized with memoryLocation using search path parameter\", () => {\n  const { searchHook } = memoryLocation({\n    path: \"/foo?key=value\",\n    searchPath: \"foo=bar\",\n  });\n\n  const { result } = renderHook(() => useSearch(), {\n    wrapper: (props) => {\n      return <Router searchHook={searchHook}>{props.children}</Router>;\n    },\n  });\n\n  expect(result.current).toEqual(\"key=value&foo=bar\");\n});\n\ntest(\"auto-inherits searchHook from hook when not explicitly provided\", () => {\n  const { hook } = memoryLocation({ path: \"/foo?key=value\" });\n\n  const { result } = renderHook(() => useSearch(), {\n    wrapper: (props) => {\n      // Only pass hook, not searchHook - it should auto-inherit!\n      return <Router hook={hook}>{props.children}</Router>;\n    },\n  });\n\n  expect(result.current).toEqual(\"key=value\");\n});\n\ntest(\"unescapes search string\", () => {\n  const { result: searchResult } = renderHook(() => useSearch());\n\n  expect(searchResult.current).toBe(\"\");\n\n  act(() => navigate(\"/?nonce=not Found&country=საქართველო\"));\n  expect(searchResult.current).toBe(\"nonce=not Found&country=საქართველო\");\n\n  // question marks\n  act(() => navigate(\"/?вопрос=как дела?\"));\n  expect(searchResult.current).toBe(\"вопрос=как дела?\");\n});\n\ntest(\"is safe against parameter injection\", () => {\n  history.replaceState(null, \"\", \"/?search=foo%26parameter_injection%3Dbar\");\n  const { result } = renderHook(() => useSearch());\n\n  const searchParams = new URLSearchParams(result.current);\n  const query = Object.fromEntries(searchParams.entries());\n\n  expect(query).toEqual({ search: \"foo&parameter_injection=bar\" });\n});\n"
  },
  {
    "path": "packages/wouter/test/view-transitions.test.tsx",
    "content": "import { test, expect, describe, mock, afterEach } from \"bun:test\";\nimport { render, cleanup, fireEvent } from \"@testing-library/react\";\nimport { Router, Link, useLocation, type AroundNavHandler } from \"../src/index.js\";\nimport { memoryLocation } from \"../src/memory-location.js\";\n\nafterEach(cleanup);\n\ndescribe(\"view transitions\", () => {\n  test(\"Link with transition prop triggers aroundNav with transition in options\", () => {\n    // 1. Setup: create aroundNav callback that captures calls\n    const aroundNav: AroundNavHandler = mock((navigate, to, options) => {\n      navigate(to, options);\n    });\n\n    const { hook } = memoryLocation({ path: \"/\" });\n\n    // 2. Render Link with transition prop\n    const { getByTestId } = render(\n      <Router hook={hook} aroundNav={aroundNav}>\n        <Link href=\"/about\" transition data-testid=\"link\">\n          About\n        </Link>\n      </Router>\n    );\n\n    // 3. Click the link\n    fireEvent.click(getByTestId(\"link\"));\n\n    // 4. Verify aroundNav was called with transition: true in options\n    expect(aroundNav).toHaveBeenCalledTimes(1);\n\n    const [navigateFn, to, options] = (aroundNav as ReturnType<typeof mock>)\n      .mock.calls[0];\n\n    expect(typeof navigateFn).toBe(\"function\");\n    expect(to).toBe(\"/about\");\n    expect(options.transition).toBe(true);\n  });\n\n  test(\"useLocation navigate with transition option triggers aroundNav\", () => {\n    const aroundNav: AroundNavHandler = mock((navigate, to, options) => {\n      navigate(to, options);\n    });\n\n    const { hook } = memoryLocation({ path: \"/\" });\n\n    const NavigateButton = () => {\n      const [, navigate] = useLocation();\n      return (\n        <button\n          data-testid=\"btn\"\n          onClick={() => navigate(\"/about\", { transition: true })}\n        >\n          Go\n        </button>\n      );\n    };\n\n    const { getByTestId } = render(\n      <Router hook={hook} aroundNav={aroundNav}>\n        <NavigateButton />\n      </Router>\n    );\n\n    fireEvent.click(getByTestId(\"btn\"));\n\n    expect(aroundNav).toHaveBeenCalledTimes(1);\n\n    const [, to, options] = (aroundNav as ReturnType<typeof mock>).mock.calls[0];\n    expect(to).toBe(\"/about\");\n    expect(options.transition).toBe(true);\n  });\n\n  test(\"navigation does not happen if aroundNav doesn't call navigate\", () => {\n    // aroundNav that does nothing\n    const aroundNav: AroundNavHandler = mock(() => {});\n\n    const { hook } = memoryLocation({ path: \"/\" });\n\n    const LocationDisplay = () => {\n      const [location] = useLocation();\n      return <span data-testid=\"location\">{location}</span>;\n    };\n\n    const { getByTestId } = render(\n      <Router hook={hook} aroundNav={aroundNav}>\n        <LocationDisplay />\n        <Link href=\"/about\" data-testid=\"link\">\n          About\n        </Link>\n      </Router>\n    );\n\n    // Verify initial location\n    expect(getByTestId(\"location\").textContent).toBe(\"/\");\n\n    // Click the link\n    fireEvent.click(getByTestId(\"link\"));\n\n    // aroundNav was called but didn't call navigate\n    expect(aroundNav).toHaveBeenCalledTimes(1);\n\n    // Location should remain unchanged\n    expect(getByTestId(\"location\").textContent).toBe(\"/\");\n  });\n});\n"
  },
  {
    "path": "packages/wouter/types/index.d.ts",
    "content": "// Minimum TypeScript Version: 4.1\n\n// tslint:disable:no-unnecessary-generics\n\nimport {\n  AnchorHTMLAttributes,\n  FunctionComponent,\n  RefAttributes,\n  ComponentType,\n  ReactNode,\n  ReactElement,\n  MouseEventHandler,\n  JSXElementConstructor,\n} from \"react\";\n\nimport {\n  Path,\n  PathPattern,\n  BaseLocationHook,\n  HookReturnValue,\n  HookNavigationOptions,\n  BaseSearchHook,\n} from \"./location-hook.js\";\nimport {\n  BrowserLocationHook,\n  BrowserSearchHook,\n} from \"./use-browser-location.js\";\n\nimport { Parser, RouterObject, RouterOptions } from \"./router.js\";\n\n// these files only export types, so we can re-export them as-is\n// in TS 5.0 we'll be able to use `export type * from ...`\nexport * from \"./location-hook.js\";\nexport * from \"./router.js\";\n\nimport { RouteParams } from \"regexparam\";\n\nexport type StringRouteParams<T extends string> = RouteParams<T> & {\n  [param: number]: string | undefined;\n};\nexport type RegexRouteParams = { [key: string | number]: string | undefined };\n\n/**\n * Route patterns and parameters\n */\nexport interface DefaultParams {\n  readonly [paramName: string | number]: string | undefined;\n}\n\nexport type Params<T extends DefaultParams = DefaultParams> = T;\n\nexport type MatchWithParams<T extends DefaultParams = DefaultParams> = [\n  true,\n  Params<T>\n];\nexport type NoMatch = [false, null];\nexport type Match<T extends DefaultParams = DefaultParams> =\n  | MatchWithParams<T>\n  | NoMatch;\n\n/*\n * Components: <Route />\n */\n\nexport interface RouteComponentProps<T extends DefaultParams = DefaultParams> {\n  params: T;\n}\n\nexport interface RouteProps<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n> {\n  children?:\n    | ((\n        params: T extends DefaultParams\n          ? T\n          : RoutePath extends string\n          ? StringRouteParams<RoutePath>\n          : RegexRouteParams\n      ) => ReactNode)\n    | ReactNode;\n  path?: RoutePath;\n  component?: JSXElementConstructor<\n    RouteComponentProps<\n      T extends DefaultParams\n        ? T\n        : RoutePath extends string\n        ? StringRouteParams<RoutePath>\n        : RegexRouteParams\n    >\n  >;\n  nest?: boolean;\n}\n\nexport function Route<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(props: RouteProps<T, RoutePath>): ReturnType<FunctionComponent>;\n\n/*\n * Components: <Link /> & <Redirect />\n */\n\nexport type NavigationalProps<\n  H extends BaseLocationHook = BrowserLocationHook\n> = ({ to: Path; href?: never } | { href: Path; to?: never }) &\n  HookNavigationOptions<H>;\n\nexport type RedirectProps<H extends BaseLocationHook = BrowserLocationHook> =\n  NavigationalProps<H> & {\n    children?: never;\n  };\n\nexport function Redirect<H extends BaseLocationHook = BrowserLocationHook>(\n  props: RedirectProps<H>,\n  context?: any\n): null;\n\ntype AsChildProps<ComponentProps, DefaultElementProps> =\n  | ({ asChild?: false } & DefaultElementProps)\n  | ({ asChild: true } & ComponentProps);\n\ntype HTMLLinkAttributes = Omit<\n  AnchorHTMLAttributes<HTMLAnchorElement>,\n  \"className\"\n> & {\n  className?: string | undefined | ((isActive: boolean) => string | undefined);\n};\n\nexport type LinkProps<H extends BaseLocationHook = BrowserLocationHook> =\n  NavigationalProps<H> &\n    AsChildProps<\n      { children: ReactElement; onClick?: MouseEventHandler },\n      HTMLLinkAttributes & RefAttributes<HTMLAnchorElement>\n    >;\n\nexport function Link<H extends BaseLocationHook = BrowserLocationHook>(\n  props: LinkProps<H>,\n  context?: any\n): ReturnType<FunctionComponent>;\n\n/*\n * Components: <Switch />\n */\n\nexport interface SwitchProps {\n  location?: string;\n  children: ReactNode;\n}\nexport const Switch: FunctionComponent<SwitchProps>;\n\n/*\n * Components: <Router />\n */\n\nexport type RouterProps = RouterOptions & {\n  children: ReactNode;\n};\n\nexport const Router: FunctionComponent<RouterProps>;\n\n/*\n * Hooks\n */\n\nexport function useRouter(): RouterObject;\n\nexport function useRoute<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(\n  pattern: RoutePath\n): Match<\n  T extends DefaultParams\n    ? T\n    : RoutePath extends string\n    ? StringRouteParams<RoutePath>\n    : RegexRouteParams\n>;\n\nexport function useLocation<\n  H extends BaseLocationHook = BrowserLocationHook\n>(): HookReturnValue<H>;\n\nexport function useSearch<\n  H extends BaseSearchHook = BrowserSearchHook\n>(): ReturnType<H>;\n\nexport type URLSearchParamsInit = ConstructorParameters<\n  typeof URLSearchParams\n>[0];\nexport type SetSearchParams = (\n  nextInit:\n    | URLSearchParamsInit\n    | ((prev: URLSearchParams) => URLSearchParamsInit),\n  options?: { replace?: boolean; state?: any }\n) => void;\n\nexport function useSearchParams(): [URLSearchParams, SetSearchParams];\n\nexport function useParams<T = undefined>(): T extends string\n  ? StringRouteParams<T>\n  : T extends undefined\n  ? DefaultParams\n  : T;\n\n/*\n * Helpers\n */\n\nexport function matchRoute<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(\n  parser: Parser,\n  pattern: RoutePath,\n  path: string,\n  loose?: boolean\n): Match<\n  T extends DefaultParams\n    ? T\n    : RoutePath extends string\n    ? StringRouteParams<RoutePath>\n    : RegexRouteParams\n>;\n\n// tslint:enable:no-unnecessary-generics\n"
  },
  {
    "path": "packages/wouter/types/location-hook.d.ts",
    "content": "/*\n * Foundation: useLocation and paths\n */\n\nexport type Path = string;\n\nexport type PathPattern = string | RegExp;\n\nexport type SearchString = string;\n\nexport type HrefsFormatter = (href: string, router?: any) => string;\n\n// the base useLocation hook type. Any custom hook (including the\n// default one) should inherit from it.\nexport type BaseLocationHook = {\n  (...args: any[]): [Path, (path: Path, ...args: any[]) => any];\n  searchHook?: BaseSearchHook;\n  hrefs?: HrefsFormatter;\n};\n\nexport type BaseSearchHook = (...args: any[]) => SearchString;\n\n/*\n * Utility types that operate on hook\n */\n\n// Returns the type of the location tuple of the given hook.\nexport type HookReturnValue<H extends BaseLocationHook> = ReturnType<H>;\n\n// Utility type that allows us to handle cases like `any` and `never`\ntype EmptyInterfaceWhenAnyOrNever<T> = 0 extends 1 & T\n  ? {}\n  : [T] extends [never]\n  ? {}\n  : T;\n\n// Returns the type of the navigation options that hook's push function accepts.\nexport type HookNavigationOptions<H extends BaseLocationHook> =\n  EmptyInterfaceWhenAnyOrNever<\n    NonNullable<Parameters<HookReturnValue<H>[1]>[1]> // get's the second argument of a tuple returned by the hook\n  >;\n"
  },
  {
    "path": "packages/wouter/types/memory-location.d.ts",
    "content": "import {\n  BaseLocationHook,\n  BaseSearchHook,\n  Path,\n  SearchString,\n} from \"./location-hook.js\";\n\ntype Navigate<S = any> = (\n  to: Path,\n  options?: { replace?: boolean; state?: S; transition?: boolean }\n) => void;\n\ntype HookReturnValue = {\n  hook: BaseLocationHook;\n  searchHook: BaseSearchHook;\n  navigate: Navigate;\n};\ntype StubHistory = { history: Path[]; reset: () => void };\n\nexport function memoryLocation(options?: {\n  path?: Path;\n  searchPath?: SearchString;\n  static?: boolean;\n  record?: false;\n}): HookReturnValue;\nexport function memoryLocation(options?: {\n  path?: Path;\n  searchPath?: SearchString;\n  static?: boolean;\n  record: true;\n}): HookReturnValue & StubHistory;\n"
  },
  {
    "path": "packages/wouter/types/router.d.ts",
    "content": "import {\n  Path,\n  SearchString,\n  BaseLocationHook,\n  BaseSearchHook,\n  HrefsFormatter,\n} from \"./location-hook.js\";\n\nexport type Parser = (\n  route: Path,\n  loose?: boolean\n) => { pattern: RegExp; keys: string[] };\n\n// Standard navigation options supported by all built-in location hooks\nexport type NavigateOptions<S = any> = {\n  replace?: boolean;\n  state?: S;\n  /** Enable view transitions for this navigation (used with aroundNav) */\n  transition?: boolean;\n};\n\n// Function that wraps navigate calls, useful for view transitions\nexport type AroundNavHandler = (\n  navigate: (to: Path, options?: NavigateOptions) => void,\n  to: Path,\n  options?: NavigateOptions\n) => void;\n\n// the object returned from `useRouter`\nexport interface RouterObject {\n  readonly hook: BaseLocationHook;\n  readonly searchHook: BaseSearchHook;\n  readonly base: Path;\n  readonly ownBase: Path;\n  readonly parser: Parser;\n  readonly ssrPath?: Path;\n  readonly ssrSearch?: SearchString;\n  readonly ssrContext?: SsrContext;\n  readonly hrefs: HrefsFormatter;\n  readonly aroundNav: AroundNavHandler;\n}\n\n// state captured during SSR render\nexport type SsrContext = {\n  // if a redirect was encountered, this will be populated with the path\n  redirectTo?: Path;\n  // HTTP status code to set for SSR response\n  statusCode?: number;\n};\n\n// basic options to construct a router\nexport type RouterOptions = {\n  hook?: BaseLocationHook;\n  searchHook?: BaseSearchHook;\n  base?: Path;\n  parser?: Parser;\n  ssrPath?: Path;\n  ssrSearch?: SearchString;\n  ssrContext?: SsrContext;\n  hrefs?: HrefsFormatter;\n  aroundNav?: AroundNavHandler;\n};\n"
  },
  {
    "path": "packages/wouter/types/use-browser-location.d.ts",
    "content": "import { Path, SearchString } from \"./location-hook.js\";\n\ntype Primitive = string | number | bigint | boolean | null | undefined | symbol;\nexport const useLocationProperty: <S extends Primitive>(\n  fn: () => S,\n  ssrFn?: () => S\n) => S;\n\nexport type BrowserSearchHook = (options?: {\n  ssrSearch?: SearchString;\n}) => SearchString;\n\nexport const useSearch: BrowserSearchHook;\n\nexport const usePathname: (options?: { ssrPath?: Path }) => Path;\n\nexport const useHistoryState: <T = any>() => T;\n\nexport const navigate: <S = any>(\n  to: string | URL,\n  options?: { replace?: boolean; state?: S; transition?: boolean }\n) => void;\n\n/*\n * Default `useLocation`\n */\n\n// The type of the default `useLocation` hook that wouter uses.\n// It operates on current URL using History API, supports base path and can\n// navigate with `pushState` or `replaceState`.\nexport type BrowserLocationHook = (options?: {\n  ssrPath?: Path;\n}) => [Path, typeof navigate];\n\nexport const useBrowserLocation: BrowserLocationHook;\n"
  },
  {
    "path": "packages/wouter/types/use-hash-location.d.ts",
    "content": "import { Path } from \"./location-hook.js\";\n\nexport function navigate<S = any>(\n  to: Path,\n  options?: { state?: S; replace?: boolean; transition?: boolean }\n): void;\n\nexport function useHashLocation(options?: {\n  ssrPath?: Path;\n}): [Path, typeof navigate];\n"
  },
  {
    "path": "packages/wouter-preact/.gitignore",
    "content": "# Copied from wouter during prepublish (react-deps.js stays as the Preact version)\nsrc/index.js\nsrc/memory-location.js\nsrc/paths.js\nsrc/use-browser-location.js\nsrc/use-hash-location.js\nsrc/use-sync-external-store.js\nsrc/use-sync-external-store.native.js\n"
  },
  {
    "path": "packages/wouter-preact/package.json",
    "content": "{\n  \"name\": \"wouter-preact\",\n  \"version\": \"3.9.0\",\n  \"description\": \"Minimalist-friendly ~1.5KB router for Preact\",\n  \"type\": \"module\",\n  \"keywords\": [\n    \"react\",\n    \"preact\",\n    \"router\",\n    \"tiny\",\n    \"routing\",\n    \"hooks\",\n    \"useLocation\"\n  ],\n  \"files\": [\n    \"src\",\n    \"types/**/*.d.ts\",\n    \"types/*.d.ts\"\n  ],\n  \"main\": \"src/index.js\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./types/index.d.ts\",\n      \"default\": \"./src/index.js\"\n    },\n    \"./use-browser-location\": {\n      \"types\": \"./types/use-browser-location.d.ts\",\n      \"default\": \"./src/use-browser-location.js\"\n    },\n    \"./use-hash-location\": {\n      \"types\": \"./types/use-hash-location.d.ts\",\n      \"default\": \"./src/use-hash-location.js\"\n    },\n    \"./memory-location\": {\n      \"types\": \"./types/memory-location.d.ts\",\n      \"default\": \"./src/memory-location.js\"\n    }\n  },\n  \"types\": \"types/index.d.ts\",\n  \"typesVersions\": {\n    \">=4.1\": {\n      \"types/index.d.ts\": [\n        \"types/index.d.ts\"\n      ],\n      \"use-browser-location\": [\n        \"types/use-browser-location.d.ts\"\n      ],\n      \"use-hash-location\": [\n        \"types/use-hash-location.d.ts\"\n      ],\n      \"memory-location\": [\n        \"types/memory-location.d.ts\"\n      ]\n    }\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \"copyfiles -f ../wouter/src/index.js ../wouter/src/memory-location.js ../wouter/src/paths.js ../wouter/src/use-browser-location.js ../wouter/src/use-hash-location.js ../wouter/src/use-sync-external-store.js ../wouter/src/use-sync-external-store.native.js src && cp ../../README.md .\"\n  },\n  \"author\": \"Alexey Taktarov <molefrog@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/molefrog/wouter.git\"\n  },\n  \"license\": \"Unlicense\",\n  \"peerDependencies\": {\n    \"preact\": \"^10.0.0\"\n  },\n  \"dependencies\": {\n    \"mitt\": \"^3.0.1\",\n    \"regexparam\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"wouter\": \"*\"\n  }\n}\n"
  },
  {
    "path": "packages/wouter-preact/src/react-deps.js",
    "content": "import { useState, useLayoutEffect, useEffect, useRef } from \"preact/hooks\";\nexport {\n  isValidElement,\n  createContext,\n  cloneElement,\n  createElement,\n  Fragment,\n} from \"preact\";\nexport {\n  useMemo,\n  useRef,\n  useLayoutEffect as useIsomorphicLayoutEffect,\n  useLayoutEffect as useInsertionEffect,\n  useState,\n  useContext,\n} from \"preact/hooks\";\n\n// Copied from:\n// https://github.com/facebook/react/blob/main/packages/shared/ExecutionEnvironment.js\nconst canUseDOM = !!(\n  typeof window !== \"undefined\" &&\n  typeof window.document !== \"undefined\" &&\n  typeof window.document.createElement !== \"undefined\"\n);\n\n// TODO: switch to `export { useSyncExternalStore } from \"preact/compat\"` once we update Preact to >= 10.11.3\nfunction is(x, y) {\n  return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);\n}\nexport function useSyncExternalStore(subscribe, getSnapshot, getSSRSnapshot) {\n  if (getSSRSnapshot && !canUseDOM) getSnapshot = getSSRSnapshot;\n  const value = getSnapshot();\n\n  const [{ _instance }, forceUpdate] = useState({\n    _instance: { _value: value, _getSnapshot: getSnapshot },\n  });\n\n  useLayoutEffect(() => {\n    _instance._value = value;\n    _instance._getSnapshot = getSnapshot;\n\n    if (!is(_instance._value, getSnapshot())) {\n      forceUpdate({ _instance });\n    }\n  }, [subscribe, value, getSnapshot]);\n\n  useEffect(() => {\n    if (!is(_instance._value, _instance._getSnapshot())) {\n      forceUpdate({ _instance });\n    }\n\n    return subscribe(() => {\n      if (!is(_instance._value, _instance._getSnapshot())) {\n        forceUpdate({ _instance });\n      }\n    });\n  }, [subscribe]);\n\n  return value;\n}\n\n// provide forwardRef stub for preact\nexport function forwardRef(component) {\n  return component;\n}\n\n// Userland polyfill while we wait for the forthcoming\n// https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md\n// Note: \"A high-fidelty polyfill for useEvent is not possible because\n// there is no lifecycle or Hook in React that we can use to switch\n// .current at the right timing.\"\n// So we will have to make do with this \"close enough\" approach for now.\nexport const useEvent = (fn) => {\n  const ref = useRef([fn, (...args) => ref[0](...args)]).current;\n  useLayoutEffect(() => {\n    ref[0] = fn;\n  });\n  return ref[1];\n};\n"
  },
  {
    "path": "packages/wouter-preact/test/preact.test.tsx",
    "content": "/** @jsx h */\n/** @jsxFrag Fragment */\n/** @jsxImportSource preact */\n\nimport {\n  test,\n  expect,\n  describe,\n  beforeEach,\n  afterEach,\n  beforeAll,\n  afterAll,\n  mock,\n} from \"bun:test\";\nimport { render } from \"preact\";\nimport { act, setupRerender, teardown } from \"preact/test-utils\";\nimport renderToString from \"preact-render-to-string\";\nimport { copyFile, rm } from \"fs/promises\";\nimport { join } from \"path\";\nimport type * as WouterPreact from \"../types/index.js\";\n\nconst assertType = <T,>(_value: T): void => {};\n\n// Files to copy from wouter/src to wouter-preact/src\nconst filesToCopy = [\n  \"memory-location.js\",\n  \"paths.js\",\n  \"use-browser-location.js\",\n  \"use-hash-location.js\",\n  \"use-sync-external-store.js\",\n  \"use-sync-external-store.native.js\",\n  \"index.js\",\n];\n\nasync function loadPreact(): Promise<typeof WouterPreact> {\n  // Import from the copied files in src/ directory\n  const module = (await import(\n    join(import.meta.dir, \"../src/index.js\")\n  )) as typeof WouterPreact;\n  return module;\n}\n\nbeforeAll(async () => {\n  const wouterSrc = join(import.meta.dir, \"../../wouter/src\");\n  const preactSrc = join(import.meta.dir, \"../src\");\n\n  for (const file of filesToCopy) {\n    await copyFile(join(wouterSrc, file), join(preactSrc, file));\n  }\n});\n\nafterAll(async () => {\n  const preactSrc = join(import.meta.dir, \"../src\");\n\n  for (const file of filesToCopy) {\n    await rm(join(preactSrc, file), { force: true });\n  }\n});\n\ndescribe(\"Preact support\", () => {\n  beforeEach(() => {\n    setupRerender();\n  });\n\n  afterEach(() => {\n    teardown();\n  });\n\n  describe(\"useRoute\", () => {\n    test(\"should only accept strings\", async () => {\n      const { useRoute } = await loadPreact();\n\n      const Component = () => {\n        // @ts-expect-error\n        assertType(useRoute(Symbol()));\n        // @ts-expect-error\n        assertType(useRoute());\n        assertType(useRoute(\"/\"));\n        return <div>Hello</div>;\n      };\n\n      expect(typeof Component).toBe(\"function\"); // dummy, we only care about the types\n    });\n  });\n\n  test(\"renders properly and reacts on navigation\", async () => {\n    const { Route, Link, Switch } = await loadPreact();\n\n    const container = document.body.appendChild(document.createElement(\"div\"));\n    const fn = mock();\n\n    const App = () => {\n      const handleAsChildClick = mock();\n\n      return (\n        <>\n          <nav>\n            <Link href=\"/albums/all\" onClick={fn} data-testid=\"index-link\">\n              The Best Albums Ever\n            </Link>\n\n            <Link\n              to=\"/albums/london-calling\"\n              asChild\n              onClick={handleAsChildClick}\n            >\n              <a data-testid=\"featured-link\">\n                Featured Now: London Calling, Clash\n              </a>\n            </Link>\n          </nav>\n\n          <main data-testid=\"routes\">\n            <Switch>\n              <>Welcome to the list of {100} greatest albums of all time!</>\n              <Route path=\"/albums/all\">Rolling Stones Best 100 Albums</Route>\n              <Route path=\"/albums/:name\">\n                {(params) => `Album ${params.name}`}\n              </Route>\n              <Route path=\"*\">Nothing was found!</Route>\n            </Switch>\n          </main>\n        </>\n      );\n    };\n\n    let node = render(<App />, container);\n\n    const routesEl = container.querySelector('[data-testid=\"routes\"]')!;\n    const indexLinkEl = container.querySelector('[data-testid=\"index-link\"]')!;\n    const featLinkEl = container.querySelector(\n      '[data-testid=\"featured-link\"]'\n    )!;\n\n    // default route should be rendered\n    expect(routesEl.textContent).toBe(\"Nothing was found!\");\n    expect(featLinkEl.getAttribute(\"href\")).toBe(\"/albums/london-calling\");\n\n    // link renders as A element\n    expect(indexLinkEl.tagName).toBe(\"A\");\n\n    act(() => {\n      const evt = new MouseEvent(\"click\", {\n        bubbles: true,\n        cancelable: true,\n        button: 0,\n      });\n\n      indexLinkEl.dispatchEvent(evt);\n    });\n\n    // performs a navigation when the link is clicked\n    expect(location.pathname).toBe(\"/albums/all\");\n\n    // Link accepts an `onClick` prop, fired after the navigation\n    expect(fn).toHaveBeenCalledTimes(1);\n  });\n});\n\ndescribe(\"Preact SSR\", () => {\n  test.skip(\"supports SSR (fix: useSyncExternalStore polyfill in Bun)\", async () => {\n    const { Router, useLocation } = await loadPreact();\n\n    const LocationPrinter = () => {\n      const [location] = useLocation();\n      return <>location = {location}</>;\n    };\n\n    const rendered = renderToString(\n      <Router ssrPath=\"/ssr/preact\">\n        <LocationPrinter />\n      </Router>\n    );\n\n    expect(rendered).toContain(\"/ssr/preact\");\n  });\n});\n"
  },
  {
    "path": "packages/wouter-preact/types/index.d.ts",
    "content": "// Minimum TypeScript Version: 4.1\n// tslint:disable:no-unnecessary-generics\n\nimport {\n  JSX,\n  FunctionComponent,\n  ComponentType,\n  ComponentChildren,\n} from \"preact\";\n\nimport {\n  Path,\n  PathPattern,\n  BaseLocationHook,\n  HookReturnValue,\n  HookNavigationOptions,\n  BaseSearchHook,\n} from \"./location-hook.js\";\nimport {\n  BrowserLocationHook,\n  BrowserSearchHook,\n} from \"./use-browser-location.js\";\n\nimport { RouterObject, RouterOptions, Parser } from \"./router.js\";\n\n// these files only export types, so we can re-export them as-is\n// in TS 5.0 we'll be able to use `export type * from ...`\nexport * from \"./location-hook.js\";\nexport * from \"./router.js\";\n\nimport { RouteParams } from \"regexparam\";\n\nexport type StringRouteParams<T extends string> = RouteParams<T> & {\n  [param: number]: string | undefined;\n};\nexport type RegexRouteParams = { [key: string | number]: string | undefined };\n\n/**\n * Route patterns and parameters\n */\nexport interface DefaultParams {\n  readonly [paramName: string | number]: string | undefined;\n}\n\nexport type Params<T extends DefaultParams = DefaultParams> = T;\n\nexport type MatchWithParams<T extends DefaultParams = DefaultParams> = [\n  true,\n  Params<T>\n];\nexport type NoMatch = [false, null];\nexport type Match<T extends DefaultParams = DefaultParams> =\n  | MatchWithParams<T>\n  | NoMatch;\n\n/*\n * Components: <Route />\n */\n\nexport interface RouteComponentProps<T extends DefaultParams = DefaultParams> {\n  params: T;\n}\n\nexport interface RouteProps<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n> {\n  children?:\n    | ((\n        params: T extends DefaultParams\n          ? T\n          : RoutePath extends string\n          ? StringRouteParams<RoutePath>\n          : RegexRouteParams\n      ) => ComponentChildren)\n    | ComponentChildren;\n  path?: RoutePath;\n  component?: ComponentType<\n    RouteComponentProps<\n      T extends DefaultParams\n        ? T\n        : RoutePath extends string\n        ? StringRouteParams<RoutePath>\n        : RegexRouteParams\n    >\n  >;\n  nest?: boolean;\n}\n\nexport function Route<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(props: RouteProps<T, RoutePath>): ReturnType<FunctionComponent>;\n\n/*\n * Components: <Link /> & <Redirect />\n */\n\nexport type NavigationalProps<\n  H extends BaseLocationHook = BrowserLocationHook\n> = ({ to: Path; href?: never } | { href: Path; to?: never }) &\n  HookNavigationOptions<H>;\n\ntype AsChildProps<ComponentProps, DefaultElementProps> =\n  | ({ asChild?: false } & DefaultElementProps)\n  | ({ asChild: true } & ComponentProps);\n\ntype HTMLLinkAttributes = Omit<JSX.HTMLAttributes, \"className\"> & {\n  className?: string | undefined | ((isActive: boolean) => string | undefined);\n};\n\nexport type LinkProps<H extends BaseLocationHook = BrowserLocationHook> =\n  NavigationalProps<H> &\n    AsChildProps<\n      { children: ComponentChildren; onClick?: JSX.MouseEventHandler<Element> },\n      HTMLLinkAttributes\n    >;\n\nexport type RedirectProps<H extends BaseLocationHook = BrowserLocationHook> =\n  NavigationalProps<H> & {\n    children?: never;\n  };\n\nexport function Redirect<H extends BaseLocationHook = BrowserLocationHook>(\n  props: RedirectProps<H>,\n  context?: any\n): null;\n\nexport function Link<H extends BaseLocationHook = BrowserLocationHook>(\n  props: LinkProps<H>,\n  context?: any\n): ReturnType<FunctionComponent>;\n\n/*\n * Components: <Switch />\n */\n\nexport interface SwitchProps {\n  location?: string;\n  children: ComponentChildren;\n}\nexport const Switch: FunctionComponent<SwitchProps>;\n\n/*\n * Components: <Router />\n */\n\nexport type RouterProps = RouterOptions & {\n  children: ComponentChildren;\n};\n\nexport const Router: FunctionComponent<RouterProps>;\n\n/*\n * Hooks\n */\n\nexport function useRouter(): RouterObject;\n\nexport function useRoute<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(\n  pattern: RoutePath\n): Match<\n  T extends DefaultParams\n    ? T\n    : RoutePath extends string\n    ? StringRouteParams<RoutePath>\n    : RegexRouteParams\n>;\n\nexport function useLocation<\n  H extends BaseLocationHook = BrowserLocationHook\n>(): HookReturnValue<H>;\n\nexport function useSearch<\n  H extends BaseSearchHook = BrowserSearchHook\n>(): ReturnType<H>;\n\nexport type URLSearchParamsInit = ConstructorParameters<\n  typeof URLSearchParams\n>[0];\n\nexport type SetSearchParams = (\n  nextInit:\n    | URLSearchParamsInit\n    | ((prev: URLSearchParams) => URLSearchParamsInit),\n  options?: { replace?: boolean; state?: any }\n) => void;\n\nexport function useSearchParams(): [URLSearchParams, SetSearchParams];\n\nexport function useParams<T = undefined>(): T extends string\n  ? StringRouteParams<T>\n  : T extends undefined\n  ? DefaultParams\n  : T;\n\n/*\n * Helpers\n */\n\nexport function matchRoute<\n  T extends DefaultParams | undefined = undefined,\n  RoutePath extends PathPattern = PathPattern\n>(\n  parser: Parser,\n  pattern: RoutePath,\n  path: string,\n  loose?: boolean\n): Match<\n  T extends DefaultParams\n    ? T\n    : RoutePath extends string\n    ? StringRouteParams<RoutePath>\n    : RegexRouteParams\n>;\n\n// tslint:enable:no-unnecessary-generics\n"
  },
  {
    "path": "packages/wouter-preact/types/location-hook.d.ts",
    "content": "/*\n * Foundation: useLocation and paths\n */\n\nexport type Path = string;\n\nexport type PathPattern = string | RegExp;\n\nexport type SearchString = string;\n\nexport type HrefsFormatter = (href: string, router?: any) => string;\n\n// the base useLocation hook type. Any custom hook (including the\n// default one) should inherit from it.\nexport type BaseLocationHook = {\n  (...args: any[]): [Path, (path: Path, ...args: any[]) => any];\n  searchHook?: BaseSearchHook;\n  hrefs?: HrefsFormatter;\n};\n\nexport type BaseSearchHook = (...args: any[]) => SearchString;\n\n/*\n * Utility types that operate on hook\n */\n\n// Returns the type of the location tuple of the given hook.\nexport type HookReturnValue<H extends BaseLocationHook> = ReturnType<H>;\n\n// Returns the type of the navigation options that hook's push function accepts.\nexport type HookNavigationOptions<H extends BaseLocationHook> =\n  HookReturnValue<H>[1] extends (\n    path: Path,\n    options: infer R,\n    ...rest: any[]\n  ) => any\n    ? R extends { [k: string]: any }\n      ? R\n      : {}\n    : {};\n"
  },
  {
    "path": "packages/wouter-preact/types/memory-location.d.ts",
    "content": "import { BaseLocationHook, Path } from \"./location-hook.js\";\n\ntype Navigate<S = any> = (\n  to: Path,\n  options?: { replace?: boolean; state?: S; transition?: boolean }\n) => void;\n\ntype HookReturnValue = { hook: BaseLocationHook; navigate: Navigate };\ntype StubHistory = { history: Path[]; reset: () => void };\n\nexport function memoryLocation(options?: {\n  path?: Path;\n  static?: boolean;\n  record?: false;\n}): HookReturnValue;\nexport function memoryLocation(options?: {\n  path?: Path;\n  static?: boolean;\n  record: true;\n}): HookReturnValue & StubHistory;\n"
  },
  {
    "path": "packages/wouter-preact/types/router.d.ts",
    "content": "import {\n  Path,\n  SearchString,\n  BaseLocationHook,\n  BaseSearchHook,\n  HrefsFormatter,\n} from \"./location-hook.js\";\n\nexport type Parser = (\n  route: Path,\n  loose?: boolean\n) => { pattern: RegExp; keys: string[] };\n\n// Standard navigation options supported by all built-in location hooks\nexport type NavigateOptions<S = any> = {\n  replace?: boolean;\n  state?: S;\n  /** Enable view transitions for this navigation (used with aroundNav) */\n  transition?: boolean;\n};\n\n// Function that wraps navigate calls, useful for view transitions\nexport type AroundNavHandler = (\n  navigate: (to: Path, options?: NavigateOptions) => void,\n  to: Path,\n  options?: NavigateOptions\n) => void;\n\n// the object returned from `useRouter`\nexport interface RouterObject {\n  readonly hook: BaseLocationHook;\n  readonly searchHook: BaseSearchHook;\n  readonly base: Path;\n  readonly ownBase: Path;\n  readonly parser: Parser;\n  readonly ssrPath?: Path;\n  readonly ssrSearch?: SearchString;\n  readonly ssrContext?: SsrContext;\n  readonly hrefs: HrefsFormatter;\n  readonly aroundNav: AroundNavHandler;\n}\n\n// state captured during SSR render\nexport type SsrContext = {\n  // if a redirect was encountered, this will be populated with the path\n  redirectTo?: Path;\n  // HTTP status code to set for SSR response\n  statusCode?: number;\n};\n\n// basic options to construct a router\nexport type RouterOptions = {\n  hook?: BaseLocationHook;\n  searchHook?: BaseSearchHook;\n  base?: Path;\n  parser?: Parser;\n  ssrPath?: Path;\n  ssrSearch?: SearchString;\n  ssrContext?: SsrContext;\n  hrefs?: HrefsFormatter;\n  aroundNav?: AroundNavHandler;\n};\n"
  },
  {
    "path": "packages/wouter-preact/types/use-browser-location.d.ts",
    "content": "import { Path, SearchString } from \"./location-hook.js\";\n\ntype Primitive = string | number | bigint | boolean | null | undefined | symbol;\nexport const useLocationProperty: <S extends Primitive>(\n  fn: () => S,\n  ssrFn?: () => S\n) => S;\n\nexport type BrowserSearchHook = (options?: {\n  ssrSearch?: SearchString;\n}) => SearchString;\n\nexport const useSearch: BrowserSearchHook;\n\nexport const usePathname: (options?: { ssrPath?: Path }) => Path;\n\nexport const useHistoryState: <T = any>() => T;\n\nexport const navigate: <S = any>(\n  to: string | URL,\n  options?: { replace?: boolean; state?: S; transition?: boolean }\n) => void;\n\n/*\n * Default `useLocation`\n */\n\n// The type of the default `useLocation` hook that wouter uses.\n// It operates on current URL using History API, supports base path and can\n// navigate with `pushState` or `replaceState`.\nexport type BrowserLocationHook = (options?: {\n  ssrPath?: Path;\n}) => [Path, typeof navigate];\n\nexport const useBrowserLocation: BrowserLocationHook;\n"
  },
  {
    "path": "packages/wouter-preact/types/use-hash-location.d.ts",
    "content": "import { Path } from \"./location-hook.js\";\n\nexport function navigate<S = any>(\n  to: Path,\n  options?: { state?: S; replace?: boolean; transition?: boolean }\n): void;\n\nexport function useHashLocation(options?: {\n  ssrPath?: Path;\n}): [Path, typeof navigate];\n"
  },
  {
    "path": "specs/view-transitions-spec.md",
    "content": "# View Transitions API in Wouter\n\nView Transitions are baseline available (as of Oct 2025). This doc describes the API for using them in wouter.\n\nThough the browser API is super simple, there are certain obstacles to overcome:\n\n## Problems\n\n- `startViewTransition` accepts a callback that must modify the DOM synchronously\n- `setState` can't guarantee that the DOM will be modified synchronously\n- There is `flushSync` but it requires `react-dom`, we want wouter to only depend on `react`\n- Wouter uses `useSyncExternalStore` to react to events. In theory sending event inside `flushSync`\n  should trigger updates synchronously, but this is not 100% proven and could break\n\n## Solution\n\nUsers implement their own behavior before and after navigate is called, so they can control\nview transitions behavior.\n\n### Basic Implementation (enable view transitions by default)\n\n```js\nimport { flushSync } from \"react-dom\";\n\nfunction aroundNav(navigate, ...navArgs) {\n  // Feature detection for older browsers\n  if (!document.startViewTransition) {\n    navigate(...navArgs);\n    return;\n  }\n\n  document.startViewTransition(() => {\n    flushSync(() => {\n      navigate(...navArgs);\n    });\n  });\n}\n\n<Router aroundNav={aroundNav}>\n  <App />\n</Router>;\n```\n\nAlternatively, with explicit arguments:\n\n```js\nfunction aroundNav(navigate, to, options) {\n  if (!document.startViewTransition) {\n    navigate(to, options);\n    return;\n  }\n\n  document.startViewTransition(() => {\n    flushSync(() => {\n      navigate(to, options);\n    });\n  });\n}\n```\n\n### Granular control (opt-in transitions)\n\nFor more control over when transitions occur:\n\n```jsx\n// In your component\n<Link to=\"/\" transition>\n  Home\n</Link>;\n\n// Or programmatically\nconst [location, navigate] = useLocation();\nnavigate(\"/\", { transition: true });\n```\n\n**Note:** The `transition` prop is now part of wouter's type definitions (`NavigateOptions`) and is available on all location hooks (`useBrowserLocation`, `useHashLocation`, `memoryLocation`). When `<Link>` calls `navigate(targetPath, props)`, all props are automatically passed as navigation options, making them available in `aroundNav`.\n\n```js\nimport { flushSync } from \"react-dom\";\n\nfunction aroundNav(navigate, to, options) {\n  // Feature detection\n  if (!document.startViewTransition) {\n    navigate(to, options);\n    return;\n  }\n\n  // Only use transitions when explicitly requested\n  if (options?.transition) {\n    document.startViewTransition(() => {\n      flushSync(() => {\n        navigate(to, options);\n      });\n    });\n  } else {\n    navigate(to, options);\n  }\n}\n```\n\n### TypeScript types\n\nWouter provides built-in types for view transitions:\n\n```typescript\nimport type { NavigateOptions, AroundNavHandler } from \"wouter\";\n\n// NavigateOptions already includes transition\nconst navigate = (to: string, options?: NavigateOptions) => {\n  // options.transition is available\n  // options.replace is available\n  // options.state is available\n};\n\n// AroundNavHandler type for the aroundNav callback\nconst aroundNav: AroundNavHandler = (navigate, to, options) => {\n  if (options?.transition) {\n    // handle transition\n  }\n  navigate(to, options);\n};\n```\n\nThe `transition` option is included in `NavigateOptions` along with `replace` and `state`, and is available on all built-in location hooks.\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"moduleResolution\": \"bundler\",\n    \"module\": \"Preserve\",\n    \"moduleDetection\": \"force\",\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"baseUrl\": \".\",\n    \"types\": [\"bun\", \"@testing-library/jest-dom\"],\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"packages/wouter/**/*\", \"packages/wouter-preact/**/*\"]\n}\n"
  }
]