[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: mateothegreat\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Logs**\nIf available please provide any available logs, screenshots, etc.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: mateothegreat\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/demo.yaml",
    "content": "name: 📱 Demo\non:\n  workflow_dispatch:\n  workflow_call:\n    secrets:\n      VERCEL_TOKEN:\n        required: true\nconcurrency:\n  group: demo\n  cancel-in-progress: true\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: install vercel\n        run: npm install --global vercel@latest\n      - name: vercel pull\n        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}\n      - name: vercel build\n        run: vercel build --local-config demo/vercel.json --prod --token=${{ secrets.VERCEL_TOKEN }}\n      - name: vercel deploy\n        run: vercel deploy --local-config demo/vercel.json --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/docs.yaml",
    "content": "name: 📚 Docs\non:\n  workflow_dispatch:\n  workflow_call:\npermissions:\n  id-token: write\n  pages: write\ndefaults:\n  run:\n    shell: bash\n    working-directory: ./docs\njobs:\n  setup:\n    name: 🔧 Request\n    uses: ./.github/workflows/setup.yaml\n  build:\n    name: \"📚 Build Docs\"\n    needs: setup\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      # [setup] checkout the repo.\n      - uses: actions/checkout@v5.0.0\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n      # [setup] setup node.js.\n      - name: Setup Node.js\n        uses: actions/setup-node@v4.4.0\n        with:\n          node-version: 20.x\n      - name: 📊 Generate diagrams\n        run: make diagrams\n      - name: 📚 Build documentation\n        run: npm run docs:build\n      - name: 📤 Upload Pages artifact\n        uses: actions/upload-pages-artifact@v3.0.1\n        with:\n          path: tmp/build\n      - id: deployment\n        name: 📤 Deploy documentation to GitHub Pages\n        uses: actions/deploy-pages@v4.0.5\n      # [setup] authenticate with the cicd app to get proper credentials for pushing to the repo later.\n      - name: cicd app auth\n        id: app\n        uses: actions/create-github-app-token@v2.1.1\n        with:\n          app-id: ${{ secrets.CICD_APP_ID }}\n          private-key: ${{ secrets.CICD_APP_PRIVATE_KEY }}\n      # [setup] configure git for later changelog generation, commit, and the finalpush.\n      - name: 🔧 Commit and push diagrams\n        run: |\n          git config --global user.name \"mateothegreat[bot]\"\n          git config --global user.email \"mateothegreat[bot]@users.noreply.github.com\"\n          git remote set-url origin https://x-access-token:${{ steps.app.outputs.token }}@github.com/${{ github.repository }}.git\n          git add .\n          git commit -am \"chore(docs): update diagrams\"\n          git push origin HEAD:main\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: 🚀 Release\nrun-name: 🚀 Release (${{ github.event.inputs.label }})\non:\n  workflow_dispatch:\n    inputs:\n      label:\n        description: \"The label to add to the release\"\n        required: false\n        default: \"standard release\"\n        type: string\n      build_docs:\n        description: \"Release the docs?\"\n        required: false\n        default: false\n        type: boolean\n      build_demo:\n        description: \"Release the demo?\"\n        required: false\n        default: false\n        type: boolean\nconcurrency:\n  group: release\n  cancel-in-progress: true\njobs:\n  release:\n    name: 🚀 Release ${{ github.event.inputs.label }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: write\n    steps:\n      # [setup] authenticate with the cicd app to get proper credentials for pushing to the repo later.\n      - name: cicd app auth\n        id: app\n        uses: actions/create-github-app-token@v2.1.1\n        with:\n          app-id: ${{ secrets.CICD_APP_ID }}\n          private-key: ${{ secrets.CICD_APP_PRIVATE_KEY }}\n      # [setup] checkout the repo.\n      - uses: actions/checkout@v5.0.0\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n      # [setup] setup node.js.\n      - name: Setup Node.js\n        uses: actions/setup-node@v4.4.0\n        with:\n          node-version: 20.x\n      # [setup] configure git for later changelog generation, commit, and the finalpush.\n      - name: configure git\n        run: |\n          git config --global user.name \"mateothegreat[bot]\"\n          git config --global user.email \"mateothegreat[bot]@users.noreply.github.com\"\n          git remote set-url origin https://x-access-token:${{ steps.app.outputs.token }}@github.com/${{ github.repository }}.git\n      # [build] install dependencies first.\n      - name: install\n        run: npm install\n      # [build] build the package which outputs to the dist/ directory.\n      - name: build\n        run: npm run build\n      # [version] bump the version\n      - name: npm version\n        id: version\n        run: |\n          version=$(npm version patch --no-git-tag-version --json | sed 's/^v//' || {\n            echo \"npm version patch failed, git is dirty\"\n            exit 1\n          })\n          echo \"version=$version\" >> \"$GITHUB_OUTPUT\"\n          echo \"version is now: $version\"\n      # [version] generate the new changelog.\n      - name: generate changelog\n        uses: orhun/git-cliff-action@98c93442bb05a455a77bee982867857ae748eeea\n        id: git-cliff\n        with:\n          config: ./docs/cliff.toml\n          args: --tag ${{ steps.version.outputs.version }}\n        env:\n          OUTPUT: docs/changelog.md\n      # [version] commit the changelog and package.json.\n      - name: commit\n        run: |\n          git add docs/changelog.md package.json\n          git commit -am \"chore(release): update of changelog and bumping package.json for ${{ steps.version.outputs.version }}\"\n          git push origin HEAD:main\n          git tag -a ${{ steps.version.outputs.version }} -m \"release: ${{ steps.version.outputs.version }}\"\n          git push origin ${{ steps.version.outputs.version }}\n      # [publish] prepare to publish by setting up credentials.\n      - name: write to .npmrc\n        run: echo \"//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}\" > ~/.npmrc\n      # [publish] release publicly only if the build was successful above.\n      - name: publish\n        working-directory: dist\n        run: |\n          cp ../package.json .\n          npm publish --access public\n      # [publish] create a github release.\n      - name: create github release\n        uses: softprops/action-gh-release@v2.3.2\n        with:\n          body: ${{ steps.git-cliff.outputs.content }}\n          tag_name: ${{ steps.version.outputs.version }}\n  docs:\n    if: ${{ github.event.inputs.build_docs == true }}\n    needs: release\n    permissions:\n      id-token: write\n      pages: write\n    uses: ./.github/workflows/docs.yaml\n  demo:\n    if: ${{ github.event.inputs.build_demo == true }}\n    needs: release\n    uses: ./.github/workflows/demo.yaml\n    secrets: inherit\n  notify:\n    if: always()\n    continue-on-error: true\n    needs: release\n    runs-on: ubuntu-latest\n    steps:\n      - uses: Ilshidur/action-discord@ad5235de713df3ef38022185499b02ffe93b7efe\n        with:\n          status: ${{ needs.build.result }}\n          webhook: ${{ secrets.DISCORD_WEBHOOK }}\n        env:\n          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}\n"
  },
  {
    "path": ".github/workflows/setup.yaml",
    "content": "name: 🔧 Setup\non:\n  workflow_call:\njobs:\n  node:\n    name: \"Runtime\"  \n    environment:\n      name: dev\n    runs-on: ubuntu-latest\n    steps:\n      - name: ⬇️ Check out repo\n        uses: actions/checkout@v4\n      - name: ⎔ Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: \"package.json\"\n          cache: \"npm\"\n      - name: 📦 Cache node_modules\n        uses: actions/cache@v3\n        id: cache-node-modules\n        with:\n          path: node_modules\n          key: ${{ hashFiles('package-lock.json') }}          \n      - name: 📦 Install dependencies\n        if: steps.cache-node-modules.outputs.cache-hit != 'true'\n        run: |\n          npm ci --legacy-peer-deps          \n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: ⚡ Test Runner\non:\n  workflow_dispatch:\n  workflow_call:\npermissions:\n  contents: write\njobs:\n  setup:\n    name: \"🔧 Setup\"\n    environment:\n      name: dev\n    runs-on: ubuntu-latest\n    steps:\n      - name: ⬇️ Check out repo\n        uses: actions/checkout@v4\n      - name: ⎔ Setup node\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: \"package.json\"\n          cache: \"npm\"\n          # cache-dependency-path: \"package-lock.json\"\n      - name: 🧹 Clean install for cross-platform compatibility\n        run: |\n          npm ci --legacy-peer-deps\n  test:\n    name: 🏃 Test\n    needs: setup\n    environment:\n      name: dev\n    runs-on: ubuntu-latest\n    steps:\n      - name: ⬇️ Check out repo\n        uses: actions/checkout@v4\n      - name: ⎔ Setup node (reuse cache from setup)\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: \"package.json\"\n          cache: \"npm\"\n      - name: Hydrate npm module cache\n        uses: actions/cache@v3\n        id: hydrate\n        with:\n          path: node_modules\n          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}\n      - name: Install dependencies\n        if: steps.hydrate.outputs.cache-hit != 'true'\n        run: npm ci --legacy-peer-deps\n      - name: 🧪 Run Tests\n        run: npm run test:ci\n      - name: ⚙️ Generating coverage badges\n        run: |\n          npx --yes coverage-badges-cli \\\n              --source tmp/coverage/coverage-summary.json \\\n              --style plastic \\\n              --type statements \\\n              --iconWidth 190 \\\n              --label \"Test Coverage\"  \\\n              --output docs/assets/coverage-badge.svg\n      - name: ⬆️ Push badges branch\n        run: |\n          git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --global user.name \"github-actions[bot]\"\n          git add docs/assets/coverage-badge.svg\n          git commit -m \"chore: update coverage badge\"\n          git push\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Matthew Davis <matthew@matthewdavis.io>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "demo/cypress/e2e/route-activation.cy.ts",
    "content": "/// <reference types=\"cypress\" />\n\nconst routes = require(\"../fixtures/routes.json\");\ndescribe.only(\"route activation\", () => {\n  beforeEach(() => {\n    cy.viewport(1500, 1500);\n    cy.visit(\"http://localhost:8173\");\n  });\n\n  routes.forEach((route) => {\n    it(`should activate ${route.id} when visiting ${route.path}`, () => {\n      cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, \"active\", route.active);\n    });\n  });\n\n  // it.only(\"displays two todo items by default\", () => {\n  //   // cy.get(\"a[href='/props']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"props.svelte\").should(\"exist\");\n\n  //   // cy.get(\"a[href='/props/foo']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"foo\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar?\"]').should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"bar\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href=\"/props/foo\"]').should(\"not.have.class\", \"active\");\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar\"]').should(\"have.class\", \"active\");\n\n  //   cy.clickAndValidateActiveClasses(\"a[href='/props']\", \"active\");\n  // });\n\n  // it(\"can add new todo items\", () => {\n  //   // We'll store our item text in a variable so we can reuse it\n  //   const newItem = \"Feed the cat\";\n\n  //   // Let's get the input element and use the `type` command to\n  //   // input our new list item. After typing the content of our item,\n  //   // we need to type the enter key as well in order to submit the input.\n  //   // This input has a data-test attribute so we'll use that to select the\n  //   // element in accordance with best practices:\n  //   // https://on.cypress.io/selecting-elements\n  //   cy.get(\"[data-test=new-todo]\").type(`${newItem}{enter}`);\n\n  //   // Now that we've typed our new item, let's check that it actually was added to the list.\n  //   // Since it's the newest item, it should exist as the last element in the list.\n  //   // In addition, with the two default items, we should have a total of 3 elements in the list.\n  //   // Since assertions yield the element that was asserted on,\n  //   // we can chain both of these assertions together into a single statement.\n  //   cy.get(\".todo-list li\")\n  //     .should(\"have.length\", 3)\n  //     .last()\n  //     .should(\"have.text\", newItem);\n  // });\n\n  // it(\"can check off an item as completed\", () => {\n  //   // In addition to using the `get` command to get an element by selector,\n  //   // we can also use the `contains` command to get an element by its contents.\n  //   // However, this will yield the <label>, which is lowest-level element that contains the text.\n  //   // In order to check the item, we'll find the <input> element for this <label>\n  //   // by traversing up the dom to the parent element. From there, we can `find`\n  //   // the child checkbox <input> element and use the `check` command to check it.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parent()\n  //     .find(\"input[type=checkbox]\")\n  //     .check();\n\n  //   // Now that we've checked the button, we can go ahead and make sure\n  //   // that the list element is now marked as completed.\n  //   // Again we'll use `contains` to find the <label> element and then use the `parents` command\n  //   // to traverse multiple levels up the dom until we find the corresponding <li> element.\n  //   // Once we get that element, we can assert that it has the completed class.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parents(\"li\")\n  //     .should(\"have.class\", \"completed\");\n  // });\n\n  // context(\"with a checked task\", () => {\n  //   beforeEach(() => {\n  //     // We'll take the command we used above to check off an element\n  //     // Since we want to perform multiple tests that start with checking\n  //     // one element, we put it in the beforeEach hook\n  //     // so that it runs at the start of every test.\n  //     cy.contains(\"Pay electric bill\")\n  //       .parent()\n  //       .find(\"input[type=checkbox]\")\n  //       .check();\n  //   });\n\n  //   it(\"can filter for uncompleted tasks\", () => {\n  //     // We'll click on the \"active\" button in order to\n  //     // display only incomplete items\n  //     cy.contains(\"Active\").click();\n\n  //     // After filtering, we can assert that there is only the one\n  //     // incomplete item in the list.\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Walk the dog\");\n\n  //     // For good measure, let's also assert that the task we checked off\n  //     // does not exist on the page.\n  //     cy.contains(\"Pay electric bill\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can filter for completed tasks\", () => {\n  //     // We can perform similar steps as the test above to ensure\n  //     // that only completed tasks are shown\n  //     cy.contains(\"Completed\").click();\n\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Pay electric bill\");\n\n  //     cy.contains(\"Walk the dog\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can delete all completed tasks\", () => {\n  //     // First, let's click the \"Clear completed\" button\n  //     // `contains` is actually serving two purposes here.\n  //     // First, it's ensuring that the button exists within the dom.\n  //     // This button only appears when at least one task is checked\n  //     // so this command is implicitly verifying that it does exist.\n  //     // Second, it selects the button so we can click it.\n  //     cy.contains(\"Clear completed\").click();\n\n  //     // Then we can make sure that there is only one element\n  //     // in the list and our element does not exist\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .should(\"not.have.text\", \"Pay electric bill\");\n\n  //     // Finally, make sure that the clear button no longer exists.\n  //     cy.contains(\"Clear completed\").should(\"not.exist\");\n  //   });\n  // });\n});\n"
  },
  {
    "path": "demo/cypress/fixtures/routes.json",
    "content": "[\n  {\n    \"path\": \"/home\",\n    \"id\": \"home.svelte\",\n    \"active\": 2\n  },\n  {\n    \"path\": \"/props\",\n    \"id\": \"props.svelte\",\n    \"active\": 1,\n    \"children\": [\n      {\n        \"path\": \"/foo\",\n        \"id\": \"display-params.svelte\",\n        \"active\": 1\n      },\n      {\n        \"path\": \"/bar?someQueryParam=123\",\n        \"id\": \"display-params.svelte\",\n        \"active\": 1\n      }\n    ]\n  },\n  {\n    \"path\": \"/nested\",\n    \"id\": \"nested.svelte\",\n    \"active\": 1,\n    \"children\": [\n      {\n        \"path\": \"/foo\",\n        \"id\": \"display-params.svelte\",\n        \"active\": 1\n      }\n    ]\n  },\n  {\n    \"path\": \"/async\",\n    \"id\": \"async.svelte\",\n    \"active\": 1\n  },\n  {\n    \"path\": \"/delayed\",\n    \"id\": \"delayed.svelte\",\n    \"active\": 1\n  }\n]\n"
  },
  {
    "path": "demo/cypress/support/commands.ts",
    "content": "/// <reference types=\"cypress\" />\n"
  },
  {
    "path": "demo/cypress/support/e2e.ts",
    "content": "import \"./commands\";\n\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      /**\n       * Custom command to check if navigation link is active based on href.\n       *\n       * @param selector - The selector to check against\n       * @param attribute - The attribute to check against\n       * @param value - The value to check against\n       *\n       * @example cy.hasClassByAttr('nav a', 'href', '/about', 'foo')\n       */\n      allowedClassesByAttr(selector: string, attribute: string, allowed: string | string[], className: string): Chainable<Element>;\n\n      /**\n       * Custom command to navigate to a specific path and validate the number of active elements.\n       *\n       * @param selector - The selector to check against\n       * @param path - The path to navigate to\n       * @param expected - The expected number of active elements\n       *\n       * @example cy.clickAndValidateActiveClasses(\"a[href='/props/foo']\", \"active\", 1)\n       */\n      clickAndValidateActiveClasses(selector: string, path: string, expected: number): Chainable<Element>;\n    }\n  }\n}\n\nCypress.Commands.add(\"allowedClassesByAttr\", (selector: string, attribute: string, allowed: string | string[], className: string) => {\n  cy.get(selector).then(($elements) => {\n    $elements.each((_, $el) => {\n      cy.wrap($el).then(($el) => {\n        if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n          if (Array.isArray(allowed) && !allowed.includes($el.attr(attribute) || \"\")) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          } else if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          }\n        }\n      });\n    });\n  });\n});\n\nCypress.Commands.add(\"clickAndValidateActiveClasses\", (selector: string, className: string, expected: number) => {\n  cy.get(selector).then(($el) => {\n    if ($el.length !== 1) {\n      throw new Error(`clickAndValidateActiveClasses: ${selector} should match only 1 element, has ${$el.length}`);\n    }\n    cy.get(selector).click();\n    cy.allowedClassesByAttr(\"nav a\", \"href\", $el.attr(\"href\") || \"\", className);\n  });\n});\n"
  },
  {
    "path": "demo/cypress.config.ts",
    "content": "import { defineConfig } from \"cypress\";\n\nexport default defineConfig({\n  component: {\n    devServer: {\n      framework: \"svelte\",\n      bundler: \"vite\",\n      viteConfig: {\n        server: {\n          port: 8173,\n        },\n      },\n    },\n  },\n  e2e: {\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Svelte5 Router</title>\n\t</head>\n\t<body class=\"dark\">\n\t\t<div id=\"app\"></div>\n\t\t<script type=\"module\" src=\"/src/main.ts\"></script>\n\t</body>\n</html>\n"
  },
  {
    "path": "demo/package.json",
    "content": "{\n  \"name\": \"test-app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --port 5174 --host\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"check\": \"svelte-check --tsconfig ./tsconfig.json\",\n    \"test:start\": \"vite --port 8173\",\n    \"test:cy:open-e2e\": \"cypress open --e2e --browser chrome\",\n    \"test:cy:open-unit\": \"cypress open --component --browser chrome\",\n    \"test:cy:run-e2e\": \"cypress run --e2e --no-runner-ui\",\n    \"test:cy:run-unit\": \"cypress run --component\",\n    \"test:cy:e2e\": \"start-server-and-test test:start http-get://localhost:8173 test:cy:open-e2e\"\n  },\n  \"devDependencies\": {\n    \"@mateothegreat/svelte5-table\": \"^1.0.29\",\n    \"@shikijs/colorized-brackets\": \"^3.12.0\",\n    \"@sveltejs/vite-plugin-svelte\": \"^6.1.3\",\n    \"@sveltejs/vite-plugin-svelte-inspector\": \"^5.0.1\",\n    \"@tailwindcss/oxide\": \"^4.1.12\",\n    \"@tailwindcss/postcss\": \"^4.1.12\",\n    \"@tailwindcss/vite\": \"^4.1.12\",\n    \"@tsconfig/svelte\": \"^5.0.5\",\n    \"cypress\": \"^15.0.0\",\n    \"lucide-svelte\": \"^0.542.0\",\n    \"prettier-plugin-svelte\": \"^3.4.0\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.14\",\n    \"shiki\": \"^3.12.0\",\n    \"svelte\": \"^5.38.6\",\n    \"svelte-check\": \"^4.3.1\",\n    \"svelte-highlight\": \"^7.8.3\",\n    \"tailwindcss\": \"^4.1.12\",\n    \"tslib\": \"^2.8.1\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^7.1.3\",\n    \"vite-plugin-version-mark\": \"^0.2.2\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  }\n}\n"
  },
  {
    "path": "demo/src/app.css",
    "content": "@import \"tailwindcss\";\n@config '../tailwind.config.ts';\n\n/*\n  The default border color has changed to `currentColor` in Tailwind CSS v4,\n  so we've added these compatibility styles to make sure everything still\n  looks the same as it did with Tailwind CSS v3.\n\n  If we ever want to remove these styles, we need to add an explicit border\n  color utility to any element that depends on these defaults.\n*/\n@layer base {\n  *,\n  ::after,\n  ::before,\n  ::backdrop,\n  ::file-selector-button {\n    border-color: var(--color-gray-200, currentColor);\n  }\n}\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --primary: 222.2 47.4% 11.2%;\n    --primary-foreground: 210 40% 98%;\n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n    --destructive: 0 72.2% 50.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 222.2 84% 4.9%;\n    --radius: 0.5rem;\n    --sidebar-background: 0 0% 98%;\n    --sidebar-foreground: 240 5.3% 26.1%;\n    --sidebar-primary: 240 5.9% 10%;\n    --sidebar-primary-foreground: 0 0% 98%;\n    --sidebar-accent: 240 4.8% 95.9%;\n    --sidebar-accent-foreground: 240 5.9% 10%;\n    --sidebar-border: 220 13% 91%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n\n  .dark {\n    --background: 0 0% 0%;\n    --foreground: 210 40% 98%;\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --primary: 210 40% 98%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 212.7 26.8% 83.9%;\n    --sidebar-background: 240 5.9% 10%;\n    --sidebar-foreground: 240 4.8% 95.9%;\n    --sidebar-primary: 224.3 76.3% 48%;\n    --sidebar-primary-foreground: 0 0% 100%;\n    --sidebar-accent: 240 3.7% 15.9%;\n    --sidebar-accent-foreground: 240 4.8% 95.9%;\n    --sidebar-border: 240 3.7% 15.9%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "demo/src/app.svelte",
    "content": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import Extras from \"$routes/extras/extras.svelte\";\n  import Home from \"$routes/home.svelte\";\n  import Nested from \"$routes/nested/nested.svelte\";\n  import PathsAndParams from \"$routes/paths-and-params/paths-and-params.svelte\";\n  import Patterns from \"$routes/patterns/patterns.svelte\";\n  import Protected from \"$routes/protected/main.svelte\";\n  import Transitions from \"$routes/transitions/transitions.svelte\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router\";\n  import { goto, logging, registry, type Route, Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { BookHeart, Github, HelpCircle, MousePointerClick } from \"lucide-svelte\";\n\n  /**\n   * Only needed for the demo environment development.\n   *\n   * It is not needed for including the router package in your project.\n   */\n  if (import.meta.hot) {\n    import.meta.hot.accept(() => {\n      import.meta.hot!.invalidate();\n    });\n  }\n\n  /**\n   * This is a state variable that will hold the router instance.\n   *\n   * It can be used to access the current route, navigate, etc:\n   */\n  let router: RouterInstance = $state();\n\n  /**\n   * Get notified when the current route changes:\n   */\n  const route = $derived(router.current);\n  $effect(() => {\n    if (router.current) {\n      logging.info(\n        `🚀 I'm an $effect in app.svelte and i'm running because the current route is now ${router.current.result.path.original}!`\n      );\n    }\n  });\n\n  /**\n   * Let's declare our routes for the main app router:\n   */\n  const routes: RouteConfig[] = [\n    {\n      // You can name your routes anything you want for tracking or debugging:\n      name: \"default-route\",\n      hooks: {\n        pre: async (route: RouteResult) => {\n          console.error(`redirecting to ${session.mode === \"hash\" ? \"/#\" : \"\"}/home using a pre hook!`, route);\n          goto(`${session.mode === \"hash\" ? \"/#\" : \"\"}/home`);\n        },\n        post: async (route: RouteResult) => {\n          console.error(`post hook fired for route`, route);\n        }\n      }\n    },\n    {\n      path: \"/patterns\",\n      component: Patterns\n    },\n    {\n      // Here we use a regex to match the home route.\n      // This is useful if you want to match a route that has a dynamic path.\n      // The \"?:\" is used to group the regex without capturing the match:\n      // path: /^\\/($|home)$/,\n      path: \"home\",\n      component: Home,\n      // Use hooks to perform actions before and after the route is resolved:\n      hooks: {\n        pre: async (route: Route): Promise<boolean> => {\n          // console.log(\"pre hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // Hooks can also be an array of functions (async too):\n        post: [\n          // This is a post hook that will be executed after the route is resolved:\n          (route: Route): boolean => {\n            // console.log(\"post hook #1 fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          },\n          // This is an async post hook that will be executed after the route is resolved:\n          async (route: Route): Promise<boolean> => {\n            // console.log(\"post hook #2 (async) fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          }\n        ]\n      }\n    },\n    {\n      path: \"nested\",\n      component: Nested\n    },\n    {\n      path: \"paths-and-params\",\n      component: PathsAndParams\n    },\n    {\n      path: \"protected\",\n      component: Protected\n    },\n    {\n      path: \"transitions\",\n      component: Transitions\n    },\n    {\n      path: \"extras\",\n      component: Extras\n    }\n  ];\n\n  // This is a global pre hook that can be applied to all routes.\n  // Here you could check if the user is logged in or perform some other\n  // authentication checks:\n  const globalAuthGuardHook = async (route: Route): Promise<boolean> => {\n    console.warn(\"globalAuthGuardHook\", route);\n    // Return true so that the route can continue down its evaluation path.\n    return true;\n  };\n</script>\n\n<div class=\"flex h-screen flex-col gap-4 bg-zinc-700/25 p-4\">\n  <div class=\"flex items-start\">\n    <div class=\"flex flex-col gap-4\">\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <Github class=\"h-6 w-6\" />\n      </a>\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <BookHeart class=\"h-6 w-6 text-fuchsia-500\" />\n      </a>\n    </div>\n    <div class=\"logo h-51 w-60\">\n      <img\n        src=\"https://github.com/mateothegreat/svelte5-router/raw/dev/docs/assets/logo.png\"\n        alt=\"logo\" />\n    </div>\n    <div class=\"m-4 flex-1 flex justify-end text-indigo-400 gap-2 text-xs\">\n      <div class=\"text-slate-500 text-sm mb-3.5 rounded-md border-2 bg-black px-2 py-1.5\">\n        demo version: <a\n          href=\"https://github.com/mateothegreat/svelte5-router/tree/{window.__SVELTE5_ROUTER_VERSION__}\"\n          class=\"text-emerald-500 hover:text-blue-400 cursor-pointer\"\n          target=\"_blank\">\n          {window.__SVELTE5_ROUTER_VERSION__}\n        </a>\n      </div>\n    </div>\n  </div>\n  <div class=\"flex-1 gap-4 flex flex-col\">\n    <div\n      class=\"text-slate-500 justify-end items-center text-sm flex gap-2 rounded-md border-2 border-slate-700/75 bg-black/30 px-2 py-1.5\">\n      <MousePointerClick class=\"h-4 w-4 text-slate-500\" />\n      change url mode:\n      <button\n        class=\"flex items-center gap-1 rounded-md border-2 border-purple-600 bg-slate-900/50 font-semibold px-3 py-0.5 cursor-pointer hover:bg-slate-800 hover:border-green-600\"\n        class:text-orange-400={session.mode === \"hash\"}\n        class:text-green-400={session.mode === \"path\"}\n        onclick={() => {\n          session.mode = session.mode === \"hash\" ? \"path\" : \"hash\";\n        }}>\n        {session.mode === \"hash\" ? \"path\" : \"hash\"}\n      </button>\n    </div>\n    <RouteWrapper\n      name=\"main app router\"\n      title={{\n        file: \"src/app.svelte\",\n        content:\n          \"This is the main app component, it contains the top level router and then uses nested routers to divide and conquer your complex routing requirements! 🚀\"\n      }}\n      {router}\n      {route}\n      links={[\n        {\n          href: \"/\",\n          label: \"/\",\n          options: {\n            active: {\n              absolute: true\n            }\n          }\n        },\n        {\n          href: \"/home\",\n          label: \"/home\"\n        },\n        {\n          href: \"/patterns\",\n          label: \"/patterns\"\n        },\n        {\n          href: \"/protected\",\n          label: \"/protected\"\n        },\n        {\n          href: \"/paths-and-params\",\n          label: \"/paths-and-params\"\n        },\n        {\n          href: \"/nested\",\n          label: \"/nested\"\n        },\n        {\n          href: \"/transitions\",\n          label: \"/transitions\"\n        },\n        {\n          href: \"/404\",\n          label: \"/404\"\n        },\n        {\n          href: \"/extras\",\n          label: \"/extras\"\n        }\n      ]}>\n      <div class=\"flex-1\">\n        <Router\n          id=\"my-main-router\"\n          bind:instance={router}\n          {routes}\n          {...myDefaultRouterConfig} />\n      </div>\n    </RouteWrapper>\n  </div>\n</div>\n<div\n  class=\"fixed bottom-0 right-10 overflow-hidden rounded-t-md border-2 border-b-0 bg-neutral-950 text-xs text-gray-400\">\n  <p class=\"flex items-center gap-1.5 bg-black/80 p-2.5 text-sm font-medium text-slate-400\">\n    <a\n      href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md\"\n      class=\"text-yellow-300/70 hover:text-pink-500\"\n      target=\"_blank\">\n      <HelpCircle class=\"h-5 w-5\" />\n    </a>\n    router registry\n  </p>\n  <table class=\"divide-y divide-gray-900 overflow-hidden rounded-md border-2 text-xs text-gray-400\">\n    <thead>\n      <tr class=\"text-center tracking-wider text-slate-500\">\n        <th class=\"px-3 py-2 font-medium\">Router Name</th>\n        <th class=\"px-3 py-2 font-medium\">Routes</th>\n        <th class=\"px-3 py-2 font-medium\">State</th>\n        <th class=\"px-3 py-2 font-medium\">Current Path</th>\n      </tr>\n    </thead>\n    <tbody class=\"divide-y divide-gray-800 font-mono\">\n      {#each registry.instances.entries() as [key, instance]}\n        <tr>\n          <td class=\"px-3 py-2 text-left text-indigo-400\">\n            {key}\n          </td>\n          <td class=\"px-3 py-2 text-pink-500\">\n            {instance.routes.size}\n          </td>\n          <td class=\"px-3 py-2\">\n            {#if instance.navigating}\n              <span class=\"text-green-500\">busy</span>\n            {:else}\n              <span class=\"text-gray-500\">idle</span>\n            {/if}\n          </td>\n          <td class=\"px-3 py-2 text-green-500\">\n            {instance.current?.path || \"<default>\"}\n          </td>\n        </tr>\n      {/each}\n    </tbody>\n  </table>\n</div>\n"
  },
  {
    "path": "demo/src/lib/components/badge.svelte",
    "content": "<script lang=\"ts\">\n  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n\n  let {\n    icon,\n    text,\n    variant = \"info\",\n    class: className,\n    children\n  }: {\n    icon?: any;\n    text?: string;\n    variant?: \"info\" | \"success\" | \"warning\" | \"error\";\n    class?: string;\n    children?: Snippet;\n  } = $props();\n\n  const variants = {\n    info: {\n      classes: \"bg-blue-900 text-white\",\n      icon: InfoIcon\n    },\n    success: {\n      classes: \"bg-green-600/80 text-white\",\n      icon: CheckCircleIcon\n    },\n    warning: {\n      classes: \"bg-yellow-200/80 text-black\",\n      icon: AlertTriangleIcon\n    },\n    error: {\n      classes: \"bg-red-900/80 text-white\",\n      icon: XCircleIcon\n    }\n  };\n\n  let Icon = icon ?? variants[variant].icon;\n</script>\n\n<span\n  class=\"inline-flex items-center gap-1.5 rounded-sm p-2 text-sm font-medium {variants[variant].classes} {className}\">\n  {#if Icon}\n    <Icon class=\"inline-block h-5 w-5\" />\n  {/if}\n  {text}\n  {@render children?.()}\n</span>\n"
  },
  {
    "path": "demo/src/lib/components/code.svelte",
    "content": "<script lang=\"ts\">\n  import { transformerColorizedBrackets } from \"@shikijs/colorized-brackets\";\n  import { Code2 } from \"lucide-svelte\";\n  import { createHighlighter, type Highlighter } from \"shiki\";\n  import { onMount, type Snippet } from \"svelte\";\n  import FileLink from \"./file-link.svelte\";\n\n  let {\n    title,\n    file,\n    class: className,\n    children,\n    language = \"typescript\",\n    theme = \"github-dark-dimmed\"\n  }: {\n    title?: string;\n    file?: string;\n    class?: string;\n    children?: Snippet;\n    language?: string;\n    theme?: string;\n  } = $props();\n\n  // State for the highlighted HTML content\n  let highlightedContent = $state<string>(\"\");\n  // State for the reference to the hidden temporary element\n  let tempElement: HTMLElement | undefined = $state(undefined);\n  // State for the Shiki highlighter instance\n  let shikiHighlighter: Highlighter | null = $state(null);\n\n  /**\n   * Initializes the Shiki highlighter instance once when the component mounts.\n   */\n  onMount(async () => {\n    shikiHighlighter = await createHighlighter({\n      themes: [theme],\n      langs: [\"typescript\", \"svelte\", \"html\", \"css\", \"json\"]\n    });\n  });\n\n  /**\n   * Reactive effect that updates the syntax highlighting whenever\n   * the children, language, or highlighter instance changes.\n   */\n  $effect(() => {\n    if (!shikiHighlighter || !children || !tempElement) {\n      if (!children) {\n        highlightedContent = \"\";\n      }\n      return;\n    }\n\n    /**\n     * At this point, Svelte has rendered the output of `children()` into `tempElement`\n     * due to the `{@render children()}` directive in the hidden div.\n     * We can now extract the raw text content from it.\n     */\n    const codeContent = (tempElement.textContent || \"\").trim();\n\n    if (codeContent) {\n      try {\n        // Convert the raw code string to HTML using Shiki.\n        highlightedContent = shikiHighlighter.codeToHtml(codeContent, {\n          lang: language,\n          theme: theme,\n          transformers: [transformerColorizedBrackets()]\n        });\n      } catch (error) {\n        console.error(\"Shiki highlighting error:\", error, { language, codeContent });\n        // Fallback to showing raw code (escaped) if highlighting fails.\n        highlightedContent = `<pre class=\"shiki-fallback\"><code>${escapeHtml(codeContent)}</code></pre>`;\n      }\n    } else {\n      // Clear highlighted content if there's no text content.\n      highlightedContent = \"\";\n    }\n  });\n\n  /**\n   * Helper function to escape HTML special characters.\n   * Used for the fallback when Shiki highlighting fails.\n   */\n  function escapeHtml(unsafe: string): string {\n    return unsafe\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\")\n      .replace(/\"/g, \"&quot;\")\n      .replace(/'/g, \"&#039;\");\n  }\n</script>\n\n<div class=\"code-block-container flex flex-col rounded-md border-2 border-slate-700 bg-black/80 {className}\">\n  <div class=\"header flex items-center justify-between bg-zinc-900/70 text-sm p-1.5 px-3 flex-shrink-0\">\n    <p class=\"flex items-center gap-1.5 font-mono text-xs text-slate-300\">\n      <Code2 class=\"h-4 w-4 text-orange-500 shrink-0\" />\n      {#if title}\n        <span>{title}</span>\n      {:else}\n        <span>Code</span>\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n\n  <!--\n    Temporary hidden element.\n    The `children` snippet is rendered here by Svelte using `{@render children()}`.\n    This component's `$effect` then reads `tempElement.textContent` to get the\n    raw string representation of the rendered children for Shiki to process.\n  -->\n  <div\n    bind:this={tempElement}\n    style=\"display: none;\"\n    aria-hidden=\"true\">\n    {#if children}\n      {@render children()}\n    {/if}\n  </div>\n\n  {#if highlightedContent}\n    <div class=\"p-2.5 text-[13px] leading-[18px] overflow-auto bg-[#0a0a0a] flex-1 min-h-0\">\n      {@html highlightedContent}\n    </div>\n  {:else if children}\n    <pre class=\"fallback-pre p-3 font-mono text-sm text-slate-200 overflow-auto flex-1 min-h-0\">\n      {@render children()}\n    </pre>\n  {:else}\n    <div class=\"p-3 text-slate-500 text-sm font-mono\">Loading syntax highlighter...</div>\n  {/if}\n</div>\n\n<style>\n  :global(.shiki) {\n    background: #090909 !important;\n  }\n</style>\n"
  },
  {
    "path": "demo/src/lib/components/container.svelte",
    "content": "<script lang=\"ts\">\n  import FileLink from \"./file-link.svelte\";\n\n  let { title, file, children }: { title?: string; file?: string; children: any } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1 rounded-md border-2 border-slate-700 bg-gray-900 text-slate-400\">\n  <div class=\"flex items-center justify-between bg-slate-800 p-2 text-sm text-slate-500\">\n    <p class=\"flex items-center gap-2 font-mono\">\n      {#if title}\n        &lt;{title} /&gt;\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n  {@render children()}\n</div>\n"
  },
  {
    "path": "demo/src/lib/components/default.svelte",
    "content": "<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n  import Badge from \"./badge.svelte\";\n\n  let { class: className, children }: { class?: string; children?: Snippet } = $props();\n</script>\n\n<div class=\"flex flex-col gap-4 rounded-md border-4 border-slate-900/80 p-4 text-center text-gray-400 {className}\">\n  {#if children}\n    {@render children()}\n  {:else}\n    <p>\n      <Badge\n        variant=\"info\"\n        icon={BadgeInfo}>\n        There was no path provided to the router, so the default route was used (declared as a snippet).\n      </Badge>\n    </p>\n    Click on a link above to see the different effects!\n  {/if}\n</div>\n"
  },
  {
    "path": "demo/src/lib/components/file-link.svelte",
    "content": "<script lang=\"ts\">\n  import { FileCode2, GithubIcon } from \"lucide-svelte\";\n\n  let { file } = $props();\n</script>\n\n<div class=\"flex gap-1\">\n  <div class=\"flex flex-1 items-center text-slate-500\">\n    <FileCode2 class=\"h-4 w-4\" />\n  </div>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-orange-400 hover:font-medium hover:text-indigo-400\">\n    {file}\n  </a>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    target=\"_blank\"\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-slate-500 hover:font-medium hover:text-indigo-400\">\n    view source\n    <GithubIcon class=\"h-4 w-4\" />\n  </a>\n</div>\n"
  },
  {
    "path": "demo/src/lib/components/inline-code.svelte",
    "content": "<script lang=\"ts\">\n  export type InlineCodeProps = {\n    text: string;\n    class?: string;\n  };\n\n  let { text, class: className }: InlineCodeProps = $props();\n\n  if (!className) {\n    className = \"text-green-400 bg-black/50\";\n  }\n</script>\n\n<span class=\"whitespace-nowrap rounded-md p-1 px-2 font-mono text-sm {className}\">{text}</span>\n"
  },
  {
    "path": "demo/src/lib/components/routes/route-link.svelte",
    "content": "<script lang=\"ts\">\n  import { session } from \"$lib/session.svelte\";\n  import { route, RouteOptions } from \"@mateothegreat/svelte5-router\";\n\n  export type RouteLinkProps = {\n    options?: RouteOptions;\n    href: string;\n    label: string;\n  };\n\n  let { options, href, label }: RouteLinkProps = $props();\n  if (!options) {\n    options = new RouteOptions();\n  }\n\n  if (!options.active) {\n    options.active = {\n      class: [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"]\n    };\n  }\n  if (!options.default) {\n    options.default = {\n      class: [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"]\n    };\n  }\n  if (!options.loading) {\n    options.loading = {\n      class: [\"loading\", \"bg-orange-500\"]\n    };\n  }\n  if (!options.disabled) {\n    options.disabled = {\n      class: [\"disabled\", \"bg-gray-500\"]\n    };\n  }\n\n  if (!options.active.class) {\n    options.active.class = [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"];\n  }\n\n  if (!options.default.class) {\n    options.default.class = [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"];\n  }\n  if (!options.loading.class) {\n    options.loading.class = [\"loading\", \"bg-orange-500\"];\n  }\n  if (!options.disabled.class) {\n    options.disabled.class = [\"disabled\", \"bg-gray-500\"];\n  }\n</script>\n\n<a\n  use:route={options}\n  href={session.mode === \"hash\" ? `/#${href}` : href}\n  class=\"duration-400 flex items-center rounded-sm border-2 px-2.5 py-0.5 text-sm transition-all hover:border-green-300 hover:bg-green-600 hover:text-white\">\n  {#if session.mode === \"hash\"}\n    <span>/#</span>\n    <span>/{label.startsWith(\"/\") ? label.slice(1) : label}</span>\n  {:else}\n    <span>\n      {label}\n    </span>\n  {/if}\n</a>\n"
  },
  {
    "path": "demo/src/lib/components/routes/route-title.svelte",
    "content": "<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowDown, ArrowRight, ArrowRightFromLine, StopCircle } from \"lucide-svelte\";\n  import FileLink from \"../file-link.svelte\";\n\n  export type RouteTitleProps = {\n    router?: RouterInstance;\n    route?: RouteResult;\n    file?: string;\n    content?: any;\n    end?: boolean;\n  };\n\n  let { router = $bindable(), route, file, content, end }: RouteTitleProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex items-center gap-3 rounded-md bg-black/50 p-1.5 px-2 border-2\">\n    {#if router}\n      <div class=\"flex flex-wrap items-center rounded-sm bg-gray-800 px-1.5 py-0.5 text-sm text-slate-500\">\n        <ArrowRightFromLine class=\"h-4 w-4 text-green-400 mr-1\" />\n        {router.config.id}\n        {#if router.navigating}\n          <span class=\"px-1 py-0.5 text-red-400\">(hooks firing)</span>\n        {:else}\n          <span class=\"px-1 py-0.5 text-slate-600\">(idle)</span>\n        {/if}\n        routed the path\n        <span class=\"px-1 py-0.5 text-green-400\">\n          {route?.absolute?.()}\n        </span>\n        and nesting&nbsp;\n        {#if end}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-red-400\">stopped</span>\n            <StopCircle class=\"h-4 w-4 text-red-400\" />\n          </span>\n        {:else}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-green-400\">continued</span>\n            <ArrowDown class=\"h-4 w-4 text-green-400\" />\n          </span>\n        {/if}\n      </div>\n    {/if}\n    <ArrowRight class=\"h-4 w-4 text-slate-500\" />\n    <FileLink {file} />\n  </div>\n  {#if content}\n    <div class=\"p-2\">\n      {#if typeof content === \"string\"}\n        <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n          <div class=\"max-w-3xl text-sm text-slate-500\">\n            {content}\n          </div>\n        </div>\n      {:else}\n        <div class=\"flex items-center\">\n          {@render content()}\n        </div>\n      {/if}\n    </div>\n  {/if}\n</div>\n"
  },
  {
    "path": "demo/src/lib/components/routes/route-wrapper.svelte",
    "content": "<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Anchor } from \"lucide-svelte\";\n  import type { RouteLinkProps } from \"./route-link.svelte\";\n  import RouteLink from \"./route-link.svelte\";\n  import type { RouteTitleProps } from \"./route-title.svelte\";\n  import RouteTitle from \"./route-title.svelte\";\n\n  export type RouteWrapperProps = {\n    router?: RouterInstance;\n    name: string;\n    route?: RouteResult;\n    end?: boolean;\n    title: RouteTitleProps;\n    links: RouteLinkProps[];\n    children?: any;\n  };\n\n  let { router = $bindable(), name, route, title, links, children, end = $bindable() }: RouteWrapperProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-3 border-2 rounded-md h-full p-2.5\">\n  <div class=\"flex flex-col gap-4\">\n    <RouteTitle\n      {router}\n      {route}\n      {end}\n      file={title.file}\n      content={title.content} />\n    <div class=\"flex w-fit items-center gap-2 rounded-md border-2 border-slate-900/80 bg-slate-700/80 p-1.5\">\n      <p class=\"flex items-center gap-1 text-sm text-slate-300\">\n        <Anchor class=\"h-4 w-4 text-indigo-500\" />\n        <span class=\"text-pink-400\">{router?.config.id}</span>\n        routes:\n      </p>\n      {#each links as link}\n        <RouteLink {...link} />\n      {/each}\n    </div>\n  </div>\n  <div class=\"flex-1\">\n    {@render children?.()}\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/lib/default-route-config.ts",
    "content": "import { StatusCode, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport NotFound from \"$routes/not-found.svelte\";\n\n/**\n * Surface a reusable configuration for routers to import\n * and apply to their router instances:\n *\n * @example\n * ```ts\n * <script lang=\"ts\">\n *   import { RouteConfig } from \"@mateothegreat/svelte5-router\";\n *   import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n *\n *   const routes: RouteConfig[] = [\n *     {\n *       path: \"/home\",\n *       component: Home\n *     }\n *   ];\n * </script>\n *\n * <Router\n *   id=\"my-main-router\"\n *   {routes}\n *   {...myDefaultRouterConfig} />\n * ```\n */\nexport const myDefaultRouterConfig = {\n  statuses: {\n    /**\n     * You can use a function to return a new route or a promise that\n     * resolves to a new route:\n     */\n    [StatusCode.NotFound]: (result: RouteResult) => {\n      console.log(result);\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n    /**\n     * You can also use an object to return a new route while having access\n     * to the path and querystring:\n     *\n     * [StatusCode.NotFound]: (path: RouteResult) => ({\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }),\n     *\n     *\n     * or simply return an object with a component and props:\n     *\n     * [StatusCode.NotFound]: {\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }\n     */\n  }\n};\n"
  },
  {
    "path": "demo/src/lib/router-history.ts",
    "content": "import type { Route } from \"@mateothegreat/svelte5-router\";\n\nexport const history = $state<Route[]>([]);\n\nexport const appendHistory = (route: Route) => {\n  history.push(route);\n};\n"
  },
  {
    "path": "demo/src/lib/session.svelte.ts",
    "content": "let _state: {\n  mode: \"hash\" | \"path\";\n} = $state({\n  mode: (localStorage.getItem(\"mode\") as \"hash\" | \"path\") || \"path\"\n});\n\nexport const session = {\n  set mode(value: \"hash\" | \"path\") {\n    localStorage.setItem(\"mode\", value);\n    _state = {\n      ..._state,\n      mode: value\n    };\n    if (value === \"hash\") {\n      window.history.pushState({}, \"\", `/#/`);\n    } else {\n      window.history.pushState({}, \"\", \"/\");\n    }\n  },\n  get mode() {\n    return _state.mode;\n  }\n};\n"
  },
  {
    "path": "demo/src/main.ts",
    "content": "import { mount } from \"svelte\";\n\nimport './app.css';\nimport App from './app.svelte';\n\nconst app = mount(App, {\n  target: document.getElementById('app')!,\n})\n\nexport default app\n"
  },
  {
    "path": "demo/src/routes/delayed.svelte",
    "content": "<div class=\"bg-indigo-400 p-10\">\n  <h1>\n    a delayed route component, the text for \"Navigating\" will be \"Navigating: busy\" while this component is loading\n  </h1>\n</div>\n"
  },
  {
    "path": "demo/src/routes/extras/dump.svelte",
    "content": "<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/extras/extras.svelte",
    "content": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { onDestroy } from \"svelte\";\n  import PassingDownProps from \"./passing-down-props.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  let randoms = $state({\n    float: (Math.random() * 1000).toFixed(2),\n    int: (Math.random() * 1000).toFixed(0),\n    string: (Math.random() * 1000).toFixed(2).toString()\n  });\n\n  const interval = setInterval(() => {\n    randoms.float = (Math.random() * 1000).toFixed(2);\n    randoms.int = (Math.random() * 1000).toFixed(0);\n    randoms.string = (Math.random() * 1000).toFixed(2).toString();\n  }, 750);\n\n  onDestroy(() => {\n    clearInterval(interval);\n  });\n\n  const routes: RouteConfig[] = [\n    {\n      component: overview\n    },\n    {\n      path: \"passing-down-props\",\n      component: PassingDownProps,\n      props: {\n        route: \"passing-down-props\"\n      },\n      hooks: {\n        pre: () => {\n          console.log(\"pre\");\n          return true;\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet overview()}\n  <div class=\"p-2\">\n    <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n      <div class=\"flex max-w-3xl flex-col gap-2 text-sm text-slate-500\">\n        <p class=\"text-fuchsia-500\">Extra & cool stuff can be demoed here.</p>\n        <p>Click a route above to see the different effects!</p>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"extras\"\n  {route}\n  title={{\n    file: \"src/routes/extras/extras.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"passing-down-props\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras\"\n    bind:instance={router}\n    {routes} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/extras/passing-down-props.svelte",
    "content": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import Dump from \"./dump.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  const routes: RouteConfig[] = [\n    {\n      component: Dump,\n      props: {\n        foo: \"bar\",\n        baz: {\n          awesome: true\n        }\n      }\n    }\n  ];\n</script>\n\n<RouteWrapper\n  {router}\n  name=\"passing-down-props\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/extras/passing-down-props.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"default path\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras/passing-down-props\"\n    bind:instance={router}\n    myAdditionalProp=\"I was added to the <Router/> component directly.\"\n    {routes} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/hash/hash.svelte",
    "content": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  console.log(location.hash);\n</script>\n\n<a\n  use:route\n  href=\"/hash/#b?test=4\">\n  b\n</a>\n\n<a\n  use:route\n  href=\"/hash/#a?test=123\">\n  a\n</a>\n"
  },
  {
    "path": "demo/src/routes/home.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { RouterInstance, type Route, type RouteResult } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { Github, MessageCircleQuestion, Newspaper } from \"lucide-svelte\";\n\n  let { route }: { route: RouteResult } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/welcome\" or \"/home/welcome\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"/\",\n      component: welcome\n    },\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/with-query-params\" or \"/home/with-query-params\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"with-query-params\",\n      component: displayRouteProps\n    }\n  ];\n</script>\n\n{#snippet welcome()}\n  <div class=\"flex flex-col gap-5 rounded-md border-2 border-gray-800 bg-gray-800/50 p-4 text-sm text-slate-300\">\n    <h1 class=\"text-xl font-bold\">Single Page Application Router (SPAR) for Svelte 5+</h1>\n    <p>\n      <InlineCode\n        text=\"@mateothegreat/svelte5-router\"\n        class=\"bg-black text-blue-500\" /> is an SPA router for Svelte that allows you to divide & conquer your app with nested\n      routers, snippets, and more.\n    </p>\n    <div class=\"flex gap-3\">\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-violet-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Newspaper class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/readme.md\"\n          class=\"\">\n          Documentation\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Github class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router\"\n          class=\"\">\n          GitHub Repository\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <MessageCircleQuestion class=\"h-5 w-5\" />\n        <a href=\"https://github.com/mateothegreat/svelte5-router/issues\">GitHub Issues</a>\n      </div>\n    </div>\n    <div class=\"flex flex-col gap-2\">\n      <h2 class=\"text-lg font-semibold text-indigo-400\">Features</h2>\n      <ul class=\"list-disc space-y-1 pl-6 text-slate-300\">\n        <li>Built for Svelte 5 🚀!</li>\n        <li>Divide & conquer - use nested routers all over the place</li>\n        <li class=\"text-teal-400\">Use components, snippets, or both 🔥!</li>\n        <li>Use regex paths (e.g. /foo/(.*?)/bar) and/or named parameters together</li>\n        <li>Use async routes simply with component: async () => import(\"./my-component.svelte\")</li>\n        <li class=\"font-bold\">Add hooks to your routes to control the navigation flow 🔧</li>\n        <li>Automagic styling of your anchor tags 💄</li>\n        <li>Helper methods 🛠️ to make your life easier</li>\n        <li>Debugging tools included 🔍</li>\n      </ul>\n    </div>\n    <div class=\"flex items-center\">\n      Get started now with\n      <InlineCode\n        text=\"npm install @mateothegreat/svelte5-router\"\n        class=\"mx-1 bg-black\" />\n      and check out the\n      <a\n        class=\"mx-1 cursor-pointer text-violet-400 hover:text-green-500 hover:underline\"\n        href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md\">\n        getting started guide..\n      </a>\n    </div>\n  </div>\n{/snippet}\n\n{#snippet displayRouteProps()}\n  <div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n    <div class=\"flex flex-col gap-5\">\n      <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n        match path\n        <InlineCode text={route.route.path.toString()} />\n        to\n        <InlineCode text={`${route.route.path}?someQueryParam=123`} />\n      </div>\n      <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n        <div class=\" gap-1\">\n          This demo shows how to use the route's\n          <InlineCode text=\"querystring\" />\n          configuration option to match against the current\n          <InlineCode text=\"location.search\" />\n          value passed in by the browser.\n        </div>\n        <Badge\n          variant=\"success\"\n          class=\"w-fit\">\n          Because we did not specify any <InlineCode text=\"querystring\" /> parameters, the route render regardless of the\n          <InlineCode text=\"querystring\" /> and will be passed to the component as shown below.\n        </Badge>\n      </div>\n    </div>\n    <Code\n      title={\"{#snippet displayRouteProps()}\"}\n      file=\"src/routes/home.svelte\">\n      {JSON.stringify(route.result, null, 2)}\n    </Code>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"home-router\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/home.svelte\",\n    content:\n      \"This route is a child of the main app router where you are redirected to /home/welcome when landing on /home using a `pre` hook.\"\n  }}\n  links={[\n    {\n      href: \"/home\",\n      label: \"/home\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/home/with-query-params?someQueryParam=123\",\n      label: \"/home/with-query-params?someQueryParam=123\"\n    }\n  ]}>\n  <Router\n    id=\"home-router\"\n    basePath=\"/home\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-1.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import Level_2 from \"./level-2/level-2.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route, foo } = $props();\n\n  /**\n   * Demonstrate how support for additional props is working.\n   */\n  console.log(\"additionalProps.foo in ../nested.svelte is passed to this component as:\", foo);\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-1.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-1\"\n  {end}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-1.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"/nested/level-1/level-2\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-1-router\"\n    basePath=\"/nested/level-1\"\n    bind:instance={router}\n    routes={[\n      {\n        path: \"level-2\",\n        component: Level_2,\n        hooks: {\n          pre: () => {\n            console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-1.svelte route)`);\n            return true;\n          }\n        }\n      },\n      /**\n       * Default routes can be placed anywhere in the routes array\n       * and will be matched if no other routes match regardless of\n       * their position in the array:\n       */\n      {\n        component: snippet\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-2/level-2.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_3 from \"./level-3/level-3.svelte\";\n\n  const routes: Route[] = [\n    {\n      path: \"level-3\",\n      component: Level_3,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-2.svelte route)`);\n          return true;\n        }\n      }\n    },\n    {\n      component: snippet\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-2/level-2.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-2\"\n  end={true}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-2/level-2.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2/level-3\",\n      label: \"/nested/level-1/level-2/level-3\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-2-router\"\n    basePath=\"/nested/level-1/level-2\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte",
    "content": "<script>\n  import Container from \"$lib/components/container.svelte\";\n</script>\n\n<Container\n  title={\"Level_3\"}\n  file=\"src/routes/nested/level-1/level-2/level-3/level-3.svelte\">\n  <div class=\"flex flex-col gap-3 bg-green-500 p-4 text-center font-medium text-white\">\n    You've reached the deepest level of nested routing (level-3)!\n  </div>\n</Container>\n"
  },
  {
    "path": "demo/src/routes/nested/nested.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_1 from \"./level-1/level-1.svelte\";\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"level-1\",\n      component: Level_1,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1\" matched (I'm a pre hook in the nested.svelte route)`);\n          return true;\n        }\n      }\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n\n  const additionalProps = {\n    foo: {\n      bar: \"baz\"\n    }\n  };\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/nested.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/nested/nested.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1\",\n      label: \"/nested/level-1\"\n    }\n  ]}>\n  <Router\n    id=\"nested-router\"\n    basePath=\"/nested\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes}\n    {...additionalProps} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/not-found.svelte",
    "content": "<script lang=\"ts\">\n  let { route } = $props();\n  $inspect(route);\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n"
  },
  {
    "path": "demo/src/routes/paths-and-params/custom-not-found.svelte",
    "content": "<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"custom-not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n"
  },
  {
    "path": "demo/src/routes/paths-and-params/display-params.svelte",
    "content": "<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<Code\n  title=\"params.route value:\"\n  file=\"src/routes/props/display-params.svelte\">\n  <div>{JSON.stringify(route, null, 2)}</div>\n</Code>\n"
  },
  {
    "path": "demo/src/routes/paths-and-params/paths-and-params.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { getStatusByValue, RouterInstance, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import CustomNotFound from \"./custom-not-found.svelte\";\n  import DisplayParams from \"./display-params.svelte\";\n  import QuerystringMatching from \"./querystring-matching.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  const routes: RouteConfig[] = [\n    /**\n     * This route will be used if there is no matching routes we\n     * define below:\n     */\n    {\n      component: snippet\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?number=2&number-as-string=2\n     *   ✅ /paths-and-params/query-matcher?number=2.1&number-as-string=2.345\n     *   ❌ /paths-and-params/query-matcher?number=2&number-as-string=two\n     */\n    {\n      name: \"match-number-and-string\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"number\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        float: /^([\\d.]+)$/,\n        /**\n         * The \"number-as-string\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        string: \"123\"\n      }\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?pagination=1,10\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=1234567\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=1234567\n     *   ❌ /paths-and-params/query-matcher?pagination=1,&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=2,3,4\n     *   ❌ /paths-and-params/query-matcher?pagination=bad-value\n     */\n    {\n      name: \"match-pagination\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"pagination\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number\n         *   - ❔ and then be followed by an optional \"cursor\" parameter:\n         *     - ✅ it must have a comma delimiter\n         *     - ✅ it must be a string of alphanumeric characters only\n         */\n        pagination: /^(?<page>\\d+)(?:,(?<cursor>\\d+))?$/,\n        /**\n         * The \"company\" querystring parameter is optional, but if present:\n         *\n         *   - ✅ can be empty\n         *   - ✅ must be a single number\n         */\n        company: /^(\\d+)?$/\n      },\n      props: {\n        metadata: {\n          src: \"paths-and-params.svelte\"\n        }\n      }\n    },\n    /**\n     * This route will match any path and pass the pattern groups\n     * as an object to the component that is passed in $props().\n     *\n     * The component will access the params using $props() and the\n     * property \"child\" will contain the value extracted from the path.\n     */\n    {\n      path: \"extensions/(?<extension>.*)/(?<page>[^/]+)(?:/(?<rest>.*))?\",\n      component: DisplayParams\n    },\n    {\n      name: \"fancy-regex-capture-group\",\n      path: \"(foo|bar).*\",\n      component: DisplayParams,\n      props: {\n        randomId: Math.random().toString(36).substring(2, 15),\n        someUserStuff: {\n          username: \"mateothegreat\",\n          userAgent: navigator.userAgent\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/paths-and-params/paths-and-params.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/paths-and-params\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/paths-and-params/paths-and-params.svelte\",\n    content: \"This demo shows how to pass values downstream to the component that is rendered.\"\n  }}\n  links={[\n    {\n      href: \"/paths-and-params\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/paths-and-params/foo\",\n      label: \"foo\"\n    },\n    {\n      href: \"/paths-and-params/query-matcher?pagination=2,23&company=123\",\n      label: \"query-matcher?pagination=2,23&company=123\"\n    }\n  ]}>\n  <Router\n    id=\"props-router\"\n    basePath=\"/paths-and-params\"\n    bind:instance={router}\n    {routes}\n    hooks={{\n      /**\n       * You could use a global auth guard here to run before every route:\n       *\n       * hooks={{\n       *   pre: (route: Routed) => {\n       *     if (!isAuthenticated()) {\n       *       console.warn(\"user is not authenticated, redirecting to login\", route);\n       *       return {\n       *         component: NotGonnaMakeIt,\n       *       };\n       *     }\n       *   }\n       * }}\n       *\n       * You could also use a global error handler here to run after every route:\n       *\n       * hooks={{\n       *   post: [\n       *     (route: Routed) => {\n       *       console.info(\"do some more work here\", route);\n       *       return true;\n       *     },\n       *     someLogMethod,\n       *     finalMethod,\n       *   ]\n       * }}\n       */\n    }}\n    statuses={{\n      [StatusCode.NotFound]: (result: RouteResult) => {\n        console.warn(`the path \"${result.result.path.original}\" could not be found :(`, {\n          /**\n           * You could use the status name to make something pretty:\n           */\n          status: getStatusByValue(StatusCode.NotFound),\n          /**\n           * You could also use the status code to something more dynamic:\n           */\n          code: StatusCode.NotFound\n        });\n        /**\n         * Now, we're going to return a new route that will be rendered by the router:\n         */\n        return {\n          component: CustomNotFound,\n          /**\n           * You can pass props to the component that is rendered if you need to\n           * share some extra information:\n           */\n          props: {\n            src: \"props.svelte\"\n          }\n        };\n      }\n    }} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/paths-and-params/querystring-matching.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n  <div class=\"flex flex-col gap-5\">\n    <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n      <InlineCode text=\"querystring\" />\n      match multiple values\n      <InlineCode text=\"RegExp\" />\n      <InlineCode text=\"number\" />\n      <InlineCode text=\"string\" />\n      <InlineCode text=\"boolean\" />\n      <InlineCode text=\"array\" />\n    </div>\n    <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n      <Badge\n        variant=\"warning\"\n        class=\"w-fit\">\n        Matching will occur if <em><strong>all</strong></em>\n        querystring keys and values match what was provided in the `querystring` configuration option.\n      </Badge>\n      <div class=\" gap-1\">\n        This demo shows how to use the route's\n        <InlineCode text=\"querystring\" />\n        configuration option to match against the current\n        <InlineCode text=\"location.search\" />\n        value passed in by the browser.\n      </div>\n      <Badge\n        variant=\"success\"\n        class=\"w-fit\">\n        There are 2 potential matches for the path \"/paths-and-params/query-matcher\". Notice how the active route is\n        highlighted in green for this route only + the querystring matching.\n      </Badge>\n    </div>\n  </div>\n  <div class=\"flex flex-col gap-6 border-2 bg-gray-900/60 rounded-lg p-4 border-slate-700\">\n    <Code\n      title=\"$props().route.result value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div>{JSON.stringify(route.result, null, 2)}</div>\n    </Code>\n    <Code\n      title=\"$props().route.route value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div class=\"text-indigo-400\">{JSON.stringify(route.route, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/patterns/dump.svelte",
    "content": "<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n\n  import { Lightbulb } from \"lucide-svelte\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    language=\"svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/patterns/output.svelte",
    "content": "<div class=\"flex flex-col gap-4 p-4\">\n  <div>\n    <h1 class=\"text-xl font-semibold text-slate-200\">Nested Paths</h1>\n    <p class=\"text-slate-400\">\n      This example will match any path that starts with `/path/path/path` and can be nested further.\n    </p>\n  </div>\n  <div class=\"bg-slate-800/50 rounded-lg p-3\">\n    <h3 class=\"text-sm font-medium text-slate-300 mb-2\">Route Props:</h3>\n    <pre class=\"text-xs text-slate-400 overflow-auto\">{JSON.stringify(props, null, 2)}</pre>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/patterns/parameter-extraction.svelte",
    "content": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route: r, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1\">\n  <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">Parameter Extraction</h1>\n  <div class=\"text-slate-500 text-sm\">\n    <p>\n      Given the following route config, everything after the \"/parameter-extraction\" will be available as a named\n      parameter. This means that the following paths will be matched:\n    </p>\n    <ul class=\"list-disc list-inside\">\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/foo\">\n          parameter-extraction/foo\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/bar\">\n          parameter-extraction/bar\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/baz\">\n          parameter-extraction/baz\n        </a>\n      </li>\n    </ul>\n  </div>\n</div>\n\n<div class=\"flex gap-3\">\n  <Code\n    title=\"RouteConfig:\"\n    language=\"typescript\"\n    class=\"flex-1\">\n    {`  const routes: RouteConfig[] = [\n  {\n    path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n    component: ParameterExtraction\n  }\n];`}\n  </Code>\n  <Code\n    title=\"$props().route.result.path.params\"\n    language=\"json\"\n    class=\"flex-1\">\n    <div>{JSON.stringify(r.result.path.params, null, 2)}</div>\n  </Code>\n</div>\n"
  },
  {
    "path": "demo/src/routes/patterns/patterns.svelte",
    "content": "<script lang=\"ts\">\n  import { route, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import type { TableColumn } from \"@mateothegreat/svelte5-table\";\n  import { DropinTable } from \"@mateothegreat/svelte5-table\";\n  import { CirclePlay, Lightbulb, MousePointerClick } from \"lucide-svelte\";\n  import { writable, type Writable } from \"svelte/store\";\n  import Dump from \"./dump.svelte\";\n  import ParameterExtraction from \"./parameter-extraction.svelte\";\n\n  type Component = {\n    name: string;\n    description: string;\n    path: string;\n  };\n\n  const columns: TableColumn[] = [\n    {\n      field: \"name\",\n      header: \"Routing Pattern\"\n    },\n    {\n      field: \"description\",\n      header: \"Description\"\n    },\n    {\n      field: \"actions\",\n      header: customHeader,\n      class: \"w-[300px]\",\n      renderer: action\n    }\n  ];\n\n  const components: Writable<Component[]> = writable([\n    {\n      name: \"Default Route\",\n      description: \"If the path is empty, the route will be matched otherwise evaluation will continue.\",\n      path: \"/default-route\"\n    },\n    {\n      name: \"Single Path\",\n      description: \"This example will match any path that starts with `/path`.\",\n      path: \"/single-path\"\n    },\n    {\n      name: \"Nested Paths\",\n      description: \"This example will match any path that starts with `/path/path/path` and can be nested further.\",\n      path: \"/nested-paths\"\n    },\n    {\n      name: \"Parameter Extraction\",\n      description: \"Combine arbitrary paths and extractable parameters.\",\n      path: \"/parameter-extraction\"\n    },\n    {\n      name: \"Named Parameters\",\n      description:\n        \"This example will match any path that starts with `/path/path/path/path` and can be nested further.\",\n      path: \"/named-parameters\"\n    }\n  ]);\n\n  let selections = writable([]);\n  const routes: RouteConfig[] = [\n    {\n      path: \"default-route\",\n      component: Dump\n    },\n    {\n      path: \"single-path\",\n      component: Dump\n    },\n    {\n      path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n      component: ParameterExtraction\n    }\n  ];\n</script>\n\n{#snippet customHeader()}\n  <div class=\"flex items-center gap-1 pl-2\">\n    Navigate to Demo\n    <CirclePlay class=\"h-4 w-4 text-green-500\" />\n  </div>\n{/snippet}\n\n{#snippet action(row: any)}\n  <div class=\"flex flex-1 ml-2\">\n    <a\n      use:route\n      href={`/patterns${row.path}`}\n      class=\"bg-gray-800/50 flex items-center gap-1 rounded-sm border-2 border-purple-700 px-2 py-1 text-sm text-slate-500 transition-all hover:border-green-300 hover:bg-slate-200/20 hover:text-white duration-400\">\n      <MousePointerClick class=\"h-4 w-4\" />\n      <span class=\"text-green-500 flex items-center gap-0.5\">\n        goto(\"\n        <span class=\"text-sky-400\">\n          {row.path}\n        </span>\n        \")\n      </span>\n    </a>\n  </div>\n{/snippet}\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex flex-col gap-1\">\n    <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">\n      <Lightbulb class=\"h-5 w-5 text-cyan-400\" />\n      Routing Patterns\n    </h1>\n    <p class=\"text-slate-500 text-sm\">This page contains an assortment of routing patterns to help you get started.</p>\n  </div>\n  <div class=\"bg-slate-700/20 text-slate-600 py-2 border-2 border-slate-700 rounded-lg\">\n    <DropinTable\n      {columns}\n      data={$components}\n      bind:selections />\n  </div>\n  <div class=\"flex flex-1 flex-col gap-2\">\n    <Router\n      basePath=\"/patterns\"\n      renavigation={true}\n      myExtraRouterProp={{\n        calledFrom: \"patterns <Router />\"\n      }}\n      {routes} />\n  </div>\n</div>\n\n<style>\n  :global(td) {\n    padding: 8px !important;\n  }\n</style>\n"
  },
  {
    "path": "demo/src/routes/protected/account-state.svelte.ts",
    "content": "let token = $state(localStorage.getItem(\"token\"));\n\nexport const client = {\n  get loggedIn() {\n    return token !== null;\n  },\n  set loggedIn(value: boolean) {\n    token = value ? \"true\" : null;\n    console.log(\"token\", token);\n    if (value) {\n      localStorage.setItem(\"token\", \"true\");\n    } else {\n      localStorage.removeItem(\"token\");\n    }\n  }\n};\n"
  },
  {
    "path": "demo/src/routes/protected/denied.svelte",
    "content": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { LockIcon, ShieldAlert } from \"lucide-svelte\";\n</script>\n\n<div class=\"flex items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8\">\n  <div class=\"w-full max-w-md space-y-8 rounded-lg bg-white p-8 shadow-lg\">\n    <div class=\"text-center\">\n      <div class=\"mb-4 flex justify-center\">\n        <ShieldAlert class=\"h-16 w-16 text-red-600\" />\n      </div>\n      <h1 class=\"mb-2 text-3xl font-bold text-gray-900\">Access Denied</h1>\n      <div class=\"mb-4 flex items-center justify-center gap-2 text-red-600\">\n        <LockIcon class=\"h-5 w-5\" />\n        <span class=\"font-semibold\">Secure Area</span>\n      </div>\n      <p class=\"mb-4 text-gray-600\">For your security, access to this banking area has been denied. This may be due to:</p>\n      <ul class=\"mb-6 ml-4 list-disc text-left text-gray-600\">\n        <li>Insufficient permissions</li>\n        <li>Invalid authentication</li>\n        <li>Session timeout</li>\n      </ul>\n      <p class=\"text-sm text-gray-500\">Please contact our support team or return to the homepage if you believe this is an error.</p>\n      <div class=\"mt-6\">\n        <a\n          use:route\n          href=\"/protected\"\n          class=\"inline-flex items-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\">\n          Return to Homepage\n        </a>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/protected/login.svelte",
    "content": "<script lang=\"ts\">\n  import { goto } from \"@mateothegreat/svelte5-router\";\n  import { Shield } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n  import { client } from \"./account-state.svelte\";\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mx-auto max-w-md rounded-xl bg-white p-8 shadow-lg\">\n    <div class=\"mb-6 text-center\">\n      <div class=\"mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100\">\n        <Shield class=\"h-8 w-8 text-blue-600\" />\n      </div>\n      <h2 class=\"text-2xl font-bold text-gray-800\">Secure Login</h2>\n      <p class=\"mt-2 text-gray-600\">Please log in to access your account</p>\n    </div>\n\n    <button\n      class=\"w-full rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700\"\n      on:click={() => {\n        client.loggedIn = true;\n        goto(\"/protected/manage-account\");\n      }}>\n      Login to Your Account\n    </button>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/protected/main.svelte",
    "content": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { goto, registry, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Loader2, Shield, Wallet } from \"lucide-svelte\";\n  import Denied from \"./denied.svelte\";\n  import Login from \"./login.svelte\";\n  import { authGuardFast } from \"./manage-account/auth-guard-fast\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(true);\n\n  $effect(() => {\n    end =\n      router.current?.result.path.condition === \"default-match\" ||\n      location.pathname === \"/protected/login\" ||\n      location.pathname === \"/protected/denied\";\n  });\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/protected/main.svelte\",\n    content: \"Demo to show how you can use hooks to control the navigation of your app to control authentication, etc.\"\n  }}\n  links={[\n    {\n      href: \"/protected\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/login\",\n      label: \"/protected/login\"\n    },\n    {\n      href: \"/protected/manage-account\",\n      label: \"/protected/manage-account\"\n    },\n    {\n      href: \"/protected/denied\",\n      label: \"/protected/denied\"\n    }\n  ]}>\n  <Router\n    id=\"protected-router\"\n    basePath=\"/protected\"\n    bind:instance={router}\n    routes={[\n      {\n        component: snippet\n      },\n      {\n        path: \"login\",\n        component: Login\n      },\n      {\n        path: \"manage-account\",\n        component: async () => import(\"./manage-account/manage-account.svelte\"),\n        hooks: {\n          pre: authGuardFast\n        },\n      },\n      {\n        path: \"denied\",\n        component: Denied\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n{#if registry.get(\"manage-account-router\")?.navigating}\n  <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n    <div class=\"flex flex-col items-center gap-4 rounded-md border-2 border-green-400 bg-black/70 px-20 py-6\">\n      <Loader2 class=\"h-12 w-12 text-green-500  animate-spin\" />\n      <div class=\"text-slate-300 font-bold\">Doing some work...</div>\n      <div class=\"text-slate-400 w-96 text-center\">\n        We've added some pre and post hooks to the manage-account router to simulate doing some work.\n      </div>\n    </div>\n  </div>\n{/if}\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/auth-guard-fast.ts",
    "content": "import { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardFast = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/auth-guard-slow.ts",
    "content": "import { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardSlow = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  await new Promise((resolve) => setTimeout(resolve, 2000));\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/balance.svelte",
    "content": "<script lang=\"ts\">\n  import { ArrowDownRight, ArrowUpRight, DollarSign } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake account data\n  const accountBalance = (Math.random() * 100000).toFixed(2);\n  const transactions = [\n    { id: 1, type: \"credit\", amount: 2500.0, description: \"Salary Deposit\", date: \"2024-03-15\" },\n    { id: 2, type: \"debit\", amount: 85.5, description: \"Grocery Store\", date: \"2024-03-14\" },\n    { id: 3, type: \"debit\", amount: 125.0, description: \"Electric Bill\", date: \"2024-03-13\" },\n    { id: 4, type: \"credit\", amount: 500.0, description: \"Freelance Payment\", date: \"2024-03-12\" },\n    { id: 5, type: \"debit\", amount: 45.99, description: \"Online Shopping\", date: \"2024-03-11\" }\n  ];\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-8 rounded-xl bg-blue-600 p-8 text-white shadow-lg\">\n    <h2 class=\"mb-2 text-xl\">Current Balance</h2>\n    <div class=\"flex items-center gap-2\">\n      <DollarSign size={32} />\n      <span class=\"text-4xl font-bold\">{accountBalance}</span>\n    </div>\n  </div>\n\n  <div class=\"rounded-xl bg-white p-6 shadow-lg\">\n    <h3 class=\"mb-4 text-xl font-semibold text-gray-800\">Recent Transactions</h3>\n    <div class=\"space-y-4\">\n      {#each transactions as transaction}\n        <div\n          class=\"flex items-center justify-between rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-50\">\n          <div class=\"flex items-center gap-3\">\n            {#if transaction.type === \"credit\"}\n              <div class=\"rounded-full bg-green-100 p-2\">\n                <ArrowUpRight class=\"h-5 w-5 text-green-600\" />\n              </div>\n            {:else}\n              <div class=\"rounded-full bg-red-100 p-2\">\n                <ArrowDownRight class=\"h-5 w-5 text-red-600\" />\n              </div>\n            {/if}\n            <div>\n              <p class=\"font-medium text-gray-800\">{transaction.description}</p>\n              <p class=\"text-sm text-gray-500\">{transaction.date}</p>\n            </div>\n          </div>\n          <span class={transaction.type === \"credit\" ? \"text-green-600\" : \"text-red-600\"}>\n            {transaction.type === \"credit\" ? \"+\" : \"-\"}${transaction.amount.toLocaleString(\"en-US\", {\n              minimumFractionDigits: 2\n            })}\n          </span>\n        </div>\n      {/each}\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/home.svelte",
    "content": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { Activity, PiggyBank, Send, Wallet } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake user data\n  const userData = {\n    name: \"Svelte Boss\",\n    lastLogin: new Date().toLocaleString(),\n    quickStats: [\n      { title: \"Total Balance\", amount: \"$45,250.80\", icon: Wallet },\n      { title: \"Monthly Savings\", amount: \"$2,450.00\", icon: PiggyBank },\n      { title: \"Recent Activity\", count: \"12 transactions\", icon: Activity }\n    ]\n  };\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-6\">\n    <h1 class=\"text-2xl text-gray-400\">\n      Welcome back,\n      <span class=\"text-indigo-400 font-bold\">{userData.name}</span>\n    </h1>\n    <p class=\"text-gray-400\">Last login: {userData.lastLogin}</p>\n  </div>\n\n  <div class=\"grid gap-6 md:grid-cols-3\">\n    {#each userData.quickStats as stat}\n      <div class=\"rounded-xl bg-slate-700/30 p-6 shadow-lg transition-transform hover:scale-[1.02]\">\n        <div class=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-100\">\n          <svelte:component\n            this={stat.icon}\n            class=\"h-6 w-6 text-blue-600\" />\n        </div>\n        <h3 class=\"text-lg text-gray-400\">{stat.title}</h3>\n        <p class=\"mt-1 text-xl font-bold text-green-400\">{stat.amount || stat.count}</p>\n      </div>\n    {/each}\n  </div>\n\n  <div class=\"mt-8\">\n    <a\n      use:route\n      href=\"/protected/manage-account/balance\"\n      class=\"flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-6 py-4 font-semibold text-white transition-colors hover:bg-blue-700\">\n      <Send class=\"h-5 w-5\" />\n      View Balance\n    </a>\n  </div>\n</div>\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/manage-account.svelte",
    "content": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import { goto, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Shield, Wallet } from \"lucide-svelte\";\n  import { client } from \"../account-state.svelte\";\n  import { authGuardSlow } from \"./auth-guard-slow\";\n  import Balance from \"./balance.svelte\";\n  import Home from \"./home.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected/manage-account\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/protected/manage-account/manage-account.svelte\",\n    content: \"This router demonstrates how you can restrict access to routes based on the user's authentication state.\"\n  }}\n  links={[\n    {\n      href: \"/protected/manage-account\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/manage-account/balance\",\n      label: \"/protected/manage-account/balance\"\n    },\n    {\n      href: \"/protected/manage-account/logout\",\n      label: \"/protected/manage-account/logout\"\n    }\n  ]}>\n  <Router\n    id=\"manage-account-router\"\n    basePath=\"/protected/manage-account\"\n    bind:instance={router}\n    routes={[\n      /**\n       * This route is optional, it's for demonstration purposes.\n       * It's used to redirect to the balance page when the user\n       * navigates to the manage-account route (the default path):\n       */\n      {\n        component: Home\n      },\n      {\n        path: \"/balance\",\n        component: Balance,\n        hooks: {\n          pre: authGuardSlow\n        }\n      },\n      {\n        path: \"/logout\",\n        hooks: {\n          pre: () => {\n            client.loggedIn = false;\n            setTimeout(() => {\n              goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n            }, 100);\n          }\n        }\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/worker-client.svelte.ts",
    "content": "let working = $state(false);\n\nexport const workerClient = {\n  get working() {\n    return working;\n  },\n  set working(value: boolean) {\n    working = value;\n  }\n};\n"
  },
  {
    "path": "demo/src/routes/transitions/fade.svelte",
    "content": "<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { fade } from \"svelte/transition\";\n</script>\n\n<div in:fade={{ duration: 500 }}>\n  <Container\n    title=\"Fade\"\n    file=\"src/routes/transitions/fade.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-sky-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n"
  },
  {
    "path": "demo/src/routes/transitions/slide.svelte",
    "content": "<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { slide } from \"svelte/transition\";\n</script>\n\n<div in:slide={{ duration: 500 }}>\n  <Container\n    title=\"Slide\"\n    file=\"src/routes/transitions/slide.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-indigo-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n"
  },
  {
    "path": "demo/src/routes/transitions/transitions.svelte",
    "content": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Fade from \"./fade.svelte\";\n  import Slide from \"./slide.svelte\";\n\n  let { route } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"fade\",\n      component: Fade,\n      props: {\n        file: \"src/routes/transitions/fade.svelte\"\n      }\n    },\n    {\n      path: \"slide\",\n      component: Slide,\n      props: {\n        file: \"src/routes/transitions/slide.svelte\"\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/transitions/transitions.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/transitions\"\n  {route}\n  end={true}\n  title={{\n    content:\n      \"Demo to show how to use transitions with the router (spoiler: they're applied at the content level rather than within the router itself).\"\n  }}\n  links={[\n    {\n      href: \"/transitions\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/transitions/fade\",\n      label: \"/transitions/fade\"\n    },\n    {\n      href: \"/transitions/slide\",\n      label: \"/transitions/slide\"\n    }\n  ]}>\n  <Router\n    id=\"transitions-router\"\n    basePath=\"/transitions\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n"
  },
  {
    "path": "demo/src/vite-env.d.ts",
    "content": "/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n\nimport type { CompilerConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  SPA_ROUTER: CompilerConfig;\n}\n"
  },
  {
    "path": "demo/svelte.config.js",
    "content": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess(),\n  vitePlugin: {\n    inspector: {\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"top-right\"\n    }\n  }\n};\n"
  },
  {
    "path": "demo/tailwind.config.ts",
    "content": "\n/** @type {import('tailwindcss').Config} */\nconst config = {\n  darkMode: [\"class\"],\n  content: [\"./src/**/*.{html,js,svelte,ts}\", \"../../src/**/*.{html,js,svelte,ts}\"],\n  safelist: [\"dark\"],\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\"\n      }\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border) / <alpha-value>)\",\n        input: \"hsl(var(--input) / <alpha-value>)\",\n        ring: \"hsl(var(--ring) / <alpha-value>)\",\n        background: \"hsl(var(--background) / <alpha-value>)\",\n        foreground: \"hsl(var(--foreground) / <alpha-value>)\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary) / <alpha-value>)\",\n          foreground: \"hsl(var(--primary-foreground) / <alpha-value>)\"\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary) / <alpha-value>)\",\n          foreground: \"hsl(var(--secondary-foreground) / <alpha-value>)\"\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive) / <alpha-value>)\",\n          foreground: \"hsl(var(--destructive-foreground) / <alpha-value>)\"\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted) / <alpha-value>)\",\n          foreground: \"hsl(var(--muted-foreground) / <alpha-value>)\"\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent) / <alpha-value>)\",\n          foreground: \"hsl(var(--accent-foreground) / <alpha-value>)\"\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover) / <alpha-value>)\",\n          foreground: \"hsl(var(--popover-foreground) / <alpha-value>)\"\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card) / <alpha-value>)\",\n          foreground: \"hsl(var(--card-foreground) / <alpha-value>)\"\n        }\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\"\n      },\n      fontFamily: {\n        sans: [\"Inter\"]\n      }\n    }\n  }\n};\nexport default config;\n"
  },
  {
    "path": "demo/tsconfig.deployed.json",
    "content": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"sourceMap\": true,\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"isolatedModules\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"moduleResolution\": \"bundler\"\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.svelte\"\n  ]\n}"
  },
  {
    "path": "demo/tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"sourceMap\": true,\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"isolatedModules\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"moduleResolution\": \"bundler\",\n    \"paths\": {\n      \"@mateothegreat/svelte5-router\": [\n        \"../src/lib/index\"\n      ],\n      \"@mateothegreat/svelte5-router/*\": [\n        \"../src/lib/*\"\n      ],\n      \"$lib\": [\n        \"./src/lib\"\n      ],\n      \"$lib/*\": [\n        \"./src/lib/*\"\n      ],\n      \"$routes\": [\n        \"./src/routes\"\n      ],\n      \"$routes/*\": [\n        \"./src/routes/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"./src/**/*\",\n    \"./../src/**/*\"\n  ]\n}"
  },
  {
    "path": "demo/vercel.json",
    "content": "{\n  \"routes\": [{ \"src\": \"/[^.]+\", \"dest\": \"/\", \"status\": 200 }],\n  \"github\": {\n    \"enabled\": false\n  }\n}\n"
  },
  {
    "path": "demo/vite.config.ts",
    "content": "import { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-inspector\";\n\nimport tailwindcss from \"@tailwindcss/vite\";\n\nimport path from \"path\";\n\nimport { defineConfig } from \"vite\";\n\nimport { vitePluginVersionMark } from \"vite-plugin-version-mark\";\n\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte(),\n    svelteInspector({\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"bottom-left\"\n    }),\n    tailwindcss(),\n    vitePluginVersionMark({\n      // name: 'test-app',\n      // version: '0.0.1',\n      // command: 'git describe --tags',\n      name: \"svelte5-router\",\n      ifGitSHA: true,\n      ifShortSHA: true,\n      ifMeta: true,\n      ifLog: true,\n      ifGlobal: true\n    })\n  ],\n  build: {\n    sourcemap: true\n  },\n  define: {\n    /** @type {import('@mateothegreat/svelte5-router').runtime.Config} */\n    /**\n     * The (optional) router package configuration for the compiler.\n     */\n    \"import.meta.env.SPA_ROUTER\": {\n      /**\n       * If enabled, tracing will be enabled providing rich tracing capabilities.\n       */\n      tracing: {\n        level: 3,\n        enabled: true,\n        console: true\n      },\n      /**\n       * The logging configuration for the router.\n       */\n      logging: {\n        /**\n         * The logging level that is applied.\n         */\n        level: 4,\n        /**\n         * Whether to log the trace to the browser console (optional).\n         */\n        console: false,\n        /**\n         * This method is called when a new trace is created (optional).\n         *\n         * You could use this to send the trace to a remote server, or store it\n         * in a local database.\n         *\n         * This example uses a promise in the event that you are needing to\n         * use async functionality.\n         */\n        sink: async (trace: any) => {\n          await new Promise((resolve) => {\n            console.log(trace);\n            resolve(void 0);\n          });\n        }\n      }\n    }\n  },\n  resolve: {\n    /**\n     * This is only needed for the demo environment.\n     *\n     * It is not needed for including the router package in your project.\n     */\n    alias: {\n      $lib: path.resolve(__dirname, \"./src/lib\"),\n      $routes: path.resolve(__dirname, \"./src/routes\"),\n      \"@mateothegreat/svelte5-router\": path.resolve(__dirname, \"../src/lib\")\n    }\n  }\n});\n"
  },
  {
    "path": "docs/CNAME",
    "content": "docs.router.svelte.spa"
  },
  {
    "path": "docs/actions.md",
    "content": "# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These actions are designed to be used with anchor (`<a>`) elements to handle navigation and manage active states.\n\n## Available Actions\n\n| Action | Description |\n|--------|-------------|\n| [`route`](#route) | Manages both navigation and active states of links. |\n| [`active`](#active) | Handles active state management for styling links. |  \n\n## Examples\n\n### Basic Navigation Link\n\n```svelte\n<a href=\"/home\" use:route>Home</a>\n```\n\n### Active State with Multiple Classes\n\n```svelte\n<a \n  href=\"/profile\" \n  use:route={{\n    default: { class: ['text-gray-600', 'hover:text-gray-900'] },\n    active: { class: ['text-blue-600', 'font-bold'] }\n  }}\n>\n  Profile\n</a>\n```\n\n### Exact Path Matching\n\n```svelte\n<a \n  href=\"/settings\" \n  use:route={{\n    active: {\n      class: 'active-link',\n      absolute: true // Only active when path exactly matches /settings\n    }\n  }}\n>\n  Settings\n</a>\n```\n\n### Query String Sensitive Navigation\n\n```svelte\n<a \n  href=\"/search?type=users\" \n  use:route={{\n    active: {\n      class: 'active-search',\n      querystring: true // Only active when querystring matches exactly\n    }\n  }}\n>\n  User Search\n</a>\n```\n\n### Notes\n\n- The `route` action automatically prevents default link behavior and handles navigation through the History API.\n- When using `active`, you'll need to handle navigation separately if needed.\n- Classes are applied dynamically based on the current route state.\n- The `absolute` option is useful for preventing parent routes from being marked as active when child routes are active.\n- The `querystring` option allows for precise matching including query parameters.\n\n---\n\n## `route`\n\nThe `route` action is the primary action for handling routing in your application. It manages both navigation and active states of links.\n\n```svelte\n<a href=\"/dashboard\" use:route>Dashboard</a>\n```\n\nThe `route` action accepts an options object with the following configuration:\n\n```typescript\n{\n  default?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  loading?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  disabled?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\n- `default`: Options applied when the route is inactive.\n- `active`: Options applied when the route is active.\n- `loading`: Options applied when the route is loading.\n- `disabled`: Options applied when the route is disabled.\n\nEach state accepts the following properties:\n\n- `absolute`: When `true`, effects only apply on exact path matches.\n- `querystring`: When `true`, effects only apply when querystring exactly matches.\n- `class`: CSS class(es) to apply when the state is active.\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:route={{\n    default: { class: 'text-gray-600' },\n    active: { \n      class: 'text-blue-600 font-bold',\n      absolute: true \n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n## `active`\n\nThe `active` action is a simplified version of `route` that only handles active state management without handling navigation events. This is useful when you want to style links based on the current route but handle navigation differently.\n\n```svelte\n<a href=\"/dashboard\" use:active>Dashboard</a>\n```\n\nThe `active` action accepts a subset of the route options, focusing only on the active state configuration:\n\n```typescript\n{\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:active={{\n    active: {\n      class: ['text-blue-600', 'font-bold'],\n      absolute: true,\n      querystring: true\n    }\n  }}\n>\n  Dashboard\n</a>\n```\n"
  },
  {
    "path": "docs/assets/coverage.json",
    "content": "{\"percent\":51,\"expected\":218,\"actual\":112,\"notDocumented\":[\"MarshallableType\",\"URL\",\"URL.protocol\",\"URL.host\",\"URL.port\",\"URL.path\",\"URL.query\",\"URL.hash\",\"urls.path.path\",\"active.active.__type.destroy.destroy\",\"route.route.__type.destroy.destroy\",\"Evaluation.condition\",\"Evaluation.params\",\"EvaluationResult.path\",\"EvaluationResult.querystring\",\"EvaluationResult.original\",\"identify.identify\",\"Identities\",\"Identities.__type.string\",\"Identities.__type.number\",\"Identities.__type.boolean\",\"Identities.__type.null\",\"Identities.__type.undefined\",\"Identities.__type.regexp\",\"Identities.__type.function\",\"Identities.__type.object\",\"Identities.__type.array\",\"Identities.__type.promise\",\"Identities.__type.unknown\",\"Identity\",\"logging.LogLevel.FATAL\",\"logging.LogLevel.ERROR\",\"logging.LogLevel.INFO\",\"logging.LogLevel.DEBUG\",\"logging.LogLevel.TRACE\",\"logging.LogLevel.DISABLED\",\"logging.Group.name\",\"logging.Group.messages\",\"Marshalled\",\"Marshalled.identity\",\"Marshalled.value\",\"runtime.Config.tracing\",\"runtime.Config.tracing.__type.enabled\",\"runtime.Config.tracing.__type.level\",\"runtime.Config.tracing.__type.console\",\"runtime.Config.tracing.__type.sink\",\"runtime.Config.logging\",\"runtime.Config.logging.__type.level\",\"runtime.Config.logging.__type.console\",\"runtime.Config.logging.__type.sink\",\"Span.prefix\",\"Span.id\",\"Span.date\",\"Span.name\",\"Span.description\",\"Span.metadata\",\"Span.traces\",\"Span.trace.trace\",\"Span.get.get\",\"Trace.prefix\",\"Trace.id\",\"Trace.index\",\"Trace.date\",\"Trace.name\",\"Trace.description\",\"Trace.metadata\",\"Trace.span\",\"Hook\",\"Query.params\",\"Query.original\",\"Query.goto.goto\",\"Query.test.test\",\"QueryEvaluationResult.condition\",\"QueryEvaluationResult.matches\",\"Registry.get.get\",\"Route.hooks.__type.pre\",\"Route.hooks.__type.post\",\"RouteConfig\",\"RouteConfig.name\",\"RouteConfig.basePath\",\"RouteConfig.path\",\"RouteConfig.querystring\",\"RouteConfig.component\",\"RouteConfig.props\",\"RouteConfig.hooks\",\"RouteConfig.hooks.__type.pre\",\"RouteConfig.hooks.__type.post\",\"RouteConfig.children\",\"RouteConfig.status\",\"RouteConfig.toJSON.toJSON\",\"RouterInstanceConfig.hooks.__type.pre\",\"RouterInstanceConfig.hooks.__type.post\",\"RouterInstanceConfig.toJSON.toJSON\",\"RouterInstance.evaluateHooks.evaluateHooks\",\"Router\",\"Router\",\"StatusCode.OK\",\"StatusCode.PermanentRedirect\",\"StatusCode.TemporaryRedirect\",\"StatusCode.BadRequest\",\"StatusCode.Unauthorized\",\"StatusCode.Forbidden\",\"StatusCode.NotFound\",\"StatusCode.InternalServerError\",\"Statuses.__type.__type.__type.component\",\"Statuses.__type.__type.__type.props\"]}"
  },
  {
    "path": "docs/changelog.md",
    "content": "<div align=\"center\">\n<img src=\"tag.png\" width=\"200\" />\n<h1><strong>@mateothegreat/svelte5-router</strong></h1>\n<h3>📋 tl;dr changelog</h3>\n<p>\n<a href=\"https://github.com/mateothegreat/svelte5-router\">github</a> •\n<a href=\"https://www.npmjs.com/package/mateothegreat/svelte5-router\">npm</a> •\n<a href=\"https://svelte5-router.docs.matthewdavis.io\">docs</a>\n</p>\n</div>\n\n<div><em>Here are the last 100~ commits ✌️</em></div>\n\n## [2.16.19](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.19) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.19`\n- **Released**: August 29, 2025\n- **Total Changes**: 2 commits\n\n### 🚀 Features\n\n-   Replace() helper ✨; closes #86  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`41801c1`](https://github.com/mateothegreat/svelte5-router/commit/41801c15b65e2b2478a6037a13c2bf4e8729fa3a))\n\n-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`0fce87c`](https://github.com/mateothegreat/svelte5-router/commit/0fce87c345e53b4965a0199de33de23fbbf36f3f))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n## [2.16.18](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.18) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.18`\n- **Released**: August 29, 2025\n- **Total Changes**: 3 commits\n\n### 🚀 Features\n\n-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`f9258d2`](https://github.com/mateothegreat/svelte5-router/commit/f9258d2ca6ee55ebecdd3b8c11776e498142a7f7))\n\n-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`ea1a2b9`](https://github.com/mateothegreat/svelte5-router/commit/ea1a2b98bb95382cb3e50ebef4fae7c03f96ca03))\n\n### 📋 Other Changes\n\n-   :wrench: update svelte5-router to version 2.16.17  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`f96168f`](https://github.com/mateothegreat/svelte5-router/commit/f96168f68051208831809f64b02d9188e6132831))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n## [2.16.17](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.17) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.17`\n- **Released**: August 29, 2025\n- **Total Changes**: 1 commit\n\n### 🔧 Maintenance\n\n-   Fix package.json versioning  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`da3bf33`](https://github.com/mateothegreat/svelte5-router/commit/da3bf3346faf4e4dd517777dcf89dba517319577))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n## [2.16.16](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.16) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.16`\n- **Released**: August 29, 2025\n- **Total Changes**: 1 commit\n\n### 🔧 Maintenance\n\n-   Fix package.json versioning  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`38f81f2`](https://github.com/mateothegreat/svelte5-router/commit/38f81f2bdeb329a84198219c4d363420bca10e52))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n## [2.16.15](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.15) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.15`\n- **Released**: August 29, 2025\n- **Total Changes**: 1 commit\n\n### 🔧 Maintenance\n\n-   Cleanup cicd  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`d2bf150`](https://github.com/mateothegreat/svelte5-router/commit/d2bf150aa6d8b7e799ac7258f42e34ed805db38d))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n## [2.16.14](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.14) - 2025-08-29\n\n### 📊 Release Summary\n- **Version**: `2.16.14`\n- **Released**: August 29, 2025\n- **Total Changes**: 7 commits\n\n### 🐛 Bug Fixes\n\n-   Fix esm exports  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`13e2c5e`](https://github.com/mateothegreat/svelte5-router/commit/13e2c5ebb862d6e7a0b93ff4b3ba40211209b810))\n\n### 📦 Dependencies\n\n- **deps**:   Bump the npm_and_yarn group across 2 directories with 4 updates  by [@dependabot[bot]](https://github.com/dependabot[bot])  ([`f7c30e8`](https://github.com/mateothegreat/svelte5-router/commit/f7c30e89b0c38f7e20b767042eace272a94eb637))\n\n- **deps-dev**:   Bump esbuild  by [@dependabot[bot]](https://github.com/dependabot[bot])  ([`d879c2c`](https://github.com/mateothegreat/svelte5-router/commit/d879c2c4a8f4cf96ce64bccc5924f7c62a4ca254))\n\n### 📋 Other Changes\n\n-   Merge pull request #107 from mateothegreat/dependabot/npm_and_yarn/test/app/npm_and_yarn-037d2236c4\n\nchore(deps-dev): bump esbuild from 0.25.8 to 0.25.9 in /test/app in the npm_and_yarn group across 1 directory  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`9e7aeda`](https://github.com/mateothegreat/svelte5-router/commit/9e7aeda5059d707eaf62febd9c3fdcb459dc0d2c))\n\n-   Merge branch 'main' into dependabot/npm_and_yarn/test/app/npm_and_yarn-037d2236c4  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`36b3fe0`](https://github.com/mateothegreat/svelte5-router/commit/36b3fe0901807f95da063327dd4b811ec0e9bee1))\n\n-   Merge pull request #111 from mateothegreat/dependabot/npm_and_yarn/npm_and_yarn-c8c34112a0\n\nchore(deps): bump the npm_and_yarn group across 2 directories with 4 updates  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`ad07412`](https://github.com/mateothegreat/svelte5-router/commit/ad07412c37173ccff7fba939ec556a93f9f5697d))\n\n-   Create dependabot.yml  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`9203170`](https://github.com/mateothegreat/svelte5-router/commit/92031701dc52f71ecae7e987662a67c20909f7f0))\n\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n- [@mateothegreat](https://github.com/mateothegreat)\n\n- [@dependabot[bot]](https://github.com/dependabot[bot])\n"
  },
  {
    "path": "docs/cliff.toml",
    "content": "# https://git-cliff.org/docs/integration/github/\n\n[changelog]\ntrim = true\nrender_always = true\nheader = \"\"\"\n{%- macro remote_url() -%}\n  https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\n{%- endmacro -%}\n\n{%- macro npm_url() -%}\n  https://www.npmjs.com/package/{{ remote.github.owner }}/{{ remote.github.repo }}\n{%- endmacro -%}\n\n{%- macro documentation_url() -%}\n  https://{{ remote.github.repo }}.docs.matthewdavis.io\n{%- endmacro -%}\n\n<div align=\"center\">\n  <img src=\"tag.png\" width=\"200\" />\n  <h1><strong>@mateothegreat/{{ remote.github.repo }}</strong></h1>\n  <h3>📋 tl;dr changelog</h3>\n  <p>\n    <a href=\"{{ self::remote_url() }}\">github</a> •\n    <a href=\"{{ self::npm_url() }}\">npm</a> •\n    <a href=\"{{ self::documentation_url() }}\">docs</a>\n  </p>\n</div>\n\n<div><em>Here are the last 100~ commits ✌️</em></div>\n\n\"\"\"\n\nbody = \"\"\"\n\n{%- macro remote_url() -%}\n  https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\n{%- endmacro -%}\n\n{%- macro npm_url() -%}\n  https://www.npmjs.com/package/{{ remote.github.owner }}/{{ remote.github.repo }}\n{%- endmacro -%}\n\n{%- macro documentation_url() -%}\n  https://docs.router.svelte.spa\n{%- endmacro -%}\n\n{% if version %}\\\n    ## [{{ version | trim_start_matches(pat=\"v\") }}]({{ self::remote_url() }}/releases/tag/{{ version }}) - {{ timestamp | date(format=\"%Y-%m-%d\") }}\n{% else %}\\\n    ## [Unreleased]({{ self::remote_url() }}/compare/HEAD...main)\n{% endif %}\\\n\n{% if version %}\n### 📊 Release Summary\n- **Version**: `{{ version | trim_start_matches(pat=\"v\") }}`\n- **Released**: {{ timestamp | date(format=\"%B %d, %Y\") }}\n- **Total Changes**: {{ commits | length }} commit{% if commits | length != 1 %}s{% endif %}\n{% endif %}\n\n{% for group, commits in commits | group_by(attribute=\"group\") %}\n  ### {{ group | striptags | trim | upper_first }}\n\n  {% for commit in commits %}\n    - {% if commit.scope %}**{{ commit.scope }}**: {% endif %} \\\n      {% if commit.breaking %}⚠️ **BREAKING**: {% endif %} \\\n      {{ commit.message | upper_first | trim }} \\\n      {% if commit.author %} by [@{{ commit.author.name }}](https://github.com/{{ commit.author.name }}) {% endif %} \\\n      ([`{{ commit.id | truncate(length=7, end=\"\") }}`]({{ self::remote_url() }}/commit/{{ commit.id }}))\n  {% endfor %}\n{% endfor %}\n\n{% if github.contributors | length != 0 %}\n### 👥 Contributors\n\nThanks ❤️‍🔥 to the following people who contributed to this release:\n\n{% for contributor in github.contributors %}\n{% if contributor.username %}\n- [@{{ contributor.username }}](https://github.com/{{ contributor.username }})\n{%- endif %}\n{%- endfor %}\n{% endif %}\n\"\"\"\n\n# Footer with useful links and metadata\nfooter = \"\"\"\n---\n\n<div align=\"center\">\n  <sub>\n    generated by <a href=\"https://git-cliff.org\">git-cliff</a> 💜 • \n    <a href=\"{{ self::remote_url() }}/issues\">report Issues</a> • \n    <a href=\"{{ self::remote_url() }}/discussions\">join discussions</a>\n  </sub>\n</div>\n\"\"\"\n\n# Replace placeholders with actual URLs\npostprocessors = [\n    { pattern = '<REPO>', replace = \"https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\" },\n    # Clean up multiple blank lines\n    { pattern = '\\n\\n\\n+', replace = \"\\n\\n\" },\n]\n\n[git]\n\nconventional_commits = true\nfilter_unconventional = false\nrequire_conventional = false\nuse_branch_tags = false\nsplit_commits = false\nlimit_commits = 20\nsort_commits = \"newest\"\ntopo_order = false\ntopo_order_commits = true\nrecurse_submodules = true\n\n# Preprocess commits to add GitHub links\ncommit_preprocessors = [\n    # Link to GitHub issues\n    { pattern = '\\((\\w+\\s)?#([0-9]+)\\)', replace = \"([#${2}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/issues/${2}))\" },\n    # Link to PRs\n    { pattern = 'PR #([0-9]+)', replace = \"[PR #${1}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/${1})\" },\n]\n\n# Protect breaking changes from being filtered\nprotect_breaking_commits = true\n\n# Enhanced commit parsers with more categories\ncommit_parsers = [\n    # Core features\n    { message = \"^feat\", group = \"<!-- 00 -->🚀 Features\" },\n    { message = \"^feature\", group = \"<!-- 00 -->🚀 Features\" },\n    \n    # Bug fixes\n    { message = \"^fix\", group = \"<!-- 01 -->🐛 Bug Fixes\" },\n    { message = \"^bugfix\", group = \"<!-- 01 -->🐛 Bug Fixes\" },\n    \n    # Performance improvements\n    { message = \"^perf\", group = \"<!-- 02 -->⚡ Performance\" },\n    { message = \"^optimize\", group = \"<!-- 02 -->⚡ Performance\" },\n    \n    # Code refactoring\n    { message = \"^refactor\", group = \"<!-- 03 -->♻️ Refactoring\" },\n    { message = \"^refact\", group = \"<!-- 03 -->♻️ Refactoring\" },\n    \n    # Documentation\n    { message = \"^docs?\", group = \"<!-- 04 -->📚 Documentation\" },\n    { message = \"^readme\", group = \"<!-- 04 -->📚 Documentation\" },\n    \n    # Testing\n    { message = \"^test\", group = \"<!-- 05 -->🧪 Testing\" },\n    { message = \"^spec\", group = \"<!-- 05 -->🧪 Testing\" },\n    { message = \"^e2e\", group = \"<!-- 05 -->🧪 Testing\" },\n    \n    # Styling\n    { message = \"^style\", group = \"<!-- 06 -->🎨 Styling\" },\n    { message = \"^css\", group = \"<!-- 06 -->🎨 Styling\" },\n    { message = \"^ui\", group = \"<!-- 06 -->🎨 Styling\" },\n    \n    # Dependencies\n    { message = \"^deps\", group = \"<!-- 07 -->📦 Dependencies\" },\n    { message = \"^dep\", group = \"<!-- 07 -->📦 Dependencies\" },\n    { message = \"update.*dependencies\", group = \"<!-- 07 -->📦 Dependencies\" },\n    \n    # Build & CI\n    { message = \"^build\", group = \"<!-- 08 -->🏗️ Build System\" },\n    { message = \"^ci\", group = \"<!-- 08 -->🏗️ Build System\" },\n    { message = \"^pipeline\", group = \"<!-- 08 -->🏗️ Build System\" },\n    \n    # Security\n    { body = \".*security\", group = \"<!-- 09 -->🔒 Security\" },\n    { message = \"^security\", group = \"<!-- 09 -->🔒 Security\" },\n    { message = \"^vuln\", group = \"<!-- 09 -->🔒 Security\" },\n    \n    # Breaking changes\n    { body = \".*BREAKING CHANGE.*\", group = \"<!-- 10 -->💥 Breaking Changes\" },\n    { message = \"^breaking\", group = \"<!-- 10 -->💥 Breaking Changes\" },\n    \n    # Reverts\n    { message = \"^revert\", group = \"<!-- 11 -->⏪ Reverts\" },\n    \n    # Types\n    { message = \"^types?\", group = \"<!-- 12 -->🏷️ Types\" },\n    { message = \"^typing\", group = \"<!-- 12 -->🏷️ Types\" },\n    \n    # Chores (but only important ones)\n    { message = \"^chore\\\\(release\\\\)\", skip = true },\n    { message = \"^chore\\\\(deps\\\\)\", skip = true },\n    { message = \"^chore\\\\(deps-dev\\\\)\", skip = true },\n    { message = \"^chore\", group = \"<!-- 13 -->🔧 Maintenance\" },\n    \n    # Examples\n    { message = \"^example\", group = \"<!-- 14 -->📝 Examples\" },\n    { message = \"^demo\", group = \"<!-- 14 -->📝 Examples\" },\n    \n    # Everything else\n    { message = \".*\", group = \"<!-- 15 -->📋 Other Changes\" },\n]\n\n# Enhanced link parsers\nlink_parsers = [\n    # Parse GitHub usernames\n    { pattern = \"@([a-zA-Z0-9_-]+)\", href = \"https://github.com/${1}\" },\n    # Parse commit SHAs\n    { pattern = \"([a-f0-9]{7})[a-f0-9]{33}\", href = \"https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/commit/${0}\" },\n]\n"
  },
  {
    "path": "docs/debugging.md",
    "content": "# Debugging\n\nThere are a few way to debug things.\n\n## Debug Logger\n\nIn your `vite.config.ts` file, you can add the following:\n\n```ts\nexport default defineConfig({\n  plugins: [svelte()],\n  build: {\n    sourcemap: true // If you want to use a debugger, add this!\n  },\n  define: {\n    // Tell the router to log when we're in debug mode.\n    // Otherwise, this statement is removed by the compiler (known as tree-shaking)\n    // and all subsequent log statements are removed at build time:\n    'import.meta.env.SPA_ROUTER': {\n      logLevel: \"debug\"\n    },\n  }\n});\n```\n\nThis allows us to log when we're in debug mode otherwise\nstatements like this are removed by the compiler (known astree-shaking):\n\n```ts\nif (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n  log.debug(this.config.id, \"unregistered router instance\", {\n    id: this.config.id,\n    routes: this.routes.size\n  });\n}\n```\n\nPutting it all together:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n\n  let instance: RouterInstance;\n\n  if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n    log.debug(instance.id, \"dumping routes\", {\n      config: instance.config,\n      routes: instance.routes,\n      current: instance.current,\n      navigating: instance.navigating\n    });\n  }\n</script>\n\n<Router bind:instance {routes}>\n```\n\nExample output:\n\n![debug](./assets/debugging-logger.png)\n"
  },
  {
    "path": "docs/diagrams/component-hierarchy.mmd",
    "content": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\ngraph TD\n    A[Router Component] -->|Creates| B[RouterInstance]\n    B -->|Registers with| C[Registry]\n    B -->|Manages| D[Route Collection]\n    D -->|Contains| E[Route Configurations]\n    E -->|Has| F[Path Patterns]\n    E -->|Has| G[Query Patterns]\n    E -->|Has| H[Hooks]\n    E -->|Renders| I[Route Components]\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style C fill:#f472b6,stroke:#db2777\n    style D fill:#4ade80,stroke:#16a34a\n    style E fill:#f472b6,stroke:#db2777 \n"
  },
  {
    "path": "docs/diagrams/route-evaluations.mmd",
    "content": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\nflowchart TD\n    A[Route Evaluation Start] --> B{Check Path Type}\n    B -->|String| C[Direct Match]\n    B -->|RegExp| D[Pattern Match]\n    B -->|Function| E[Custom Match]\n    \n    C --> F{Path Matches?}\n    D --> F\n    E --> F\n    \n    F -->|Yes| G[Check Query Parameters]\n    F -->|No| H[Try Next Route]\n    \n    G --> I{Query Matches?}\n    I -->|Yes| J[Create Route Result]\n    I -->|No| H\n    \n    H --> K{More Routes?}\n    K -->|Yes| B\n    K -->|No| L[Use Default Route]\n    \n    J --> M[Execute Pre Hooks]\n    L --> M\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style F fill:#f472b6,stroke:#db2777\n    style I fill:#f472b6,stroke:#db2777\n    style J fill:#4ade80,stroke:#16a34a\n    style M fill:#3b82f6,stroke:#1d4ed8 \n"
  },
  {
    "path": "docs/diagrams/router-architecture.mmd",
    "content": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\ngraph TB\n    A[Browser URL Change] -->|Triggers| B[RouterInstance]\n    B -->|Registers| C[Registry]\n    B -->|Evaluates| D[Route Matching]\n    D -->|Checks| E[Path Matching]\n    D -->|Checks| F[Query Matching]\n    E --> G[Route Resolution]\n    F --> G\n    G -->|Pre Hooks| H[Route Guards]\n    H -->|Success| I[Component Rendering]\n    H -->|Failure| J[Redirect/Deny]\n    I -->|Post Hooks| K[Final Render]\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style C fill:#f472b6,stroke:#db2777\n    style D fill:#4ade80,stroke:#16a34a\n    style G fill:#f472b6,stroke:#db2777\n    style H fill:#3b82f6,stroke:#1d4ed8\n    style I fill:#4ade80,stroke:#16a34a \n"
  },
  {
    "path": "docs/diagrams/routing-lifecycle.mmd",
    "content": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\nsequenceDiagram\n    participant U as User/Browser\n    participant R as Router\n    participant RI as RouterInstance\n    participant RG as Registry\n    participant H as Hooks\n    participant C as Component\n\n    U->>R: URL Change\n    R->>RG: Check Registration\n    RG-->>R: Return Instance\n    R->>RI: Handle State Change\n    RI->>RI: Evaluate Route\n    RI->>H: Execute Pre-Hooks\n    alt Hook Success\n        H-->>RI: Continue\n        RI->>C: Render Component\n        RI->>H: Execute Post-Hooks\n        H-->>RI: Complete\n    else Hook Failure\n        H-->>RI: Abort/Redirect\n        RI->>U: Navigate Away\n    end \n"
  },
  {
    "path": "docs/diagrams.md",
    "content": "# Router Architecture Diagrams\n\nThis document contains Mermaid diagrams that illustrate the architecture and flow of the Svelte 5 Router. These diagrams are designed to help you understand how the router works internally.\n\n## 1. Router Architecture\n\nShows the high-level architecture of the router, including how URL changes flow through the system to component rendering.\n\n<div align=\"center\">\n  <img src=\"./diagrams/router-architecture.png\" alt=\"Router Architecture\" />\n</div>\n\n## 2. Routing Lifecycle\n\nA sequence diagram showing the temporal flow of routing operations from URL change to final render.\n\n<div align=\"center\">\n  <img src=\"./diagrams/routing-lifecycle.png\" alt=\"Routing Lifecycle\" />\n</div>\n\n## 3. Route Evaluation\n\nA detailed flowchart showing how routes are evaluated, including path matching, query parameter checking, and hook execution.\n\n<div align=\"center\">\n  <img src=\"./diagrams/route-evaluations.png\" alt=\"Route Evaluations\" />\n</div>\n\n## 4. Component Hierarchy\n\nShows the relationship between different components in the router system.\n\n<div align=\"center\">\n  <img src=\"./diagrams/component-hierarchy.png\" alt=\"Component Hierarchy\" />\n</div>\n"
  },
  {
    "path": "docs/getting-started.md",
    "content": "# Getting Started\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Usage\n\nIn your `app.svelte` file, you can use the `Router` component to render your routes:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n\n  const routes: RouteConfig[] = [\n    {\n      component: Home\n    }\n    {\n      path: \"products\",\n      component: Products\n    },\n    {\n      path: \"settings\",\n      component: Settings\n    }\n  ];\n</script>\n\n<Router {routes} />\n```\n\nWhen you navigate to the root route, the `Home` component will be rendered.\n\nWhen you navigate to the `/products` route, the `Products` component will be rendered.\n\nWhen you navigate to the `/settings` route, the `Settings` component will be rendered.\n"
  },
  {
    "path": "docs/helpers.md",
    "content": "# Helpers\n\nThere are a few helpers that are available to you when using the router.\n\n## `goto(path: string, queryParams?: Record<string, string>)`\n\nNavigates to the given path by calling `goto(\"/path\")`.\n\nExample:\n\n```ts\nimport { goto } from \"@mateothegreat/svelte5-router\";\n\ngoto(\"/foo\", { bar: \"baz\" });\n```\n\nThis will navigate to `/foo?bar=baz`.\n\n## `pop(delta?: number)`\n\nNavigates backwards in the browser history N pages.\n\nExample:\n\n```ts\nimport { pop } from \"@mateothegreat/svelte5-router\";\n\npop(); // Navigate back 1 page\npop(2); // Navigate back 2 pages\n```\n\n## `replace(path: string, queryParams?: Record<string, unknown>)`\n\nNavigates to the given path by calling `replace(\"/path\")` and replacing the current browser history entry instead of adding a new one.\n\nExample:\n\n```ts\nimport { replace } from \"@mateothegreat/svelte5-router\";\n\nreplace(\"/foo\"); // Navigates to \"/foo\"\nreplace(\"/foo\", { bar: \"baz\" }); // Navigates to \"/foo?bar=baz\"\n```\n\n## `query(key: string): string | null`\n\nReturns the value of the query parameter for the given key or null if the key does not exist.\n\n## The `QueryString` class\n\nA helper class for working with the query string.\n\n> Check it out at [src/lib/query.svelte.ts](../src/lib/query.svelte.ts) and start using it now!\n\nBasic usage:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\nquery.get(\"foo\", \"bar\"); // \"bar\"\nquery.set(\"baz\", \"qux\");\nquery.toString(); // \"foo=bar&baz=qux\"\n```\n\nUsing it with navigation:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\n// ...\nquery.set(\"foo\", \"baz\");\nquery.set(\"baz\", \"qux\");\n// ...\n\nquery.goto(\"/test\"); // Navigates to \"/test?foo=baz&baz=qux\"\n```\n\nYou can also pass a query object to the `goto` method:\n\n```ts\ngoto(\"/test\", { foo: \"baz\" }); // Navigates to \"/test?foo=baz\"\n```\n"
  },
  {
    "path": "docs/hooks.md",
    "content": "# Routing Hooks\n\n | Order | Event  | Scope       | Description                                |\n | ----- | ------ | ----------- | ------------------------------------------ |\n | 1.    | `pre`  | `<Router/>` | Always runs *before* a route is attempted. |\n | 2.    | `pre`  | `Route`     | Runs *before* the route is rendered.       |\n | 3.    | `post` | `Route`     | Runs *after* the route is rendered.        |\n | 4.    | `post` | `<Router/>` | Always runs *after* a route is rendered.   |\n\n```ts\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nexport const authGuard = async (route: RouteResult): Promise<boolean> => {\n  console.log(\"simulating some login/auth check...\");\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  if (!localStorage.getItem(\"token\")) {\n    console.warn(\"redirecting to denied\");\n    goto(\"/protected/denied\");\n    return false;\n  }\n  return true;\n}\n\nconst globalPostHook1 = (route: RouteResult): boolean => {\n  console.warn(\"globalPostHook1\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n\nconst globalPostHook2 = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalPostHook2\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\nYou can pass an array or single method for the `pre` and `post` hooks and you can\nalso mix and match asynchronous and synchronous hooks.\n\n```svelte\n<Router\n  {routes}\n  hooks={{\n    pre: authGuard,\n    post: [\n      globalPostHook1,\n      globalPostHook2\n    ]\n  }}\n/>\n```\n"
  },
  {
    "path": "docs/llms.txt",
    "content": "Directory Structure:\n\n└── ./\n    ├── .github\n    │   └── ISSUE_TEMPLATE\n    │       ├── bug_report.md\n    │       └── feature_request.md\n    ├── demo\n    │   ├── cypress\n    │   │   ├── e2e\n    │   │   │   └── route-activation.cy.ts\n    │   │   └── support\n    │   │       ├── commands.ts\n    │   │       └── e2e.ts\n    │   ├── src\n    │   │   ├── lib\n    │   │   │   ├── components\n    │   │   │   │   ├── routes\n    │   │   │   │   │   ├── route-link.svelte\n    │   │   │   │   │   ├── route-title.svelte\n    │   │   │   │   │   └── route-wrapper.svelte\n    │   │   │   │   ├── badge.svelte\n    │   │   │   │   ├── code.svelte\n    │   │   │   │   ├── container.svelte\n    │   │   │   │   ├── default.svelte\n    │   │   │   │   ├── file-link.svelte\n    │   │   │   │   └── inline-code.svelte\n    │   │   │   ├── default-route-config.ts\n    │   │   │   ├── router-history.ts\n    │   │   │   └── session.svelte.ts\n    │   │   ├── routes\n    │   │   │   ├── extras\n    │   │   │   │   ├── dump.svelte\n    │   │   │   │   ├── extras.svelte\n    │   │   │   │   └── passing-down-props.svelte\n    │   │   │   ├── hash\n    │   │   │   │   └── hash.svelte\n    │   │   │   ├── nested\n    │   │   │   │   ├── level-1\n    │   │   │   │   │   ├── level-2\n    │   │   │   │   │   │   ├── level-3\n    │   │   │   │   │   │   │   └── level-3.svelte\n    │   │   │   │   │   │   └── level-2.svelte\n    │   │   │   │   │   └── level-1.svelte\n    │   │   │   │   └── nested.svelte\n    │   │   │   ├── paths-and-params\n    │   │   │   │   ├── custom-not-found.svelte\n    │   │   │   │   ├── display-params.svelte\n    │   │   │   │   ├── paths-and-params.svelte\n    │   │   │   │   └── querystring-matching.svelte\n    │   │   │   ├── patterns\n    │   │   │   │   ├── dump.svelte\n    │   │   │   │   ├── output.svelte\n    │   │   │   │   ├── parameter-extraction.svelte\n    │   │   │   │   └── patterns.svelte\n    │   │   │   ├── protected\n    │   │   │   │   ├── manage-account\n    │   │   │   │   │   ├── auth-guard-fast.ts\n    │   │   │   │   │   ├── auth-guard-slow.ts\n    │   │   │   │   │   ├── balance.svelte\n    │   │   │   │   │   ├── home.svelte\n    │   │   │   │   │   ├── manage-account.svelte\n    │   │   │   │   │   └── worker-client.svelte.ts\n    │   │   │   │   ├── account-state.svelte.ts\n    │   │   │   │   ├── denied.svelte\n    │   │   │   │   ├── login.svelte\n    │   │   │   │   └── main.svelte\n    │   │   │   ├── transitions\n    │   │   │   │   ├── fade.svelte\n    │   │   │   │   ├── slide.svelte\n    │   │   │   │   └── transitions.svelte\n    │   │   │   ├── delayed.svelte\n    │   │   │   ├── home.svelte\n    │   │   │   └── not-found.svelte\n    │   │   ├── app.css\n    │   │   ├── app.svelte\n    │   │   ├── main.ts\n    │   │   └── vite-env.d.ts\n    │   ├── cypress.config.ts\n    │   ├── index.html\n    │   ├── svelte.config.ts\n    │   ├── tailwind.config.ts\n    │   └── vite.config.ts\n    ├── docs\n    │   ├── actions.md\n    │   ├── changelog.md\n    │   ├── debugging.md\n    │   ├── getting-started.md\n    │   ├── helpers.md\n    │   ├── hooks.md\n    │   ├── props.md\n    │   ├── readme.md\n    │   ├── registry.md\n    │   ├── routing-patterns.md\n    │   ├── routing.md\n    │   ├── statuses.md\n    │   └── styling.md\n    ├── src\n    │   ├── lib\n    │   │   ├── actions\n    │   │   │   ├── active.svelte.ts\n    │   │   │   ├── apply-classes.ts\n    │   │   │   ├── index.ts\n    │   │   │   ├── options.ts\n    │   │   │   └── route.svelte.ts\n    │   │   ├── helpers\n    │   │   │   ├── evaluators.test.ts\n    │   │   │   ├── evaluators.ts\n    │   │   │   ├── goto.ts\n    │   │   │   ├── identify.ts\n    │   │   │   ├── index.ts\n    │   │   │   ├── logging.ts\n    │   │   │   ├── marshal.test.ts\n    │   │   │   ├── marshal.ts\n    │   │   │   ├── normalize.ts\n    │   │   │   ├── objects.ts\n    │   │   │   ├── query.ts\n    │   │   │   ├── regexp.ts\n    │   │   │   ├── runtime.ts\n    │   │   │   ├── tracing.svelte.ts\n    │   │   │   ├── urls.test.ts\n    │   │   │   └── urls.ts\n    │   │   ├── hash.test.ts\n    │   │   ├── hash.ts\n    │   │   ├── hooks.ts\n    │   │   ├── index.ts\n    │   │   ├── path.ts\n    │   │   ├── query.svelte.ts\n    │   │   ├── query.test.ts\n    │   │   ├── registry.svelte.ts\n    │   │   ├── route.svelte.ts\n    │   │   ├── router-instance-config.ts\n    │   │   ├── router-instance.svelte.ts\n    │   │   ├── router.svelte\n    │   │   ├── statuses.ts\n    │   │   └── utilities.svelte.ts\n    │   └── vite-env.d.ts\n    ├── svelte.config.ts\n    ├── vite.config.ts\n    ├── vitest.config.ts\n    └── vitest.setup.ts\n\n\n\n---\nFile: /.github/ISSUE_TEMPLATE/bug_report.md\n---\n\n---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: mateothegreat\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Logs**\nIf available please provide any available logs, screenshots, etc.\n\n**Additional context**\nAdd any other context about the problem here.\n\n\n\n---\nFile: /.github/ISSUE_TEMPLATE/feature_request.md\n---\n\n---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: mateothegreat\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n\n\n---\nFile: /demo/cypress/e2e/route-activation.cy.ts\n---\n\n/// <reference types=\"cypress\" />\n\nconst routes = require(\"../fixtures/routes.json\");\ndescribe.only(\"route activation\", () => {\n  beforeEach(() => {\n    cy.viewport(1500, 1500);\n    cy.visit(\"http://localhost:8173\");\n  });\n\n  routes.forEach((route) => {\n    it(`should activate ${route.id} when visiting ${route.path}`, () => {\n      cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, \"active\", route.active);\n    });\n  });\n\n  // it.only(\"displays two todo items by default\", () => {\n  //   // cy.get(\"a[href='/props']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"props.svelte\").should(\"exist\");\n\n  //   // cy.get(\"a[href='/props/foo']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"foo\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar?\"]').should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"bar\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href=\"/props/foo\"]').should(\"not.have.class\", \"active\");\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar\"]').should(\"have.class\", \"active\");\n\n  //   cy.clickAndValidateActiveClasses(\"a[href='/props']\", \"active\");\n  // });\n\n  // it(\"can add new todo items\", () => {\n  //   // We'll store our item text in a variable so we can reuse it\n  //   const newItem = \"Feed the cat\";\n\n  //   // Let's get the input element and use the `type` command to\n  //   // input our new list item. After typing the content of our item,\n  //   // we need to type the enter key as well in order to submit the input.\n  //   // This input has a data-test attribute so we'll use that to select the\n  //   // element in accordance with best practices:\n  //   // https://on.cypress.io/selecting-elements\n  //   cy.get(\"[data-test=new-todo]\").type(`${newItem}{enter}`);\n\n  //   // Now that we've typed our new item, let's check that it actually was added to the list.\n  //   // Since it's the newest item, it should exist as the last element in the list.\n  //   // In addition, with the two default items, we should have a total of 3 elements in the list.\n  //   // Since assertions yield the element that was asserted on,\n  //   // we can chain both of these assertions together into a single statement.\n  //   cy.get(\".todo-list li\")\n  //     .should(\"have.length\", 3)\n  //     .last()\n  //     .should(\"have.text\", newItem);\n  // });\n\n  // it(\"can check off an item as completed\", () => {\n  //   // In addition to using the `get` command to get an element by selector,\n  //   // we can also use the `contains` command to get an element by its contents.\n  //   // However, this will yield the <label>, which is lowest-level element that contains the text.\n  //   // In order to check the item, we'll find the <input> element for this <label>\n  //   // by traversing up the dom to the parent element. From there, we can `find`\n  //   // the child checkbox <input> element and use the `check` command to check it.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parent()\n  //     .find(\"input[type=checkbox]\")\n  //     .check();\n\n  //   // Now that we've checked the button, we can go ahead and make sure\n  //   // that the list element is now marked as completed.\n  //   // Again we'll use `contains` to find the <label> element and then use the `parents` command\n  //   // to traverse multiple levels up the dom until we find the corresponding <li> element.\n  //   // Once we get that element, we can assert that it has the completed class.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parents(\"li\")\n  //     .should(\"have.class\", \"completed\");\n  // });\n\n  // context(\"with a checked task\", () => {\n  //   beforeEach(() => {\n  //     // We'll take the command we used above to check off an element\n  //     // Since we want to perform multiple tests that start with checking\n  //     // one element, we put it in the beforeEach hook\n  //     // so that it runs at the start of every test.\n  //     cy.contains(\"Pay electric bill\")\n  //       .parent()\n  //       .find(\"input[type=checkbox]\")\n  //       .check();\n  //   });\n\n  //   it(\"can filter for uncompleted tasks\", () => {\n  //     // We'll click on the \"active\" button in order to\n  //     // display only incomplete items\n  //     cy.contains(\"Active\").click();\n\n  //     // After filtering, we can assert that there is only the one\n  //     // incomplete item in the list.\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Walk the dog\");\n\n  //     // For good measure, let's also assert that the task we checked off\n  //     // does not exist on the page.\n  //     cy.contains(\"Pay electric bill\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can filter for completed tasks\", () => {\n  //     // We can perform similar steps as the test above to ensure\n  //     // that only completed tasks are shown\n  //     cy.contains(\"Completed\").click();\n\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Pay electric bill\");\n\n  //     cy.contains(\"Walk the dog\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can delete all completed tasks\", () => {\n  //     // First, let's click the \"Clear completed\" button\n  //     // `contains` is actually serving two purposes here.\n  //     // First, it's ensuring that the button exists within the dom.\n  //     // This button only appears when at least one task is checked\n  //     // so this command is implicitly verifying that it does exist.\n  //     // Second, it selects the button so we can click it.\n  //     cy.contains(\"Clear completed\").click();\n\n  //     // Then we can make sure that there is only one element\n  //     // in the list and our element does not exist\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .should(\"not.have.text\", \"Pay electric bill\");\n\n  //     // Finally, make sure that the clear button no longer exists.\n  //     cy.contains(\"Clear completed\").should(\"not.exist\");\n  //   });\n  // });\n});\n\n\n\n---\nFile: /demo/cypress/support/commands.ts\n---\n\n/// <reference types=\"cypress\" />\n\n\n\n---\nFile: /demo/cypress/support/e2e.ts\n---\n\nimport \"./commands\";\n\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      /**\n       * Custom command to check if navigation link is active based on href.\n       *\n       * @param selector - The selector to check against\n       * @param attribute - The attribute to check against\n       * @param value - The value to check against\n       *\n       * @example cy.hasClassByAttr('nav a', 'href', '/about', 'foo')\n       */\n      allowedClassesByAttr(selector: string, attribute: string, allowed: string | string[], className: string): Chainable<Element>;\n\n      /**\n       * Custom command to navigate to a specific path and validate the number of active elements.\n       *\n       * @param selector - The selector to check against\n       * @param path - The path to navigate to\n       * @param expected - The expected number of active elements\n       *\n       * @example cy.clickAndValidateActiveClasses(\"a[href='/props/foo']\", \"active\", 1)\n       */\n      clickAndValidateActiveClasses(selector: string, path: string, expected: number): Chainable<Element>;\n    }\n  }\n}\n\nCypress.Commands.add(\"allowedClassesByAttr\", (selector: string, attribute: string, allowed: string | string[], className: string) => {\n  cy.get(selector).then(($elements) => {\n    $elements.each((_, $el) => {\n      cy.wrap($el).then(($el) => {\n        if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n          if (Array.isArray(allowed) && !allowed.includes($el.attr(attribute) || \"\")) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          } else if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          }\n        }\n      });\n    });\n  });\n});\n\nCypress.Commands.add(\"clickAndValidateActiveClasses\", (selector: string, className: string, expected: number) => {\n  cy.get(selector).then(($el) => {\n    if ($el.length !== 1) {\n      throw new Error(`clickAndValidateActiveClasses: ${selector} should match only 1 element, has ${$el.length}`);\n    }\n    cy.get(selector).click();\n    cy.allowedClassesByAttr(\"nav a\", \"href\", $el.attr(\"href\") || \"\", className);\n  });\n});\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-link.svelte\n---\n\n<script lang=\"ts\">\n  import { session } from \"$lib/session.svelte\";\n  import { route, RouteOptions } from \"@mateothegreat/svelte5-router\";\n\n  export type RouteLinkProps = {\n    options?: RouteOptions;\n    href: string;\n    label: string;\n  };\n\n  let { options, href, label }: RouteLinkProps = $props();\n  if (!options) {\n    options = new RouteOptions();\n  }\n\n  if (!options.active) {\n    options.active = {\n      class: [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"]\n    };\n  }\n  if (!options.default) {\n    options.default = {\n      class: [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"]\n    };\n  }\n  if (!options.loading) {\n    options.loading = {\n      class: [\"loading\", \"bg-orange-500\"]\n    };\n  }\n  if (!options.disabled) {\n    options.disabled = {\n      class: [\"disabled\", \"bg-gray-500\"]\n    };\n  }\n\n  if (!options.active.class) {\n    options.active.class = [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"];\n  }\n\n  if (!options.default.class) {\n    options.default.class = [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"];\n  }\n  if (!options.loading.class) {\n    options.loading.class = [\"loading\", \"bg-orange-500\"];\n  }\n  if (!options.disabled.class) {\n    options.disabled.class = [\"disabled\", \"bg-gray-500\"];\n  }\n</script>\n\n<a\n  use:route={options}\n  href={session.mode === \"hash\" ? `/#${href}` : href}\n  class=\"duration-400 flex items-center rounded-sm border-2 px-2.5 py-0.5 text-sm transition-all hover:border-green-300 hover:bg-green-600 hover:text-white\">\n  {#if session.mode === \"hash\"}\n    <span>/#</span>\n    <span>/{label.startsWith(\"/\") ? label.slice(1) : label}</span>\n  {:else}\n    <span>\n      {label}\n    </span>\n  {/if}\n</a>\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-title.svelte\n---\n\n<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowDown, ArrowRight, ArrowRightFromLine, StopCircle } from \"lucide-svelte\";\n  import FileLink from \"../file-link.svelte\";\n\n  export type RouteTitleProps = {\n    router?: RouterInstance;\n    route?: RouteResult;\n    file?: string;\n    content?: any;\n    end?: boolean;\n  };\n\n  let { router = $bindable(), route, file, content, end }: RouteTitleProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex items-center gap-3 rounded-md bg-black/50 p-1.5 px-2 border-2\">\n    {#if router}\n      <div class=\"flex flex-wrap items-center rounded-sm bg-gray-800 px-1.5 py-0.5 text-sm text-slate-500\">\n        <ArrowRightFromLine class=\"h-4 w-4 text-green-400 mr-1\" />\n        {router.config.id}\n        {#if router.navigating}\n          <span class=\"px-1 py-0.5 text-red-400\">(hooks firing)</span>\n        {:else}\n          <span class=\"px-1 py-0.5 text-slate-600\">(idle)</span>\n        {/if}\n        routed the path\n        <span class=\"px-1 py-0.5 text-green-400\">\n          {route?.absolute?.()}\n        </span>\n        and nesting&nbsp;\n        {#if end}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-red-400\">stopped</span>\n            <StopCircle class=\"h-4 w-4 text-red-400\" />\n          </span>\n        {:else}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-green-400\">continued</span>\n            <ArrowDown class=\"h-4 w-4 text-green-400\" />\n          </span>\n        {/if}\n      </div>\n    {/if}\n    <ArrowRight class=\"h-4 w-4 text-slate-500\" />\n    <FileLink {file} />\n  </div>\n  {#if content}\n    <div class=\"p-2\">\n      {#if typeof content === \"string\"}\n        <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n          <div class=\"max-w-3xl text-sm text-slate-500\">\n            {content}\n          </div>\n        </div>\n      {:else}\n        <div class=\"flex items-center\">\n          {@render content()}\n        </div>\n      {/if}\n    </div>\n  {/if}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-wrapper.svelte\n---\n\n<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Anchor } from \"lucide-svelte\";\n  import type { RouteLinkProps } from \"./route-link.svelte\";\n  import RouteLink from \"./route-link.svelte\";\n  import type { RouteTitleProps } from \"./route-title.svelte\";\n  import RouteTitle from \"./route-title.svelte\";\n\n  export type RouteWrapperProps = {\n    router?: RouterInstance;\n    name: string;\n    route?: RouteResult;\n    end?: boolean;\n    title: RouteTitleProps;\n    links: RouteLinkProps[];\n    children?: any;\n  };\n\n  let { router = $bindable(), name, route, title, links, children, end = $bindable() }: RouteWrapperProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-3 border-2 rounded-md h-full p-2.5\">\n  <div class=\"flex flex-col gap-4\">\n    <RouteTitle\n      {router}\n      {route}\n      {end}\n      file={title.file}\n      content={title.content} />\n    <div class=\"flex w-fit items-center gap-2 rounded-md border-2 border-slate-900/80 bg-slate-700/80 p-1.5\">\n      <p class=\"flex items-center gap-1 text-sm text-slate-300\">\n        <Anchor class=\"h-4 w-4 text-indigo-500\" />\n        <span class=\"text-pink-400\">{router?.config.id}</span>\n        routes:\n      </p>\n      {#each links as link}\n        <RouteLink {...link} />\n      {/each}\n    </div>\n  </div>\n  <div class=\"flex-1\">\n    {@render children?.()}\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/badge.svelte\n---\n\n<script lang=\"ts\">\n  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n\n  let {\n    icon,\n    text,\n    variant = \"info\",\n    class: className,\n    children\n  }: {\n    icon?: any;\n    text?: string;\n    variant?: \"info\" | \"success\" | \"warning\" | \"error\";\n    class?: string;\n    children?: Snippet;\n  } = $props();\n\n  const variants = {\n    info: {\n      classes: \"bg-blue-900 text-white\",\n      icon: InfoIcon\n    },\n    success: {\n      classes: \"bg-green-600/80 text-white\",\n      icon: CheckCircleIcon\n    },\n    warning: {\n      classes: \"bg-yellow-200/80 text-black\",\n      icon: AlertTriangleIcon\n    },\n    error: {\n      classes: \"bg-red-900/80 text-white\",\n      icon: XCircleIcon\n    }\n  };\n\n  let Icon = icon ?? variants[variant].icon;\n</script>\n\n<span\n  class=\"inline-flex items-center gap-1.5 rounded-sm p-2 text-sm font-medium {variants[variant].classes} {className}\">\n  {#if Icon}\n    <Icon class=\"inline-block h-5 w-5\" />\n  {/if}\n  {text}\n  {@render children?.()}\n</span>\n\n\n\n---\nFile: /demo/src/lib/components/code.svelte\n---\n\n<script lang=\"ts\">\n  import { transformerColorizedBrackets } from \"@shikijs/colorized-brackets\";\n  import { Code2 } from \"lucide-svelte\";\n  import { createHighlighter, type Highlighter } from \"shiki\";\n  import { onMount, type Snippet } from \"svelte\";\n  import FileLink from \"./file-link.svelte\";\n\n  let {\n    title,\n    file,\n    class: className,\n    children,\n    language = \"typescript\",\n    theme = \"github-dark-dimmed\"\n  }: {\n    title?: string;\n    file?: string;\n    class?: string;\n    children?: Snippet;\n    language?: string;\n    theme?: string;\n  } = $props();\n\n  // State for the highlighted HTML content\n  let highlightedContent = $state<string>(\"\");\n  // State for the reference to the hidden temporary element\n  let tempElement: HTMLElement | undefined = $state(undefined);\n  // State for the Shiki highlighter instance\n  let shikiHighlighter: Highlighter | null = $state(null);\n\n  /**\n   * Initializes the Shiki highlighter instance once when the component mounts.\n   */\n  onMount(async () => {\n    shikiHighlighter = await createHighlighter({\n      themes: [theme],\n      langs: [\"typescript\", \"svelte\", \"html\", \"css\", \"json\"]\n    });\n  });\n\n  /**\n   * Reactive effect that updates the syntax highlighting whenever\n   * the children, language, or highlighter instance changes.\n   */\n  $effect(() => {\n    if (!shikiHighlighter || !children || !tempElement) {\n      if (!children) {\n        highlightedContent = \"\";\n      }\n      return;\n    }\n\n    /**\n     * At this point, Svelte has rendered the output of `children()` into `tempElement`\n     * due to the `{@render children()}` directive in the hidden div.\n     * We can now extract the raw text content from it.\n     */\n    const codeContent = (tempElement.textContent || \"\").trim();\n\n    if (codeContent) {\n      try {\n        // Convert the raw code string to HTML using Shiki.\n        highlightedContent = shikiHighlighter.codeToHtml(codeContent, {\n          lang: language,\n          theme: theme,\n          transformers: [transformerColorizedBrackets()]\n        });\n      } catch (error) {\n        console.error(\"Shiki highlighting error:\", error, { language, codeContent });\n        // Fallback to showing raw code (escaped) if highlighting fails.\n        highlightedContent = `<pre class=\"shiki-fallback\"><code>${escapeHtml(codeContent)}</code></pre>`;\n      }\n    } else {\n      // Clear highlighted content if there's no text content.\n      highlightedContent = \"\";\n    }\n  });\n\n  /**\n   * Helper function to escape HTML special characters.\n   * Used for the fallback when Shiki highlighting fails.\n   */\n  function escapeHtml(unsafe: string): string {\n    return unsafe\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\")\n      .replace(/\"/g, \"&quot;\")\n      .replace(/'/g, \"&#039;\");\n  }\n</script>\n\n<div class=\"code-block-container flex flex-col rounded-md border-2 border-slate-700 bg-black/80 {className}\">\n  <div class=\"header flex items-center justify-between bg-zinc-900/70 text-sm p-1.5 px-3 flex-shrink-0\">\n    <p class=\"flex items-center gap-1.5 font-mono text-xs text-slate-300\">\n      <Code2 class=\"h-4 w-4 text-orange-500 shrink-0\" />\n      {#if title}\n        <span>{title}</span>\n      {:else}\n        <span>Code</span>\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n\n  <!--\n    Temporary hidden element.\n    The `children` snippet is rendered here by Svelte using `{@render children()}`.\n    This component's `$effect` then reads `tempElement.textContent` to get the\n    raw string representation of the rendered children for Shiki to process.\n  -->\n  <div\n    bind:this={tempElement}\n    style=\"display: none;\"\n    aria-hidden=\"true\">\n    {#if children}\n      {@render children()}\n    {/if}\n  </div>\n\n  {#if highlightedContent}\n    <div class=\"p-2.5 text-[13px] leading-[18px] overflow-auto bg-[#0a0a0a] flex-1 min-h-0\">\n      {@html highlightedContent}\n    </div>\n  {:else if children}\n    <pre class=\"fallback-pre p-3 font-mono text-sm text-slate-200 overflow-auto flex-1 min-h-0\">\n      {@render children()}\n    </pre>\n  {:else}\n    <div class=\"p-3 text-slate-500 text-sm font-mono\">Loading syntax highlighter...</div>\n  {/if}\n</div>\n\n<style>\n  :global(.shiki) {\n    background: #090909 !important;\n  }\n</style>\n\n\n\n---\nFile: /demo/src/lib/components/container.svelte\n---\n\n<script lang=\"ts\">\n  import FileLink from \"./file-link.svelte\";\n\n  let { title, file, children }: { title?: string; file?: string; children: any } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1 rounded-md border-2 border-slate-700 bg-gray-900 text-slate-400\">\n  <div class=\"flex items-center justify-between bg-slate-800 p-2 text-sm text-slate-500\">\n    <p class=\"flex items-center gap-2 font-mono\">\n      {#if title}\n        &lt;{title} /&gt;\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n  {@render children()}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/default.svelte\n---\n\n<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n  import Badge from \"./badge.svelte\";\n\n  let { class: className, children }: { class?: string; children?: Snippet } = $props();\n</script>\n\n<div class=\"flex flex-col gap-4 rounded-md border-4 border-slate-900/80 p-4 text-center text-gray-400 {className}\">\n  {#if children}\n    {@render children()}\n  {:else}\n    <p>\n      <Badge\n        variant=\"info\"\n        icon={BadgeInfo}>\n        There was no path provided to the router, so the default route was used (declared as a snippet).\n      </Badge>\n    </p>\n    Click on a link above to see the different effects!\n  {/if}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/file-link.svelte\n---\n\n<script lang=\"ts\">\n  import { FileCode2, GithubIcon } from \"lucide-svelte\";\n\n  let { file } = $props();\n</script>\n\n<div class=\"flex gap-1\">\n  <div class=\"flex flex-1 items-center text-slate-500\">\n    <FileCode2 class=\"h-4 w-4\" />\n  </div>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-orange-400 hover:font-medium hover:text-indigo-400\">\n    {file}\n  </a>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    target=\"_blank\"\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-slate-500 hover:font-medium hover:text-indigo-400\">\n    view source\n    <GithubIcon class=\"h-4 w-4\" />\n  </a>\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/inline-code.svelte\n---\n\n<script lang=\"ts\">\n  export type InlineCodeProps = {\n    text: string;\n    class?: string;\n  };\n\n  let { text, class: className }: InlineCodeProps = $props();\n\n  if (!className) {\n    className = \"text-green-400 bg-black/50\";\n  }\n</script>\n\n<span class=\"whitespace-nowrap rounded-md p-1 px-2 font-mono text-sm {className}\">{text}</span>\n\n\n\n---\nFile: /demo/src/lib/default-route-config.ts\n---\n\nimport { StatusCode, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport NotFound from \"$routes/not-found.svelte\";\n\n/**\n * Surface a reusable configuration for routers to import\n * and apply to their router instances:\n *\n * @example\n * ```ts\n * <script lang=\"ts\">\n *   import { RouteConfig } from \"@mateothegreat/svelte5-router\";\n *   import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n *\n *   const routes: RouteConfig[] = [\n *     {\n *       path: \"/home\",\n *       component: Home\n *     }\n *   ];\n * </script>\n *\n * <Router\n *   id=\"my-main-router\"\n *   {routes}\n *   {...myDefaultRouterConfig} />\n * ```\n */\nexport const myDefaultRouterConfig = {\n  statuses: {\n    /**\n     * You can use a function to return a new route or a promise that\n     * resolves to a new route:\n     */\n    [StatusCode.NotFound]: (result: RouteResult) => {\n      console.log(result);\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n    /**\n     * You can also use an object to return a new route while having access\n     * to the path and querystring:\n     *\n     * [StatusCode.NotFound]: (path: RouteResult) => ({\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }),\n     *\n     *\n     * or simply return an object with a component and props:\n     *\n     * [StatusCode.NotFound]: {\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }\n     */\n  }\n};\n\n\n\n---\nFile: /demo/src/lib/router-history.ts\n---\n\nimport type { Route } from \"@mateothegreat/svelte5-router\";\n\nexport const history = $state<Route[]>([]);\n\nexport const appendHistory = (route: Route) => {\n  history.push(route);\n};\n\n\n\n---\nFile: /demo/src/lib/session.svelte.ts\n---\n\nlet _state: {\n  mode: \"hash\" | \"path\";\n} = $state({\n  mode: (localStorage.getItem(\"mode\") as \"hash\" | \"path\") || \"path\"\n});\n\nexport const session = {\n  set mode(value: \"hash\" | \"path\") {\n    localStorage.setItem(\"mode\", value);\n    _state = {\n      ..._state,\n      mode: value\n    };\n    if (value === \"hash\") {\n      window.history.pushState({}, \"\", `/#/`);\n    } else {\n      window.history.pushState({}, \"\", \"/\");\n    }\n  },\n  get mode() {\n    return _state.mode;\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/extras/dump.svelte\n---\n\n<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/extras/extras.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { onDestroy } from \"svelte\";\n  import PassingDownProps from \"./passing-down-props.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  let randoms = $state({\n    float: (Math.random() * 1000).toFixed(2),\n    int: (Math.random() * 1000).toFixed(0),\n    string: (Math.random() * 1000).toFixed(2).toString()\n  });\n\n  const interval = setInterval(() => {\n    randoms.float = (Math.random() * 1000).toFixed(2);\n    randoms.int = (Math.random() * 1000).toFixed(0);\n    randoms.string = (Math.random() * 1000).toFixed(2).toString();\n  }, 750);\n\n  onDestroy(() => {\n    clearInterval(interval);\n  });\n\n  const routes: RouteConfig[] = [\n    {\n      component: overview\n    },\n    {\n      path: \"passing-down-props\",\n      component: PassingDownProps,\n      props: {\n        route: \"passing-down-props\"\n      },\n      hooks: {\n        pre: () => {\n          console.log(\"pre\");\n          return true;\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet overview()}\n  <div class=\"p-2\">\n    <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n      <div class=\"flex max-w-3xl flex-col gap-2 text-sm text-slate-500\">\n        <p class=\"text-fuchsia-500\">Extra & cool stuff can be demoed here.</p>\n        <p>Click a route above to see the different effects!</p>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"extras\"\n  {route}\n  title={{\n    file: \"src/routes/extras/extras.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"passing-down-props\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras\"\n    bind:instance={router}\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/extras/passing-down-props.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import Dump from \"./dump.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  const routes: RouteConfig[] = [\n    {\n      component: Dump,\n      props: {\n        foo: \"bar\",\n        baz: {\n          awesome: true\n        }\n      }\n    }\n  ];\n</script>\n\n<RouteWrapper\n  {router}\n  name=\"passing-down-props\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/extras/passing-down-props.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"default path\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras/passing-down-props\"\n    bind:instance={router}\n    myAdditionalProp=\"I was added to the <Router/> component directly.\"\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/hash/hash.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  console.log(location.hash);\n</script>\n\n<a\n  use:route\n  href=\"/hash/#b?test=4\">\n  b\n</a>\n\n<a\n  use:route\n  href=\"/hash/#a?test=123\">\n  a\n</a>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte\n---\n\n<script>\n  import Container from \"$lib/components/container.svelte\";\n</script>\n\n<Container\n  title={\"Level_3\"}\n  file=\"src/routes/nested/level-1/level-2/level-3/level-3.svelte\">\n  <div class=\"flex flex-col gap-3 bg-green-500 p-4 text-center font-medium text-white\">\n    You've reached the deepest level of nested routing (level-3)!\n  </div>\n</Container>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-2/level-2.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_3 from \"./level-3/level-3.svelte\";\n\n  const routes: Route[] = [\n    {\n      path: \"level-3\",\n      component: Level_3,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-2.svelte route)`);\n          return true;\n        }\n      }\n    },\n    {\n      component: snippet\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-2/level-2.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-2\"\n  end={true}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-2/level-2.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2/level-3\",\n      label: \"/nested/level-1/level-2/level-3\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-2-router\"\n    basePath=\"/nested/level-1/level-2\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-1.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import Level_2 from \"./level-2/level-2.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route, foo } = $props();\n\n  /**\n   * Demonstrate how support for additional props is working.\n   */\n  console.log(\"additionalProps.foo in ../nested.svelte is passed to this component as:\", foo);\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-1.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-1\"\n  {end}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-1.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"/nested/level-1/level-2\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-1-router\"\n    basePath=\"/nested/level-1\"\n    bind:instance={router}\n    routes={[\n      {\n        path: \"level-2\",\n        component: Level_2,\n        hooks: {\n          pre: () => {\n            console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-1.svelte route)`);\n            return true;\n          }\n        }\n      },\n      /**\n       * Default routes can be placed anywhere in the routes array\n       * and will be matched if no other routes match regardless of\n       * their position in the array:\n       */\n      {\n        component: snippet\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/nested/nested.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_1 from \"./level-1/level-1.svelte\";\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"level-1\",\n      component: Level_1,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1\" matched (I'm a pre hook in the nested.svelte route)`);\n          return true;\n        }\n      }\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n\n  const additionalProps = {\n    foo: {\n      bar: \"baz\"\n    }\n  };\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/nested.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/nested/nested.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1\",\n      label: \"/nested/level-1\"\n    }\n  ]}>\n  <Router\n    id=\"nested-router\"\n    basePath=\"/nested\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes}\n    {...additionalProps} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/custom-not-found.svelte\n---\n\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"custom-not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/display-params.svelte\n---\n\n<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<Code\n  title=\"params.route value:\"\n  file=\"src/routes/props/display-params.svelte\">\n  <div>{JSON.stringify(route, null, 2)}</div>\n</Code>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/paths-and-params.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { getStatusByValue, RouterInstance, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { onDestroy } from \"svelte\";\n  import CustomNotFound from \"./custom-not-found.svelte\";\n  import DisplayParams from \"./display-params.svelte\";\n  import QuerystringMatching from \"./querystring-matching.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  let randoms = $state({\n    float: (Math.random() * 1000).toFixed(2),\n    int: (Math.random() * 1000).toFixed(0),\n    string: (Math.random() * 1000).toFixed(2).toString()\n  });\n\n  const interval = setInterval(() => {\n    randoms.float = (Math.random() * 1000).toFixed(2);\n    randoms.int = (Math.random() * 1000).toFixed(0);\n    randoms.string = (Math.random() * 1000).toFixed(2).toString();\n  }, 750);\n\n  onDestroy(() => {\n    clearInterval(interval);\n  });\n\n  const routes: RouteConfig[] = [\n    /**\n     * This route will be used if there is no matching routes we\n     * define below:\n     */\n    {\n      component: snippet\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?number=2&number-as-string=2\n     *   ✅ /paths-and-params/query-matcher?number=2.1&number-as-string=2.345\n     *   ❌ /paths-and-params/query-matcher?number=2&number-as-string=two\n     */\n    {\n      name: \"match-number-and-string\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"number\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        float: /^([\\d.]+)$/,\n        /**\n         * The \"number-as-string\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        string: \"123\"\n      }\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?pagination=1,10\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=1234567\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=1234567\n     *   ❌ /paths-and-params/query-matcher?pagination=1,&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=2,3,4\n     *   ❌ /paths-and-params/query-matcher?pagination=bad-value\n     */\n    {\n      name: \"match-pagination\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"pagination\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number\n         *   - ❔ and then be followed by an optional \"cursor\" parameter:\n         *     - ✅ it must have a comma delimiter\n         *     - ✅ it must be a string of alphanumeric characters only\n         */\n        pagination: /^(?<page>\\d+)(?:,(?<cursor>\\d+))?$/,\n        /**\n         * The \"company\" querystring parameter is optional, but if present:\n         *\n         *   - ✅ can be empty\n         *   - ✅ must be a single number\n         */\n        company: /^(\\d+)?$/\n      },\n      props: {\n        metadata: {\n          src: \"paths-and-params.svelte\"\n        }\n      }\n    },\n    /**\n     * This route will match any path and pass the pattern groups\n     * as an object to the component that is passed in $props().\n     *\n     * The component will access the params using $props() and the\n     * property \"child\" will contain the value extracted from the path.\n     */\n    {\n      path: \"extensions/(?<extension>.*)/(?<page>[^/]+)(?:/(?<rest>.*))?\",\n      component: DisplayParams\n    },\n    {\n      name: \"fancy-regex-capture-group\",\n      path: \"(foo|bar).*\",\n      component: DisplayParams,\n      props: {\n        randomId: Math.random().toString(36).substring(2, 15),\n        someUserStuff: {\n          username: \"mateothegreat\",\n          userAgent: navigator.userAgent\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/paths-and-params/paths-and-params.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/paths-and-params\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/paths-and-params/paths-and-params.svelte\",\n    content: \"This demo shows how to pass values downstream to the component that is rendered.\"\n  }}\n  links={[\n    {\n      href: \"/paths-and-params\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/paths-and-params/foo\",\n      label: \"foo\"\n    },\n    {\n      href: \"/paths-and-params/query-matcher?pagination=2,23&company=123\",\n      label: \"query-matcher?pagination=2,23&company=123\"\n    },\n    {\n      href: `/paths-and-params/query-matcher?string=123&float=${randoms.float}`,\n      label: `query-matcher?string=123&float=${randoms.float}`\n    }\n  ]}>\n  <Router\n    id=\"props-router\"\n    basePath=\"/paths-and-params\"\n    bind:instance={router}\n    {routes}\n    hooks={{\n      /**\n       * You could use a global auth guard here to run before every route:\n       *\n       * hooks={{\n       *   pre: (route: Routed) => {\n       *     if (!isAuthenticated()) {\n       *       console.warn(\"user is not authenticated, redirecting to login\", route);\n       *       return {\n       *         component: NotGonnaMakeIt,\n       *       };\n       *     }\n       *   }\n       * }}\n       *\n       * You could also use a global error handler here to run after every route:\n       *\n       * hooks={{\n       *   post: [\n       *     (route: Routed) => {\n       *       console.info(\"do some more work here\", route);\n       *       return true;\n       *     },\n       *     someLogMethod,\n       *     finalMethod,\n       *   ]\n       * }}\n       */\n    }}\n    statuses={{\n      [StatusCode.NotFound]: (result: RouteResult) => {\n        console.warn(`the path \"${result.result.path.original}\" could not be found :(`, {\n          /**\n           * You could use the status name to make something pretty:\n           */\n          status: getStatusByValue(StatusCode.NotFound),\n          /**\n           * You could also use the status code to something more dynamic:\n           */\n          code: StatusCode.NotFound\n        });\n        /**\n         * Now, we're going to return a new route that will be rendered by the router:\n         */\n        return {\n          component: CustomNotFound,\n          /**\n           * You can pass props to the component that is rendered if you need to\n           * share some extra information:\n           */\n          props: {\n            src: \"props.svelte\"\n          }\n        };\n      }\n    }} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/querystring-matching.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n  <div class=\"flex flex-col gap-5\">\n    <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n      <InlineCode text=\"querystring\" />\n      match multiple values\n      <InlineCode text=\"RegExp\" />\n      <InlineCode text=\"number\" />\n      <InlineCode text=\"string\" />\n      <InlineCode text=\"boolean\" />\n      <InlineCode text=\"array\" />\n    </div>\n    <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n      <Badge\n        variant=\"warning\"\n        class=\"w-fit\">\n        Matching will occur if <em><strong>all</strong></em>\n        querystring keys and values match what was provided in the `querystring` configuration option.\n      </Badge>\n      <div class=\" gap-1\">\n        This demo shows how to use the route's\n        <InlineCode text=\"querystring\" />\n        configuration option to match against the current\n        <InlineCode text=\"location.search\" />\n        value passed in by the browser.\n      </div>\n      <Badge\n        variant=\"success\"\n        class=\"w-fit\">\n        There are 2 potential matches for the path \"/paths-and-params/query-matcher\". Notice how the active route is\n        highlighted in green for this route only + the querystring matching.\n      </Badge>\n    </div>\n  </div>\n  <div class=\"flex flex-col gap-6 border-2 bg-gray-900/60 rounded-lg p-4 border-slate-700\">\n    <Code\n      title=\"$props().route.result value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div>{JSON.stringify(route.result, null, 2)}</div>\n    </Code>\n    <Code\n      title=\"$props().route.route value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div class=\"text-indigo-400\">{JSON.stringify(route.route, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/dump.svelte\n---\n\n<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n\n  import { Lightbulb } from \"lucide-svelte\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    language=\"svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/output.svelte\n---\n\n<div class=\"flex flex-col gap-4 p-4\">\n  <div>\n    <h1 class=\"text-xl font-semibold text-slate-200\">Nested Paths</h1>\n    <p class=\"text-slate-400\">\n      This example will match any path that starts with `/path/path/path` and can be nested further.\n    </p>\n  </div>\n  <div class=\"bg-slate-800/50 rounded-lg p-3\">\n    <h3 class=\"text-sm font-medium text-slate-300 mb-2\">Route Props:</h3>\n    <pre class=\"text-xs text-slate-400 overflow-auto\">{JSON.stringify(props, null, 2)}</pre>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/parameter-extraction.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route: r, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1\">\n  <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">Parameter Extraction</h1>\n  <div class=\"text-slate-500 text-sm\">\n    <p>\n      Given the following route config, everything after the \"/parameter-extraction\" will be available as a named\n      parameter. This means that the following paths will be matched:\n    </p>\n    <ul class=\"list-disc list-inside\">\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/foo\">\n          parameter-extraction/foo\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/bar\">\n          parameter-extraction/bar\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/baz\">\n          parameter-extraction/baz\n        </a>\n      </li>\n    </ul>\n  </div>\n</div>\n\n<div class=\"flex gap-3\">\n  <Code\n    title=\"RouteConfig:\"\n    language=\"typescript\"\n    class=\"flex-1\">\n    {`  const routes: RouteConfig[] = [\n  {\n    path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n    component: ParameterExtraction\n  }\n];`}\n  </Code>\n  <Code\n    title=\"$props().route.result.path.params\"\n    language=\"json\"\n    class=\"flex-1\">\n    <div>{JSON.stringify(r.result.path.params, null, 2)}</div>\n  </Code>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/patterns.svelte\n---\n\n<script lang=\"ts\">\n  import { route, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import type { TableColumn } from \"@mateothegreat/svelte5-table\";\n  import { DropinTable } from \"@mateothegreat/svelte5-table\";\n  import { CirclePlay, Lightbulb, MousePointerClick } from \"lucide-svelte\";\n  import { writable, type Writable } from \"svelte/store\";\n  import Dump from \"./dump.svelte\";\n  import ParameterExtraction from \"./parameter-extraction.svelte\";\n\n  type Component = {\n    name: string;\n    description: string;\n    path: string;\n  };\n\n  const columns: TableColumn[] = [\n    {\n      field: \"name\",\n      header: \"Routing Pattern\"\n    },\n    {\n      field: \"description\",\n      header: \"Description\"\n    },\n    {\n      field: \"actions\",\n      header: customHeader,\n      class: \"w-[300px]\",\n      renderer: action\n    }\n  ];\n\n  const components: Writable<Component[]> = writable([\n    {\n      name: \"Default Route\",\n      description: \"If the path is empty, the route will be matched otherwise evaluation will continue.\",\n      path: \"/default-route\"\n    },\n    {\n      name: \"Single Path\",\n      description: \"This example will match any path that starts with `/path`.\",\n      path: \"/single-path\"\n    },\n    {\n      name: \"Nested Paths\",\n      description: \"This example will match any path that starts with `/path/path/path` and can be nested further.\",\n      path: \"/nested-paths\"\n    },\n    {\n      name: \"Parameter Extraction\",\n      description: \"Combine arbitrary paths and extractable parameters.\",\n      path: \"/parameter-extraction\"\n    },\n    {\n      name: \"Named Parameters\",\n      description:\n        \"This example will match any path that starts with `/path/path/path/path` and can be nested further.\",\n      path: \"/named-parameters\"\n    }\n  ]);\n\n  let selections = writable([]);\n  const routes: RouteConfig[] = [\n    {\n      path: \"default-route\",\n      component: Dump\n    },\n    {\n      path: \"single-path\",\n      component: Dump\n    },\n    {\n      path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n      component: ParameterExtraction\n    }\n  ];\n</script>\n\n{#snippet customHeader()}\n  <div class=\"flex items-center gap-1 pl-2\">\n    Navigate to Demo\n    <CirclePlay class=\"h-4 w-4 text-green-500\" />\n  </div>\n{/snippet}\n\n{#snippet action(row: any)}\n  <div class=\"flex flex-1 ml-2\">\n    <a\n      use:route\n      href={`/patterns${row.path}`}\n      class=\"bg-gray-800/50 flex items-center gap-1 rounded-sm border-2 border-purple-700 px-2 py-1 text-sm text-slate-500 transition-all hover:border-green-300 hover:bg-slate-200/20 hover:text-white duration-400\">\n      <MousePointerClick class=\"h-4 w-4\" />\n      <span class=\"text-green-500 flex items-center gap-0.5\">\n        goto(\"\n        <span class=\"text-sky-400\">\n          {row.path}\n        </span>\n        \")\n      </span>\n    </a>\n  </div>\n{/snippet}\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex flex-col gap-1\">\n    <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">\n      <Lightbulb class=\"h-5 w-5 text-cyan-400\" />\n      Routing Patterns\n    </h1>\n    <p class=\"text-slate-500 text-sm\">This page contains an assortment of routing patterns to help you get started.</p>\n  </div>\n  <div class=\"bg-slate-700/20 text-slate-600 py-2 border-2 border-slate-700 rounded-lg\">\n    <DropinTable\n      {columns}\n      data={$components}\n      bind:selections />\n  </div>\n  <div class=\"flex flex-1 flex-col gap-2\">\n    <Router\n      basePath=\"/patterns\"\n      myExtraRouterProp={{\n        calledFrom: \"patterns <Router />\"\n      }}\n      {routes} />\n  </div>\n</div>\n\n<style>\n  :global(td) {\n    padding: 8px !important;\n  }\n</style>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/auth-guard-fast.ts\n---\n\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardFast = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/auth-guard-slow.ts\n---\n\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardSlow = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  await new Promise((resolve) => setTimeout(resolve, 2000));\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/balance.svelte\n---\n\n<script lang=\"ts\">\n  import { ArrowDownRight, ArrowUpRight, DollarSign } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake account data\n  const accountBalance = (Math.random() * 100000).toFixed(2);\n  const transactions = [\n    { id: 1, type: \"credit\", amount: 2500.0, description: \"Salary Deposit\", date: \"2024-03-15\" },\n    { id: 2, type: \"debit\", amount: 85.5, description: \"Grocery Store\", date: \"2024-03-14\" },\n    { id: 3, type: \"debit\", amount: 125.0, description: \"Electric Bill\", date: \"2024-03-13\" },\n    { id: 4, type: \"credit\", amount: 500.0, description: \"Freelance Payment\", date: \"2024-03-12\" },\n    { id: 5, type: \"debit\", amount: 45.99, description: \"Online Shopping\", date: \"2024-03-11\" }\n  ];\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-8 rounded-xl bg-blue-600 p-8 text-white shadow-lg\">\n    <h2 class=\"mb-2 text-xl\">Current Balance</h2>\n    <div class=\"flex items-center gap-2\">\n      <DollarSign size={32} />\n      <span class=\"text-4xl font-bold\">{accountBalance}</span>\n    </div>\n  </div>\n\n  <div class=\"rounded-xl bg-white p-6 shadow-lg\">\n    <h3 class=\"mb-4 text-xl font-semibold text-gray-800\">Recent Transactions</h3>\n    <div class=\"space-y-4\">\n      {#each transactions as transaction}\n        <div\n          class=\"flex items-center justify-between rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-50\">\n          <div class=\"flex items-center gap-3\">\n            {#if transaction.type === \"credit\"}\n              <div class=\"rounded-full bg-green-100 p-2\">\n                <ArrowUpRight class=\"h-5 w-5 text-green-600\" />\n              </div>\n            {:else}\n              <div class=\"rounded-full bg-red-100 p-2\">\n                <ArrowDownRight class=\"h-5 w-5 text-red-600\" />\n              </div>\n            {/if}\n            <div>\n              <p class=\"font-medium text-gray-800\">{transaction.description}</p>\n              <p class=\"text-sm text-gray-500\">{transaction.date}</p>\n            </div>\n          </div>\n          <span class={transaction.type === \"credit\" ? \"text-green-600\" : \"text-red-600\"}>\n            {transaction.type === \"credit\" ? \"+\" : \"-\"}${transaction.amount.toLocaleString(\"en-US\", {\n              minimumFractionDigits: 2\n            })}\n          </span>\n        </div>\n      {/each}\n    </div>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/home.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { Activity, PiggyBank, Send, Wallet } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake user data\n  const userData = {\n    name: \"Svelte Boss\",\n    lastLogin: new Date().toLocaleString(),\n    quickStats: [\n      { title: \"Total Balance\", amount: \"$45,250.80\", icon: Wallet },\n      { title: \"Monthly Savings\", amount: \"$2,450.00\", icon: PiggyBank },\n      { title: \"Recent Activity\", count: \"12 transactions\", icon: Activity }\n    ]\n  };\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-6\">\n    <h1 class=\"text-2xl text-gray-400\">\n      Welcome back,\n      <span class=\"text-indigo-400 font-bold\">{userData.name}</span>\n    </h1>\n    <p class=\"text-gray-400\">Last login: {userData.lastLogin}</p>\n  </div>\n\n  <div class=\"grid gap-6 md:grid-cols-3\">\n    {#each userData.quickStats as stat}\n      <div class=\"rounded-xl bg-slate-700/30 p-6 shadow-lg transition-transform hover:scale-[1.02]\">\n        <div class=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-100\">\n          <svelte:component\n            this={stat.icon}\n            class=\"h-6 w-6 text-blue-600\" />\n        </div>\n        <h3 class=\"text-lg text-gray-400\">{stat.title}</h3>\n        <p class=\"mt-1 text-xl font-bold text-green-400\">{stat.amount || stat.count}</p>\n      </div>\n    {/each}\n  </div>\n\n  <div class=\"mt-8\">\n    <a\n      use:route\n      href=\"/protected/manage-account/balance\"\n      class=\"flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-6 py-4 font-semibold text-white transition-colors hover:bg-blue-700\">\n      <Send class=\"h-5 w-5\" />\n      View Balance\n    </a>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/manage-account.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import { goto, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Shield, Wallet } from \"lucide-svelte\";\n  import { client } from \"../account-state.svelte\";\n  import { authGuardSlow } from \"./auth-guard-slow\";\n  import Balance from \"./balance.svelte\";\n  import Home from \"./home.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected/manage-account\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/protected/manage-account/manage-account.svelte\",\n    content: \"This router demonstrates how you can restrict access to routes based on the user's authentication state.\"\n  }}\n  links={[\n    {\n      href: \"/protected/manage-account\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/manage-account/balance\",\n      label: \"/protected/manage-account/balance\"\n    },\n    {\n      href: \"/protected/manage-account/logout\",\n      label: \"/protected/manage-account/logout\"\n    }\n  ]}>\n  <Router\n    id=\"manage-account-router\"\n    basePath=\"/protected/manage-account\"\n    bind:instance={router}\n    routes={[\n      /**\n       * This route is optional, it's for demonstration purposes.\n       * It's used to redirect to the balance page when the user\n       * navigates to the manage-account route (the default path):\n       */\n      {\n        component: Home\n      },\n      {\n        path: \"/balance\",\n        component: Balance,\n        hooks: {\n          pre: authGuardSlow\n        }\n      },\n      {\n        path: \"/logout\",\n        hooks: {\n          pre: () => {\n            client.loggedIn = false;\n            setTimeout(() => {\n              goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n            }, 100);\n          }\n        }\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/worker-client.svelte.ts\n---\n\nlet working = $state(false);\n\nexport const workerClient = {\n  get working() {\n    return working;\n  },\n  set working(value: boolean) {\n    working = value;\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/protected/account-state.svelte.ts\n---\n\nlet token = $state(localStorage.getItem(\"token\"));\n\nexport const client = {\n  get loggedIn() {\n    return token !== null;\n  },\n  set loggedIn(value: boolean) {\n    token = value ? \"true\" : null;\n    console.log(\"token\", token);\n    if (value) {\n      localStorage.setItem(\"token\", \"true\");\n    } else {\n      localStorage.removeItem(\"token\");\n    }\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/protected/denied.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { LockIcon, ShieldAlert } from \"lucide-svelte\";\n</script>\n\n<div class=\"flex items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8\">\n  <div class=\"w-full max-w-md space-y-8 rounded-lg bg-white p-8 shadow-lg\">\n    <div class=\"text-center\">\n      <div class=\"mb-4 flex justify-center\">\n        <ShieldAlert class=\"h-16 w-16 text-red-600\" />\n      </div>\n      <h1 class=\"mb-2 text-3xl font-bold text-gray-900\">Access Denied</h1>\n      <div class=\"mb-4 flex items-center justify-center gap-2 text-red-600\">\n        <LockIcon class=\"h-5 w-5\" />\n        <span class=\"font-semibold\">Secure Area</span>\n      </div>\n      <p class=\"mb-4 text-gray-600\">For your security, access to this banking area has been denied. This may be due to:</p>\n      <ul class=\"mb-6 ml-4 list-disc text-left text-gray-600\">\n        <li>Insufficient permissions</li>\n        <li>Invalid authentication</li>\n        <li>Session timeout</li>\n      </ul>\n      <p class=\"text-sm text-gray-500\">Please contact our support team or return to the homepage if you believe this is an error.</p>\n      <div class=\"mt-6\">\n        <a\n          use:route\n          href=\"/protected\"\n          class=\"inline-flex items-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\">\n          Return to Homepage\n        </a>\n      </div>\n    </div>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/login.svelte\n---\n\n<script lang=\"ts\">\n  import { goto } from \"@mateothegreat/svelte5-router\";\n  import { Shield } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n  import { client } from \"./account-state.svelte\";\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mx-auto max-w-md rounded-xl bg-white p-8 shadow-lg\">\n    <div class=\"mb-6 text-center\">\n      <div class=\"mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100\">\n        <Shield class=\"h-8 w-8 text-blue-600\" />\n      </div>\n      <h2 class=\"text-2xl font-bold text-gray-800\">Secure Login</h2>\n      <p class=\"mt-2 text-gray-600\">Please log in to access your account</p>\n    </div>\n\n    <button\n      class=\"w-full rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700\"\n      on:click={() => {\n        client.loggedIn = true;\n        goto(\"/protected/manage-account\");\n      }}>\n      Login to Your Account\n    </button>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/main.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { goto, registry, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Loader2, Shield, Wallet } from \"lucide-svelte\";\n  import Denied from \"./denied.svelte\";\n  import Login from \"./login.svelte\";\n  import { authGuardFast } from \"./manage-account/auth-guard-fast\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(true);\n\n  $effect(() => {\n    end =\n      router.current?.result.path.condition === \"default-match\" ||\n      location.pathname === \"/protected/login\" ||\n      location.pathname === \"/protected/denied\";\n  });\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/protected/main.svelte\",\n    content: \"Demo to show how you can use hooks to control the navigation of your app to control authentication, etc.\"\n  }}\n  links={[\n    {\n      href: \"/protected\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/login\",\n      label: \"/protected/login\"\n    },\n    {\n      href: \"/protected/manage-account\",\n      label: \"/protected/manage-account\"\n    },\n    {\n      href: \"/protected/denied\",\n      label: \"/protected/denied\"\n    }\n  ]}>\n  <Router\n    id=\"protected-router\"\n    basePath=\"/protected\"\n    bind:instance={router}\n    routes={[\n      {\n        component: snippet\n      },\n      {\n        path: \"login\",\n        component: Login\n      },\n      {\n        path: \"manage-account\",\n        component: async () => import(\"./manage-account/manage-account.svelte\"),\n        hooks: {\n          pre: authGuardFast\n        },\n      },\n      {\n        path: \"denied\",\n        component: Denied\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n{#if registry.get(\"manage-account-router\")?.navigating}\n  <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n    <div class=\"flex flex-col items-center gap-4 rounded-md border-2 border-green-400 bg-black/70 px-20 py-6\">\n      <Loader2 class=\"h-12 w-12 text-green-500  animate-spin\" />\n      <div class=\"text-slate-300 font-bold\">Doing some work...</div>\n      <div class=\"text-slate-400 w-96 text-center\">\n        We've added some pre and post hooks to the manage-account router to simulate doing some work.\n      </div>\n    </div>\n  </div>\n{/if}\n\n\n\n---\nFile: /demo/src/routes/transitions/fade.svelte\n---\n\n<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { fade } from \"svelte/transition\";\n</script>\n\n<div in:fade={{ duration: 500 }}>\n  <Container\n    title=\"Fade\"\n    file=\"src/routes/transitions/fade.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-sky-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n\n\n\n---\nFile: /demo/src/routes/transitions/slide.svelte\n---\n\n<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { slide } from \"svelte/transition\";\n</script>\n\n<div in:slide={{ duration: 500 }}>\n  <Container\n    title=\"Slide\"\n    file=\"src/routes/transitions/slide.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-indigo-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n\n\n\n---\nFile: /demo/src/routes/transitions/transitions.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Fade from \"./fade.svelte\";\n  import Slide from \"./slide.svelte\";\n\n  let { route } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"fade\",\n      component: Fade,\n      props: {\n        file: \"src/routes/transitions/fade.svelte\"\n      }\n    },\n    {\n      path: \"slide\",\n      component: Slide,\n      props: {\n        file: \"src/routes/transitions/slide.svelte\"\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/transitions/transitions.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/transitions\"\n  {route}\n  end={true}\n  title={{\n    content:\n      \"Demo to show how to use transitions with the router (spoiler: they're applied at the content level rather than within the router itself).\"\n  }}\n  links={[\n    {\n      href: \"/transitions\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/transitions/fade\",\n      label: \"/transitions/fade\"\n    },\n    {\n      href: \"/transitions/slide\",\n      label: \"/transitions/slide\"\n    }\n  ]}>\n  <Router\n    id=\"transitions-router\"\n    basePath=\"/transitions\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/delayed.svelte\n---\n\n<div class=\"bg-indigo-400 p-10\">\n  <h1>\n    a delayed route component, the text for \"Navigating\" will be \"Navigating: busy\" while this component is loading\n  </h1>\n</div>\n\n\n\n---\nFile: /demo/src/routes/home.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { RouterInstance, type Route, type RouteResult } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { Github, MessageCircleQuestion, Newspaper } from \"lucide-svelte\";\n\n  let { route }: { route: RouteResult } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/welcome\" or \"/home/welcome\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"/\",\n      component: welcome\n    },\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/with-query-params\" or \"/home/with-query-params\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"with-query-params\",\n      component: displayRouteProps\n    }\n  ];\n</script>\n\n{#snippet welcome()}\n  <div class=\"flex flex-col gap-5 rounded-md border-2 border-gray-800 bg-gray-800/50 p-4 text-sm text-slate-300\">\n    <h1 class=\"text-xl font-bold\">Single Page Application Router (SPAR) for Svelte 5+</h1>\n    <p>\n      <InlineCode\n        text=\"@mateothegreat/svelte5-router\"\n        class=\"bg-black text-blue-500\" /> is an SPA router for Svelte that allows you to divide & conquer your app with nested\n      routers, snippets, and more.\n    </p>\n    <div class=\"flex gap-3\">\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-violet-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Newspaper class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/readme.md\"\n          class=\"\">\n          Documentation\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Github class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router\"\n          class=\"\">\n          GitHub Repository\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <MessageCircleQuestion class=\"h-5 w-5\" />\n        <a href=\"https://github.com/mateothegreat/svelte5-router/issues\">GitHub Issues</a>\n      </div>\n    </div>\n    <div class=\"flex flex-col gap-2\">\n      <h2 class=\"text-lg font-semibold text-indigo-400\">Features</h2>\n      <ul class=\"list-disc space-y-1 pl-6 text-slate-300\">\n        <li>Built for Svelte 5 🚀!</li>\n        <li>Divide & conquer - use nested routers all over the place</li>\n        <li class=\"text-teal-400\">Use components, snippets, or both 🔥!</li>\n        <li>Use regex paths (e.g. /foo/(.*?)/bar) and/or named parameters together</li>\n        <li>Use async routes simply with component: async () => import(\"./my-component.svelte\")</li>\n        <li class=\"font-bold\">Add hooks to your routes to control the navigation flow 🔧</li>\n        <li>Automagic styling of your anchor tags 💄</li>\n        <li>Helper methods 🛠️ to make your life easier</li>\n        <li>Debugging tools included 🔍</li>\n      </ul>\n    </div>\n    <div class=\"flex items-center\">\n      Get started now with\n      <InlineCode\n        text=\"npm install @mateothegreat/svelte5-router\"\n        class=\"mx-1 bg-black\" />\n      and check out the\n      <a\n        class=\"mx-1 cursor-pointer text-violet-400 hover:text-green-500 hover:underline\"\n        href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md\">\n        getting started guide..\n      </a>\n    </div>\n  </div>\n{/snippet}\n\n{#snippet displayRouteProps()}\n  <div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n    <div class=\"flex flex-col gap-5\">\n      <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n        match path\n        <InlineCode text={route.route.path.toString()} />\n        to\n        <InlineCode text={`${route.route.path}?someQueryParam=123`} />\n      </div>\n      <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n        <div class=\" gap-1\">\n          This demo shows how to use the route's\n          <InlineCode text=\"querystring\" />\n          configuration option to match against the current\n          <InlineCode text=\"location.search\" />\n          value passed in by the browser.\n        </div>\n        <Badge\n          variant=\"success\"\n          class=\"w-fit\">\n          Because we did not specify any <InlineCode text=\"querystring\" /> parameters, the route render regardless of the\n          <InlineCode text=\"querystring\" /> and will be passed to the component as shown below.\n        </Badge>\n      </div>\n    </div>\n    <Code\n      title={\"{#snippet displayRouteProps()}\"}\n      file=\"src/routes/home.svelte\">\n      {JSON.stringify(route.result, null, 2)}\n    </Code>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"home-router\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/home.svelte\",\n    content:\n      \"This route is a child of the main app router where you are redirected to /home/welcome when landing on /home using a `pre` hook.\"\n  }}\n  links={[\n    {\n      href: \"/home\",\n      label: \"/home\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/home/with-query-params?someQueryParam=123\",\n      label: \"/home/with-query-params?someQueryParam=123\"\n    }\n  ]}>\n  <Router\n    id=\"home-router\"\n    basePath=\"/home\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/not-found.svelte\n---\n\n<script lang=\"ts\">\n  let { route } = $props();\n  $inspect(route);\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n\n\n\n---\nFile: /demo/src/app.css\n---\n\n@import \"tailwindcss\";\n@config '../tailwind.config.ts';\n\n/*\n  The default border color has changed to `currentColor` in Tailwind CSS v4,\n  so we've added these compatibility styles to make sure everything still\n  looks the same as it did with Tailwind CSS v3.\n\n  If we ever want to remove these styles, we need to add an explicit border\n  color utility to any element that depends on these defaults.\n*/\n@layer base {\n  *,\n  ::after,\n  ::before,\n  ::backdrop,\n  ::file-selector-button {\n    border-color: var(--color-gray-200, currentColor);\n  }\n}\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --primary: 222.2 47.4% 11.2%;\n    --primary-foreground: 210 40% 98%;\n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n    --destructive: 0 72.2% 50.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 222.2 84% 4.9%;\n    --radius: 0.5rem;\n    --sidebar-background: 0 0% 98%;\n    --sidebar-foreground: 240 5.3% 26.1%;\n    --sidebar-primary: 240 5.9% 10%;\n    --sidebar-primary-foreground: 0 0% 98%;\n    --sidebar-accent: 240 4.8% 95.9%;\n    --sidebar-accent-foreground: 240 5.9% 10%;\n    --sidebar-border: 220 13% 91%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n\n  .dark {\n    --background: 0 0% 0%;\n    --foreground: 210 40% 98%;\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --primary: 210 40% 98%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 212.7 26.8% 83.9%;\n    --sidebar-background: 240 5.9% 10%;\n    --sidebar-foreground: 240 4.8% 95.9%;\n    --sidebar-primary: 224.3 76.3% 48%;\n    --sidebar-primary-foreground: 0 0% 100%;\n    --sidebar-accent: 240 3.7% 15.9%;\n    --sidebar-accent-foreground: 240 4.8% 95.9%;\n    --sidebar-border: 240 3.7% 15.9%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n\n\n---\nFile: /demo/src/app.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import Extras from \"$routes/extras/extras.svelte\";\n  import Home from \"$routes/home.svelte\";\n  import Nested from \"$routes/nested/nested.svelte\";\n  import PathsAndParams from \"$routes/paths-and-params/paths-and-params.svelte\";\n  import Patterns from \"$routes/patterns/patterns.svelte\";\n  import Protected from \"$routes/protected/main.svelte\";\n  import Transitions from \"$routes/transitions/transitions.svelte\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router\";\n  import { goto, logging, registry, type Route, Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { BookHeart, Github, HelpCircle, MousePointerClick } from \"lucide-svelte\";\n\n  /**\n   * Only needed for the demo environment development.\n   *\n   * It is not needed for including the router package in your project.\n   */\n  if (import.meta.hot) {\n    import.meta.hot.accept(() => {\n      import.meta.hot!.invalidate();\n    });\n  }\n\n  /**\n   * This is a state variable that will hold the router instance.\n   *\n   * It can be used to access the current route, navigate, etc:\n   */\n  let router: RouterInstance = $state();\n\n  /**\n   * Get notified when the current route changes:\n   */\n  const route = $derived(router.current);\n  $effect(() => {\n    if (router.current) {\n      logging.info(\n        `🚀 I'm an $effect in app.svelte and i'm running because the current route is now ${router.current.result.path.original}!`\n      );\n    }\n  });\n\n  /**\n   * Let's declare our routes for the main app router:\n   */\n  const routes: RouteConfig[] = [\n    {\n      // You can name your routes anything you want for tracking or debugging:\n      name: \"default-route\",\n      hooks: {\n        pre: async (route: RouteResult) => {\n          console.error(`redirecting to ${session.mode === \"hash\" ? \"/#\" : \"\"}/home using a pre hook!`, route);\n          goto(`${session.mode === \"hash\" ? \"/#\" : \"\"}/home`);\n        },\n        post: async (route: RouteResult) => {\n          console.error(`post hook fired for route`, route);\n        }\n      }\n    },\n    {\n      path: \"/patterns\",\n      component: Patterns\n    },\n    {\n      // Here we use a regex to match the home route.\n      // This is useful if you want to match a route that has a dynamic path.\n      // The \"?:\" is used to group the regex without capturing the match:\n      // path: /^\\/($|home)$/,\n      path: \"home\",\n      component: Home,\n      // Use hooks to perform actions before and after the route is resolved:\n      hooks: {\n        pre: async (route: Route): Promise<boolean> => {\n          // console.log(\"pre hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // Hooks can also be an array of functions (async too):\n        post: [\n          // This is a post hook that will be executed after the route is resolved:\n          (route: Route): boolean => {\n            // console.log(\"post hook #1 fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          },\n          // This is an async post hook that will be executed after the route is resolved:\n          async (route: Route): Promise<boolean> => {\n            // console.log(\"post hook #2 (async) fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          }\n        ]\n      }\n    },\n    {\n      path: \"nested\",\n      component: Nested\n    },\n    {\n      path: \"paths-and-params\",\n      component: PathsAndParams\n    },\n    {\n      path: \"protected\",\n      component: Protected\n    },\n    {\n      path: \"transitions\",\n      component: Transitions\n    },\n    {\n      path: \"extras\",\n      component: Extras\n    }\n  ];\n\n  // This is a global pre hook that can be applied to all routes.\n  // Here you could check if the user is logged in or perform some other\n  // authentication checks:\n  const globalAuthGuardHook = async (route: Route): Promise<boolean> => {\n    console.warn(\"globalAuthGuardHook\", route);\n    // Return true so that the route can continue down its evaluation path.\n    return true;\n  };\n</script>\n\n<div class=\"flex h-screen flex-col gap-4 bg-zinc-700/25 p-4\">\n  <div class=\"flex items-start\">\n    <div class=\"flex flex-col gap-4\">\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <Github class=\"h-6 w-6\" />\n      </a>\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <BookHeart class=\"h-6 w-6 text-fuchsia-500\" />\n      </a>\n    </div>\n    <div class=\"logo h-51 w-60\">\n      <img\n        src=\"https://github.com/mateothegreat/svelte5-router/raw/dev/docs/assets/logo.png\"\n        alt=\"logo\" />\n    </div>\n    <div class=\"m-4 flex-1 flex justify-end text-indigo-400 gap-2 text-xs\">\n      <div class=\"text-slate-500 text-sm mb-3.5 rounded-md border-2 bg-black px-2 py-1.5\">\n        demo version: <a\n          href=\"https://github.com/mateothegreat/svelte5-router/tree/{window.__SVELTE5_ROUTER_VERSION__}\"\n          class=\"text-emerald-500 hover:text-blue-400 cursor-pointer\"\n          target=\"_blank\">\n          {window.__SVELTE5_ROUTER_VERSION__}\n        </a>\n      </div>\n    </div>\n  </div>\n  <div class=\"flex-1 gap-4 flex flex-col\">\n    <div\n      class=\"text-slate-500 justify-end items-center text-sm flex gap-2 rounded-md border-2 border-slate-700/75 bg-black/30 px-2 py-1.5\">\n      <MousePointerClick class=\"h-4 w-4 text-slate-500\" />\n      change url mode:\n      <button\n        class=\"flex items-center gap-1 rounded-md border-2 border-purple-600 bg-slate-900/50 font-semibold px-3 py-0.5 cursor-pointer hover:bg-slate-800 hover:border-green-600\"\n        class:text-orange-400={session.mode === \"hash\"}\n        class:text-green-400={session.mode === \"path\"}\n        onclick={() => {\n          session.mode = session.mode === \"hash\" ? \"path\" : \"hash\";\n        }}>\n        {session.mode === \"hash\" ? \"path\" : \"hash\"}\n      </button>\n    </div>\n    <RouteWrapper\n      name=\"main app router\"\n      title={{\n        file: \"src/app.svelte\",\n        content:\n          \"This is the main app component, it contains the top level router and then uses nested routers to divide and conquer your complex routing requirements! 🚀\"\n      }}\n      {router}\n      {route}\n      links={[\n        {\n          href: \"/\",\n          label: \"/\",\n          options: {\n            active: {\n              absolute: true\n            }\n          }\n        },\n        {\n          href: \"/home\",\n          label: \"/home\"\n        },\n        {\n          href: \"/patterns\",\n          label: \"/patterns\"\n        },\n        {\n          href: \"/protected\",\n          label: \"/protected\"\n        },\n        {\n          href: \"/paths-and-params\",\n          label: \"/paths-and-params\"\n        },\n        {\n          href: \"/nested\",\n          label: \"/nested\"\n        },\n        {\n          href: \"/transitions\",\n          label: \"/transitions\"\n        },\n        {\n          href: \"/404\",\n          label: \"/404\"\n        },\n        {\n          href: \"/extras\",\n          label: \"/extras\"\n        }\n      ]}>\n      <div class=\"flex-1\">\n        <Router\n          id=\"my-main-router\"\n          bind:instance={router}\n          {routes}\n          {...myDefaultRouterConfig} />\n      </div>\n    </RouteWrapper>\n  </div>\n</div>\n<div\n  class=\"fixed bottom-0 right-10 overflow-hidden rounded-t-md border-2 border-b-0 bg-neutral-950 text-xs text-gray-400\">\n  <p class=\"flex items-center gap-1.5 bg-black/80 p-2.5 text-sm font-medium text-slate-400\">\n    <a\n      href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md\"\n      class=\"text-yellow-300/70 hover:text-pink-500\"\n      target=\"_blank\">\n      <HelpCircle class=\"h-5 w-5\" />\n    </a>\n    router registry\n  </p>\n  <table class=\"divide-y divide-gray-900 overflow-hidden rounded-md border-2 text-xs text-gray-400\">\n    <thead>\n      <tr class=\"text-center tracking-wider text-slate-500\">\n        <th class=\"px-3 py-2 font-medium\">Router Name</th>\n        <th class=\"px-3 py-2 font-medium\">Routes</th>\n        <th class=\"px-3 py-2 font-medium\">State</th>\n        <th class=\"px-3 py-2 font-medium\">Current Path</th>\n      </tr>\n    </thead>\n    <tbody class=\"divide-y divide-gray-800 font-mono\">\n      {#each registry.instances.entries() as [key, instance]}\n        <tr>\n          <td class=\"px-3 py-2 text-left text-indigo-400\">\n            {key}\n          </td>\n          <td class=\"px-3 py-2 text-pink-500\">\n            {instance.routes.size}\n          </td>\n          <td class=\"px-3 py-2\">\n            {#if instance.navigating}\n              <span class=\"text-green-500\">busy</span>\n            {:else}\n              <span class=\"text-gray-500\">idle</span>\n            {/if}\n          </td>\n          <td class=\"px-3 py-2 text-green-500\">\n            {instance.current?.path || \"<default>\"}\n          </td>\n        </tr>\n      {/each}\n    </tbody>\n  </table>\n</div>\n\n\n\n---\nFile: /demo/src/main.ts\n---\n\nimport { mount } from \"svelte\";\n\nimport './app.css';\nimport App from './app.svelte';\n\nconst app = mount(App, {\n  target: document.getElementById('app')!,\n})\n\nexport default app\n\n\n\n---\nFile: /demo/src/vite-env.d.ts\n---\n\n/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n\nimport type { CompilerConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  SPA_ROUTER: CompilerConfig;\n}\n\n\n\n---\nFile: /demo/cypress.config.ts\n---\n\nimport { defineConfig } from \"cypress\";\n\nexport default defineConfig({\n  component: {\n    devServer: {\n      framework: \"svelte\",\n      bundler: \"vite\",\n      viteConfig: {\n        server: {\n          port: 8173,\n        },\n      },\n    },\n  },\n  e2e: {\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n\n\n\n---\nFile: /demo/index.html\n---\n\n<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Vite + Svelte + TS</title>\n\t</head>\n\t<body class=\"dark\">\n\t\t<div id=\"app\"></div>\n\t\t<script type=\"module\" src=\"/src/main.ts\"></script>\n\t</body>\n</html>\n\n\n\n---\nFile: /demo/svelte.config.ts\n---\n\nimport { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess(),\n  vitePlugin: {\n    inspector: {\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"top-right\"\n    }\n  }\n};\n\n\n\n---\nFile: /demo/tailwind.config.ts\n---\n\n\n/** @type {import('tailwindcss').Config} */\nconst config = {\n  darkMode: [\"class\"],\n  content: [\"./src/**/*.{html,js,svelte,ts}\", \"../../src/**/*.{html,js,svelte,ts}\"],\n  safelist: [\"dark\"],\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\"\n      }\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border) / <alpha-value>)\",\n        input: \"hsl(var(--input) / <alpha-value>)\",\n        ring: \"hsl(var(--ring) / <alpha-value>)\",\n        background: \"hsl(var(--background) / <alpha-value>)\",\n        foreground: \"hsl(var(--foreground) / <alpha-value>)\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary) / <alpha-value>)\",\n          foreground: \"hsl(var(--primary-foreground) / <alpha-value>)\"\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary) / <alpha-value>)\",\n          foreground: \"hsl(var(--secondary-foreground) / <alpha-value>)\"\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive) / <alpha-value>)\",\n          foreground: \"hsl(var(--destructive-foreground) / <alpha-value>)\"\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted) / <alpha-value>)\",\n          foreground: \"hsl(var(--muted-foreground) / <alpha-value>)\"\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent) / <alpha-value>)\",\n          foreground: \"hsl(var(--accent-foreground) / <alpha-value>)\"\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover) / <alpha-value>)\",\n          foreground: \"hsl(var(--popover-foreground) / <alpha-value>)\"\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card) / <alpha-value>)\",\n          foreground: \"hsl(var(--card-foreground) / <alpha-value>)\"\n        }\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\"\n      },\n      fontFamily: {\n        sans: [\"Inter\"]\n      }\n    }\n  }\n};\nexport default config;\n\n\n\n---\nFile: /demo/vite.config.ts\n---\n\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-inspector\";\n\nimport tailwindcss from \"@tailwindcss/vite\";\n\nimport path from \"path\";\n\nimport { defineConfig } from \"vite\";\n\nimport { vitePluginVersionMark } from \"vite-plugin-version-mark\";\n\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte(),\n    svelteInspector({\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"bottom-left\"\n    }),\n    tailwindcss(),\n    vitePluginVersionMark({\n      // name: 'test-app',\n      // version: '0.0.1',\n      // command: 'git describe --tags',\n      name: \"svelte5-router\",\n      ifGitSHA: true,\n      ifShortSHA: true,\n      ifMeta: true,\n      ifLog: true,\n      ifGlobal: true\n    })\n  ],\n  build: {\n    sourcemap: true\n  },\n  define: {\n    /** @type {import('@mateothegreat/svelte5-router').runtime.Config} */\n    /**\n     * The (optional) router package configuration for the compiler.\n     */\n    \"import.meta.env.SPA_ROUTER\": {\n      /**\n       * If enabled, tracing will be enabled providing rich tracing capabilities.\n       */\n      tracing: {\n        level: 3,\n        enabled: true,\n        console: true\n      },\n      /**\n       * The logging configuration for the router.\n       */\n      logging: {\n        /**\n         * The logging level that is applied.\n         */\n        level: 4,\n        /**\n         * Whether to log the trace to the browser console (optional).\n         */\n        console: false,\n        /**\n         * This method is called when a new trace is created (optional).\n         *\n         * You could use this to send the trace to a remote server, or store it\n         * in a local database.\n         *\n         * This example uses a promise in the event that you are needing to\n         * use async functionality.\n         */\n        sink: async (trace: any) => {\n          await new Promise((resolve) => {\n            console.log(trace);\n            resolve(void 0);\n          });\n        }\n      }\n    }\n  },\n  resolve: {\n    /**\n     * This is only needed for the demo environment.\n     *\n     * It is not needed for including the router package in your project.\n     */\n    alias: {\n      $lib: path.resolve(__dirname, \"./src/lib\"),\n      $routes: path.resolve(__dirname, \"./src/routes\"),\n      \"@mateothegreat/svelte5-router\": path.resolve(__dirname, \"../src/lib\")\n    }\n  }\n});\n\n\n\n---\nFile: /docs/actions.md\n---\n\n# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These actions are designed to be used with anchor (`<a>`) elements to handle navigation and manage active states.\n\n## Available Actions\n\n| Action | Description |\n|--------|-------------|\n| [`route`](#route) | Manages both navigation and active states of links. |\n| [`active`](#active) | Handles active state management for styling links. |  \n\n## Examples\n\n### Basic Navigation Link\n\n```svelte\n<a href=\"/home\" use:route>Home</a>\n```\n\n### Active State with Multiple Classes\n\n```svelte\n<a \n  href=\"/profile\" \n  use:route={{\n    default: { class: ['text-gray-600', 'hover:text-gray-900'] },\n    active: { class: ['text-blue-600', 'font-bold'] }\n  }}\n>\n  Profile\n</a>\n```\n\n### Exact Path Matching\n\n```svelte\n<a \n  href=\"/settings\" \n  use:route={{\n    active: {\n      class: 'active-link',\n      absolute: true // Only active when path exactly matches /settings\n    }\n  }}\n>\n  Settings\n</a>\n```\n\n### Query String Sensitive Navigation\n\n```svelte\n<a \n  href=\"/search?type=users\" \n  use:route={{\n    active: {\n      class: 'active-search',\n      querystring: true // Only active when querystring matches exactly\n    }\n  }}\n>\n  User Search\n</a>\n```\n\n### Notes\n\n- The `route` action automatically prevents default link behavior and handles navigation through the History API.\n- When using `active`, you'll need to handle navigation separately if needed.\n- Classes are applied dynamically based on the current route state.\n- The `absolute` option is useful for preventing parent routes from being marked as active when child routes are active.\n- The `querystring` option allows for precise matching including query parameters.\n\n---\n\n## `route`\n\nThe `route` action is the primary action for handling routing in your application. It manages both navigation and active states of links.\n\n```svelte\n<a href=\"/dashboard\" use:route>Dashboard</a>\n```\n\nThe `route` action accepts an options object with the following configuration:\n\n```typescript\n{\n  default?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  loading?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  disabled?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\n- `default`: Options applied when the route is inactive.\n- `active`: Options applied when the route is active.\n- `loading`: Options applied when the route is loading.\n- `disabled`: Options applied when the route is disabled.\n\nEach state accepts the following properties:\n\n- `absolute`: When `true`, effects only apply on exact path matches.\n- `querystring`: When `true`, effects only apply when querystring exactly matches.\n- `class`: CSS class(es) to apply when the state is active.\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:route={{\n    default: { class: 'text-gray-600' },\n    active: { \n      class: 'text-blue-600 font-bold',\n      absolute: true \n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n## `active`\n\nThe `active` action is a simplified version of `route` that only handles active state management without handling navigation events. This is useful when you want to style links based on the current route but handle navigation differently.\n\n```svelte\n<a href=\"/dashboard\" use:active>Dashboard</a>\n```\n\nThe `active` action accepts a subset of the route options, focusing only on the active state configuration:\n\n```typescript\n{\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:active={{\n    active: {\n      class: ['text-blue-600', 'font-bold'],\n      absolute: true,\n      querystring: true\n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n\n\n---\nFile: /docs/changelog.md\n---\n\n## [2.15.2] - 2025-05-26\n\n### 🐛 Bug Fixes\n\n- Support list of defaults for route; fixes #76\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.1] - 2025-05-25\n\n### 🐛 Bug Fixes\n\n- Route result serialization to string; closes #73\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.0] - 2025-05-24\n\n### 🚀 Features\n\n- Additional props support @ <Router/> -> [child], closes #70\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.14.1] - 2025-03-27\n\n### 🐛 Bug Fixes\n\n- Hooks return to false; closes #63\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- *(docs)* Better explain named params\n\n<!-- generated by git-cliff -->\n## [2.14.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.13.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.12.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.11.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.10.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.9.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.8.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.7.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.1] - 2025-03-05\n\n### ⚙️ Miscellaneous Tasks\n\n- Vercel cicd\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- *(demo)* Show random querystring usage\n- *(demo)* Show random querystring usage\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Clean up docs\n- Clean up docs\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n\n<!-- generated by git-cliff -->\n## [2.5.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.4.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.34] - 2025-03-05\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Better regexp instance handling\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.33] - 2025-03-04\n\n### 🐛 Bug Fixes\n\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.32] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.31] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.30] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.29] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.28] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.27] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.26] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.25] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.24] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.23] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.22] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.21] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.20] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.19] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.18] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.17] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.16] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.15] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.14] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.13] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.12] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.11] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.10] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.9] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.8] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.7] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.6] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.5] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.4] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.3] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.2] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.1] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.0] - 2025-03-04\n\n### 🚀 Features\n\n- Query param matchers; fix: same route, diff params #54\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.7] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.6] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.5] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.4] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.3] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.2] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.1] - 2025-02-28\n\n### 📚 Documentation\n\n- Update statuses doc\n- Update statuses doc\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.0] - 2025-02-28\n\n### 🚀 Features\n\n- Better status mapping; added callable status code handlers\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.1] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Options handling for <a> tags proper\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.0] - 2025-02-28\n\n### 🚀 Features\n\n- /protected demo\n- /protected demo\n- /protected demo\n- /protected demo\n\n### 💼 Other\n\n- Refactor\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.57] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.56] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.55] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.54] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.53] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.52] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.51] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.50] - 2025-02-27\n\n### 📚 Documentation\n\n- Add transitions demo\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.49] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.48] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.47] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.46] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.45] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.44] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.43] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.42] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.41] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.40] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n\n\n\n---\nFile: /docs/debugging.md\n---\n\n# Debugging\n\nThere are a few way to debug things.\n\n## Debug Logger\n\nIn your `vite.config.ts` file, you can add the following:\n\n```ts\nexport default defineConfig({\n  plugins: [svelte()],\n  build: {\n    sourcemap: true // If you want to use a debugger, add this!\n  },\n  define: {\n    // Tell the router to log when we're in debug mode.\n    // Otherwise, this statement is removed by the compiler (known as tree-shaking)\n    // and all subsequent log statements are removed at build time:\n    'import.meta.env.SPA_ROUTER': {\n      logLevel: \"debug\"\n    },\n  }\n});\n```\n\nThis allows us to log when we're in debug mode otherwise\nstatements like this are removed by the compiler (known astree-shaking):\n\n```ts\nif (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n  log.debug(this.config.id, \"unregistered router instance\", {\n    id: this.config.id,\n    routes: this.routes.size\n  });\n}\n```\n\nPutting it all together:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n\n  let instance: RouterInstance;\n\n  if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n    log.debug(instance.id, \"dumping routes\", {\n      config: instance.config,\n      routes: instance.routes,\n      current: instance.current,\n      navigating: instance.navigating\n    });\n  }\n</script>\n\n<Router bind:instance {routes}>\n```\n\nExample output:\n\n![debug](./assets/debugging-logger.png)\n\n\n\n---\nFile: /docs/getting-started.md\n---\n\n# Getting Started\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Usage\n\nIn your `app.svelte` file, you can use the `Router` component to render your routes:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n\n  const routes: RouteConfig[] = [\n    {\n      component: Home\n    }\n    {\n      path: \"products\",\n      component: Products\n    },\n    {\n      path: \"settings\",\n      component: Settings\n    }\n  ];\n</script>\n\n<Router {routes} />\n```\n\nWhen you navigate to the root route, the `Home` component will be rendered.\n\nWhen you navigate to the `/products` route, the `Products` component will be rendered.\n\nWhen you navigate to the `/settings` route, the `Settings` component will be rendered.\n\n\n\n---\nFile: /docs/helpers.md\n---\n\n# Helpers\n\nThere are a few helpers that are available to you when using the router.\n\n## `goto(path: string, queryParams?: Record<string, string>)`\n\nNavigates to the given path by calling `goto(\"/path\")`.\n\nExample:\n\n```ts\ngoto(\"/foo\", { bar: \"baz\" });\n```\n\nThis will navigate to `/foo?bar=baz`.\n\n## `query(key: string): string | null`\n\nReturns the value of the query parameter for the given key or null if the key does not exist.\n\n## The `QueryString` class\n\nA helper class for working with the query string.\n\n> Check it out at [src/lib/query.svelte.ts](../src/lib/query.svelte.ts).\n> or import it with:\n>\n> ```ts\n> import { QueryString } from \"@mateothegreat/svelte5-router\";\n> ```\n>\n> and start using it now!\n\nBasic usage:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\nquery.get(\"foo\", \"bar\"); // \"bar\"\nquery.set(\"baz\", \"qux\");\nquery.toString();        // \"foo=bar&baz=qux\"\n```\n\nUsing it with navigation:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\n// ...\nquery.set(\"foo\", \"baz\");\nquery.set(\"baz\", \"qux\");\n// ...\n\nquery.goto(\"/test\"); // Navigates to \"/test?foo=baz&baz=qux\"\n```\n\nYou can also pass a query object to the `goto` method:\n\n```ts\ngoto(\"/test\", { foo: \"baz\" }); // Navigates to \"/test?foo=baz\"\n```\n\n\n\n---\nFile: /docs/hooks.md\n---\n\n# Routing Hooks\n\n | Order | Event  | Scope       | Description                                |\n | ----- | ------ | ----------- | ------------------------------------------ |\n | 1.    | `pre`  | `<Router/>` | Always runs *before* a route is attempted. |\n | 2.    | `pre`  | `Route`     | Runs *before* the route is rendered.       |\n | 3.    | `post` | `Route`     | Runs *after* the route is rendered.        |\n | 4.    | `post` | `<Router/>` | Always runs *after* a route is rendered.   |\n\n```ts\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nexport const authGuard = async (route: RouteResult): Promise<boolean> => {\n  console.log(\"simulating some login/auth check...\");\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  if (!localStorage.getItem(\"token\")) {\n    console.warn(\"redirecting to denied\");\n    goto(\"/protected/denied\");\n    return false;\n  }\n  return true;\n}\n\nconst globalPostHook1 = (route: RouteResult): boolean => {\n  console.warn(\"globalPostHook1\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n\nconst globalPostHook2 = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalPostHook2\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\nYou can pass an array or single method for the `pre` and `post` hooks and you can\nalso mix and match asynchronous and synchronous hooks.\n\n```svelte\n<Router\n  {routes}\n  hooks={{\n    pre: authGuard,\n    post: [\n      globalPostHook1,\n      globalPostHook2\n    ]\n  }}\n/>\n```\n\n\n\n---\nFile: /docs/props.md\n---\n\n# Passing Props\n\nYou can pass props to a route by using the `props` property on any route.\n\nThese props will be passed to the component via `$props()`:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: /\\/(?<child>.*)/,\n    component: DisplayParams,\n    props: {\n      randomId: Math.random().toString(36).substring(2, 15),\n      someUserStuff: {\n        username: \"mateothegreat\",\n        userAgent: navigator.userAgent\n      }\n    }\n  }\n];\n```\n\nThen, in your component, you can access the prop like this:\n\n```svelte\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<pre>{JSON.stringify(route, null, 2)}</pre>\n```\n\nWhen you navigate to `/props/bar?someQueryParam=123`, the output will be:\n\n```json\n{\n  \"route\": {\n    \"params\": {\n      \"child\": \"bar\"\n    },\n    \"props\": {\n      \"randomId\": \"y3pbfi1mgmg\",\n      \"someUserStuff\": {\n        \"username\": \"mateothegreat\",\n        \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\"\n      }\n    },\n    \"query\": {\n      \"someQueryParam\": \"123\"\n    },\n    \"name\": \"props-fancy-regex\",\n    \"path\": {\n      \"before\": \"/\\\\/(?<child>.*)/\",\n      \"after\": \"/props/bar\"\n    }\n  }\n}\n```\n\n\n\n---\nFile: /docs/readme.md\n---\n\n# Svelte 5 SPA Router 🚀 🔥\n\n![logo](https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/logo-1000px.png)\n\n<img src=\"https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/coverage.svg?sanitize=true\" />\n\nAn SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.\n\n> [!NOTE]\n> Live demo: <https://demo.router.svelte.spa>\n>\n> API documentation: <https://docs.router.svelte.spa>\n\n## Features\n\n- Built for Svelte 5 🚀!\n- Divide & conquer - use nested routers all over the place.\n- Use components, snippets, or both 🔥!\n- Use regex paths (e.g. `/foo/(.*?)/bar`) and/or named parameters together.\n- Use async routes simply with `component: async () => import(\"./my-component.svelte\")`.\n- Add hooks to your routes to control the navigation flow 🔧.\n- Automagic styling of your anchor tags 💄.\n- Helper methods 🛠️ to make your life easier.\n- Debugging tools included 🔍.\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Table of Contents\n\n- [Getting Started](https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md)\n- [Routing](https://github.com/mateothegreat/svelte5-router/blob/main/docs/routing.md)\n- [Hooks](https://github.com/mateothegreat/svelte5-router/blob/main/docs/hooks.md)\n- [Actions](https://github.com/mateothegreat/svelte5-router/blob/main/docs/actions.md)\n- [Helper Methods](https://github.com/mateothegreat/svelte5-router/blob/main/docs/helpers.md)\n- [Default Status Mapping](https://github.com/mateothegreat/svelte5-router/blob/main/docs/statuses.md)\n- [The Router Registry](https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md)\n- [Route Styling](https://github.com/mateothegreat/svelte5-router/blob/main/docs/styling.md)\n- [Accessing Props](https://github.com/mateothegreat/svelte5-router/blob/main/docs/props.md)\n- [Debugging](https://github.com/mateothegreat/svelte5-router/blob/main/docs/debugging.md)\n\n\n\n---\nFile: /docs/registry.md\n---\n\n# Router Registry\n\nThe router [registry](../src/lib/registry.svelte.ts) is a global object that is used to store route\ninstances and their associated routing configuration.\n\nThis registry updates as you navigate through your application and as `<Router />` components\nare mounted and unmounted dynamically.\n\n![registry](./assets/registry.png)\n\n## Usage\n\nThough the registry is managed internally, though you can access it to debug your application.\n\nWhen `<Router />` is mounted, it will register itself in the registry.\n\nWhen `<Router />` is unmounted, it will unregister itself from the registry.\n\nYou can access the __global__ registry to debug your application by adding the following to your application:\n\n```svelte\n<script lang=\"ts\">\n  import { registry } from \"@mateothegreat/svelte5-router\";\n</script>\n\n<div>\n  {#each registry.instances.entries() as [id, instance]}\n    <div>\n      <pre>id: {id}</pre>\n      <pre>routes: {instance.routes.size}</pre>\n      <pre>current: {instance.current?.path || \"<default>\"}</pre>\n      <pre>navigating: {instance.navigating ? \"yes\" : \"no\"}</pre>\n    </div>\n  {/each}\n</div>\n```\n\nOr, you can access the __local__ registry directly from your `<Router />` component:\n\n```svelte\n<script lang=\"ts\">\n  import type { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Router } from \"@mateothegreat/svelte5-router\";\n\n  let instance = $state<RouterInstance>();\n\n  $inspect(instance); // Outputs the registry instance to the console.\n</script>\n\n<Router bind:instance {routes} />\n```\n\n\n\n---\nFile: /docs/routing-patterns.md\n---\n\n# Routing Patterns\n\nAs your application grows, you'll likely need to use more complex routing patterns. This guide will cover the most common patterns and how to use them.\n<p>The <abbr title=\"Hyper Text Markup Language\">HTML</abbr> specification\nis maintained by the <abbr title=\"World Wide Web Consortium\">W3C</abbr>.</p>\n\nasdf [^1]\n\n<h1 class=\"style-me\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n\n[<button>Click me</button>](https://www.google.com)\n\n[[docs/readme]]asdf\n\n## Table of Contents\n\n- [Routing Patterns](#routing-patterns)\n  - [Table of Contents](#table-of-contents)\n  - [Default Route](#default-route)\n  - [Single Path](#single-path)\n  - [Nested Paths](#nested-paths)\n  - [Parameter Extraction](#parameter-extraction)\n  - [Named Parameters](#named-parameters)\n\n## Default Route\n\nThis example demonstrates how to make a route be the default route under the following conditions:\n\nOrder of operations:\n\n1. If the path is empty, the route will be matched otherwise evaluation will continue.\n2. If no other route matches, the default route will be matched and evaluation will continue.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n> [!NOTE]\n> You can omit the `path` property to make the route the default route which is the\n> same as `path: \"/\"` and `path: \"\"`.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Single Path\n\nThis example will match any path that starts with `/path`.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Nested Paths\n\nThis example will match any path that starts with `/path/path/path` and can be nested further.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/nested>!\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Parameter Extraction\n\nCombine arbitrary paths and extractable parameters.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/paths-and-params>!\n\nOrder of operations:\n\n1. If there are arbitrary paths, the route **must** contain all of them.\n2. If there are parameters, the path **must** match the expression.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n**Unnamed Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(.*?)/path/(.*?)\",\n    component: ComponentToRender \n  }\n];\n```\n\n**Named Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Named Parameters\n\nThis example will match any path that starts with `/path/path/path/path` and can be nested further.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\na\n[^1]: This is a footnote\nb\n\n\n\n---\nFile: /docs/routing.md\n---\n\n# Routing Usage\n\nWe provide an array of `RouteConfig` objects to the `Router` component.\n\nEach `RouteConfig` object describes a route and its associated component.\n\n## Pattern Matching\n\nYou can simply use static paths like `/foo` or dynamic paths like `/foo/(.*?)` with regex.\n\nExample patterns:\n\n| Pattern                                        | Description                                             |\n| ---------------------------------------------- | ------------------------------------------------------- |\n| `/`                                            | The root path.                                          |\n| `/foo`                                         | A static path.                                          |\n| `/foo/(.*?)`                                   | A dynamic path.                                         |\n| `/cool/(.*?)/(.*?)`                            | A dynamic path with two parameters.                     |\n| `(?<param1>.*)`                                | A dynamic path with a named parameter.                  |\n| `^/users/(?<id>[a-z0-9]{25})(?:/(?<tab>.*))?$` | A dynamic path with a named parameter and optional tab. |\n\n> When using named parameters, you can access them using the `$props()` function.\n>\n> For example, if the route is `/users/123/settings`, then `$props()` will be `{ id: \"123\", tab: \"settings\" }`.\n\n## Parameter Extraction\n\nParameters that are capable of being parsed from the path are passed to the component through the `route` prop:\n\n```svelte\n<script>\n  let { route } = $props();\n</script>\n```\n\nWhen using named parameters such as `(?<id>[a-z0-9]{25})`, the parameter value will be passed through the `route` prop as an object:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/users/(?<id>[a-z0-9]{25})\",\n    component: User\n  }\n];\n```\n\nand can be accessed like this:\n\n```svelte\n<script>\n  const userId = route.result.path.params.id;\n</script>\n```\n\nFor the full shape of `RouteResult` see the [API Reference](https://github.com/mateothegreat/svelte5-router/blob/main/src/lib/route.svelte.ts#L24).\n\n## Examples\n\n### Basic\n\nThe following example demonstrates a basic route configuration with two routes:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    // Notice that we don't need to specify the path.\n    // The router will use this route as the default route when \"/\" is visited.\n    component: Home\n  },\n  {\n    path: \"/about\",\n    component: About\n  }\n];\n```\n\nPassing the routes to the `<Router />` component:\n\n```svelte\n<Router {routes} />\n```\n\n## Full Example\n\nThe following example demonstrates a more complex route configuration with multiple routes and hooks:\n\n```ts\nconst routes: RouteConfig[] = [\n  // Example of a route that redirects to the home route:\n  {\n    path: \"\",\n    hooks: {\n      pre: () => {\n        goto(\"/home\");\n      }\n    }\n  },\n  {\n    // Here we use a regex to match the home route.\n    // This is useful if you want to match a route that has a dynamic path.\n    // The \"?:\" is used to group the regex without capturing the match:\n    path: /(?:^$|home)/,\n    component: Home,\n    // Use hooks to perform actions before and after the route is resolved:\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        console.log(\"pre hook #1 fired for route\");\n        return true; // Return true to continue down the route evaluation path.\n      },\n      // Hooks can also be an array of functions (async too):\n      post: [\n        // This is a post hook that will be executed after the route is resolved:\n        (route: RouteResult): boolean => {\n          console.log(\"post hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // This is an async post hook that will be executed after the route is resolved:\n        async (route: RouteResult): Promise<boolean> => {\n          console.log(\"post hook #2 (async) fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        }\n      ]\n    }\n  },\n  {\n    path: \"nested\",\n    component: Nested\n  },\n  {\n    path: \"async\",\n    // Routes can also be async functions that return a promise.\n    // This is useful if you want to load a component asynchronously aka \"lazy loading\":\n    component: async () => import(\"./lib/async/async.svelte\")\n  },\n  {\n    path: \"delayed\",\n    component: Delayed,\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        // Simulate a network delay by returning a promise that resolves after a second:\n        return new Promise((resolve) =>\n          setTimeout(() => {\n            resolve(true);\n          }, 1000)\n        );\n      }\n    }\n  },\n  {\n    path: \"props\",\n    component: Props\n  },\n  {\n    path: \"protected\",\n    component: Protected\n  },\n  {\n    path: \"query-redirect\",\n    component: QueryRedirect\n  },\n  {\n    path: \"context\",\n    component: Context\n  }\n];\n\n// This is a global pre hook that can be applied to all routes.\n// Here you could check if the user is logged in or perform some other\n// authentication checks:\nconst globalAuthGuardHook = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalAuthGuardHook\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\n\n\n---\nFile: /docs/statuses.md\n---\n\n# Route Statuses\n\nEach router instance can have a set of statuses that are rendered when a route\nreturns a specific status code such as 404 for \"Not Found\".\n\nWhen a route returns a status code, the router will render the component or execute the function\nspecified in the `statuses` prop for that status code.\n\n## Status Codes\n\nUsing the `StatusesMapping` enum, the following status codes are to be supported:\n\n> [!NOTE]\n> Currently, the `404` status code is implemented. We will be adding the\n> other status codes in the future.\n\n| Code    | Description           | Status          |\n| ------- | --------------------- | --------------- |\n| 301     | Permanent Redirect    | Coming Soon     |\n| 302     | Temporary Redirect    | Coming Soon     |\n| 400     | Bad Request           | Coming Soon     |\n| 401     | Unauthorized          | Coming Soon     |\n| 403     | Forbidden             | Coming Soon     |\n| __404__ | __Not Found__         | __Implemented__ |\n| 500     | Internal Server Error | Coming Soon     |\n\n## `BadRouted` Object\n\nWhen passing a function to the `statuses` value, the [`RouteResult`](../src/lib/route.ts) object is passed to that function.\n\nIt contains the following properties:\n\n- `path`: The path that was attempted to be accessed\n- `status`: The status code that was returned\n\n## Usage\n\n### Basic Usage\n\nIn this example, we will use the `NotFound` component to render when the router\nreturns a 404 status code because the route `/bad` does not exist.\n\nFirst, we will create the `NotFound` component:\n\n```svelte\n<script lang=\"ts\">\n  let props = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">\n    $props():\n    {JSON.stringify(props, null, 2)}\n  </pre>\n</div>\n```\n\nNext, we will create the `Router` component and pass the `NotFound` component\nto the `statuses` prop:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, Route, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  {routes}\n  statuses={{\n    [StatusCode.NotFound]: NotFound\n  }} />\n```\n\nWhen you navigate to `/bad`, the `NotFound` component will be rendered because\nthe route `/bad` does not exist.\n\n### Custom Usage\n\nYou can also pass a function to the `statuses` prop to have more control over the rendered component. The function receives a `BadRouted` object containing information about the failed route and must return an object with the component to render and any additional props:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig, StatusCode, getStatusByValue } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  id=\"my-main-router\"\n  bind:instance\n  {routes}\n  statuses={{\n     [StatusCode.NotFound]: (route: RouteResult) => {\n      console.warn(\n        `Route \"${route.result.path.original}\" could not be found :(`,\n        {\n          statusName: getStatusByValue(route.result.status),\n          statusValue: route.result.status\n        },\n        route\n      );\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n  }} />\n```\n\nIn this example, when a 404 error occurs:\n\n1. The function logs a warning with details about the failed route\n2. Returns the `NotFound` component with an additional prop `somethingExtra` containing the current timestamp\n3. The `NotFound` component will receive both its default props and the extra props specified in the function\n\n\n\n---\nFile: /docs/styling.md\n---\n\n# Routing Styling\n\nYou can have the router apply a class to the active route by setting the `active.class` option\nwhen configuring your routes.\n\nAs the routes change, the router will apply the class to the active route while removing it from the previous active route(s).\n\n## Configuration\n\nThis property can be a string or an array of strings:\n\n> [!NOTE]\n> This is a convenience feature and is not required. You can apply the class to the active route manually in your component.\n>\n> See <https://docs.router.svelte.spa/classes/RouteOptions.html> for more information.\n\nUsing a string:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: \"bg-yellow-500\"\n  }\n};\n```\n\nUsing an array of strings:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: [\n      \"bg-yellow-500\",\n      \"underline\"\n    ]\n  }\n};\n```\n\n## Usage\n\nWith our common configuration declared we can use it in our routes:\n\nImport the common configuration:\n\n```ts\n<script lang=\"ts\">\n  import { myDefaultRouteConfig } from \"./lib/common-stuff\";\n</script>\n```\n\n### Using `use:route`\n\n```svelte\n<a\n  use:route={myDefaultRouteConfig}\n  href=\"/props\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  /props\n</a>\n```\n\n### Using `use:active`\n\nYou can also be more prescriptive and pass in the active class as an object.\n\n> [!NOTE]\n> This is functionally equivalent to using `use:route` with the same configuration.\n> It is just a convenience method for when you don't need to pass in any other options.\n\n```svelte\n<a\n  use:route\n  use:active={{ active: { class: \"bg-pink-500\" } }}\n  href=\"/baz\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  Click Me\n</a>\n```\n\nHere we used two actions:\n\n- `use:route`\n  - This is the default action and is used to apply the active class to the active route.\n- `use:active`\n  - This is a convenience action that is used to apply the active class to the active route.\n\n\n\n---\nFile: /src/lib/actions/active.svelte.ts\n---\n\nimport { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Add the `active` class to the node if the current route matches the node's href.\n *\n * > Similar to {@link route}.\n *\n * Add `use:active` to an anchor element to manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n *\n * @category Actions\n * @source\n */\nexport const active = (node: HTMLAnchorElement, options: Pick<RouteOptions, \"active\"> = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  window.addEventListener(\"pushState\", apply);\n\n  return {\n    destroy() {\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n\n\n\n---\nFile: /src/lib/actions/apply-classes.ts\n---\n\nimport { urls, type URL } from \"../helpers/urls\";\n\nimport { RouteOptions } from \"./options\";\n\n/**\n * Applies the active class to the node if the href is the same as the current location.\n *\n * @param href - The href to check if it is the same as the current location.\n * @param options - The options to apply to the node.\n * @param node - The node to apply the active class to.\n *\n * @category Actions\n */\nexport const applyActiveClass = (href: URL, options: RouteOptions, node: HTMLAnchorElement) => {\n  const url = urls.parse(location.toString());\n  if (\n    (href.path === url.path ||\n      href.path === url.hash.path ||\n      href.hash.path === url.path ||\n      (!options.active?.absolute && url.path.startsWith(href.path))) &&\n    (options.active?.querystring || options.active?.querystring === undefined) &&\n    (href.query.original == \"\" ||\n      href.query.original === location.search.replace(\"?\", \"\") ||\n      href.query.original === url.hash.query.original)\n  ) {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.add(...options.active?.class);\n    } else {\n      node.classList.add(options.active?.class);\n    }\n    if (options.default?.class) {\n      node.classList.remove(...options.default?.class);\n    }\n  } else {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.remove(...options.active?.class);\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    } else {\n      if (options.active?.class) {\n        node.classList.remove(options.active?.class);\n      }\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    }\n  }\n};\n\n\n\n---\nFile: /src/lib/actions/index.ts\n---\n\nexport * from \"./apply-classes\";\nexport * from \"./options\";\nexport * from \"./route.svelte\";\n\n\n\n---\nFile: /src/lib/actions/options.ts\n---\n\n/**\n * Options that are applied to the html element when the route is active, inactive,\n * loading, or disabled.\n *\n * @category Router\n */\nexport type RouteOptionState = {\n  /**\n   * When true, the effects will only be applied if the path is an exact match.\n   *\n   * This is useful for when you want to apply the effects to a specific route, but\n   * not when it's part of a parent route.\n   */\n  absolute?: boolean;\n\n  /**\n   * When true, the effects will only be applied if the querystring is an exact match.\n   */\n  querystring?: boolean;\n\n  /**\n   * The css class(es) to add when this state is currently active.\n   */\n  class?: string | string[];\n};\n\n/**\n * Options for the route action.\n *\n * @category Router\n */\nexport class RouteOptions {\n  /**\n   * When the route is inactive, these options are applied.\n   */\n  default?: RouteOptionState;\n\n  /**\n   * When the route is active, these options are applied.\n   */\n  active?: RouteOptionState;\n\n  /**\n   * The css class(es) to add when route is loading.\n   */\n  loading?: RouteOptionState;\n\n  /**\n   * When the route is disabled, these options are applied.\n   */\n  disabled?: RouteOptionState;\n\n  constructor(options?: Partial<RouteOptions>) {\n    if (options) {\n      Object.assign(this, options);\n    }\n  }\n}\n\n\n\n---\nFile: /src/lib/actions/route.svelte.ts\n---\n\nimport { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Svelte action to handle routing with optional active state.\n *\n * Similar to {@link active}\n *\n * Add `use:route` to an anchor element to handle routing and optionally manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n * @param {RouteOptions} options Options for the route action (optional).\n * @category Actions\n * @includeExample demo/src/app.svelte\n * @source\n */\nexport const route = (node: HTMLAnchorElement, options: RouteOptions = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  /**\n   * Handle click events on the anchor element.\n   * @param event - The click event.\n   */\n  const handleClick = (event: Event) => {\n    event.preventDefault();\n    window.history.pushState({}, \"\", node.href);\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  node.addEventListener(\"click\", handleClick);\n  window.addEventListener(\"pushState\", apply);\n  return {\n    destroy() {\n      node.removeEventListener(\"click\", handleClick);\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n\n\n\n---\nFile: /src/lib/helpers/evaluators.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { evaluators } from \"./evaluators\";\nimport { Identities } from \"./identify\";\nimport { regexp } from \"./regexp\";\n\ndescribe(\"regexp\", () => {\n  test(\"should convert ^home$ to a RegExp\", () => {\n    expect(regexp.from(\"^home$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /^home$/ to a RegExp\", () => {\n    expect(regexp.from(\"/^home$/\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert a RegExp to a RegExp\", () => {\n    expect(regexp.from(/^home$/)).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert ^/($|home)$ to a RegExp\", () => {\n    expect(regexp.from(\"^/($|home)$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /(^home$/ to a RegExp\", () => {\n    expect(() => regexp.from(\"/(^home$/\")).toThrowError();\n  });\n});\n\ndescribe(\"evaluators\", () => {\n  test(\"should return true for a non-empty object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 1 })).toBe(true);\n  });\n\n  test(\"should return false for an empty-ish object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined, b: true })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 0, b: { c: null } })).toBe(false);\n  });\n});\n\n\n\n---\nFile: /src/lib/helpers/evaluators.ts\n---\n\nimport { identify, Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\nimport type { ReturnParam } from \"./urls\";\n\n/**\n * Path or querystring evaluation result.\n *\n * @category Router\n */\nexport type Condition =\n  | \"exact-match\"\n  | \"base-match\"\n  | \"default-match\"\n  | \"no-match\"\n  | \"permitted-no-conditions\"\n  | \"one-or-more-missing\";\n\n/**\n * The conditions that are considered successful.\n *\n * @category Router\n */\nexport const SuccessfulConditions: Condition[] = [\n  \"exact-match\",\n  \"base-match\",\n  \"default-match\",\n  \"permitted-no-conditions\"\n];\n\n/**\n * The conditions that are considered failed.\n *\n * @category Router\n */\nexport const FailedConditions: Condition[] = [\"no-match\", \"one-or-more-missing\"];\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type Evaluation = {\n  condition: Condition;\n  params?: ReturnParam;\n};\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type EvaluationResult = {\n  path: Evaluation;\n  querystring: Evaluation;\n  original: ReturnParam;\n};\n\nexport namespace evaluators {\n  /**\n   * Composite evaluator function that can handle different types of values.\n   *\n   * @param a - The first value to evaluate.\n   * @param b - The second value to evaluate {a} against.\n   * @returns A boolean, string[], or object.\n   */\n  export const any: Record<\n    string,\n    (\n      a: any,\n      b: any\n    ) =>\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | string\n      | string[]\n      | { [key: string]: boolean | boolean[] | number | number[] | string | string[] }\n  > = {\n    [Identities.string]: (a, b) => a === b,\n    [Identities.number]: (a, b) => a === b,\n    [Identities.boolean]: (a, b) => a === b,\n    [Identities.promise]: (a, b) => a === b,\n    [Identities.function]: (a, b) => a === b,\n    [Identities.null]: (a, b) => a === b,\n    [Identities.undefined]: (a, b) => a === b,\n    [Identities.unknown]: (a, b) => a === b,\n    [Identities.array]: (a, b) =>\n      Array.isArray(a) &&\n      Array.isArray(b) &&\n      a.length === b.length &&\n      a.every((value, index) => any[identify(value)](value, b[index])),\n    [Identities.object]: (a, b) => {\n      if (typeof a !== \"object\" || typeof b !== \"object\") {\n        return false;\n      }\n      const aKeys = Object.keys(a);\n      const bKeys = Object.keys(b);\n      if (aKeys.length !== bKeys.length) {\n        return false;\n      }\n      return aKeys.every((key) => any[identify(a[key])](a[key], b[key]));\n    },\n    [Identities.regexp]: (a, b) => {\n      const result = (a as RegExp).exec(b);\n      if (result) {\n        if (result.groups) {\n          return marshal(result.groups).value as { [key: string]: string };\n        } else {\n          if (result.length === 1 && result[0] === result.input) {\n            return true;\n          }\n          return marshal(result.slice(1)[0]).value as string[];\n        }\n      }\n      return false;\n    }\n  };\n\n  /**\n   * Evaluator function that checks if a value is empty recursively.\n   *\n   * @category Router\n   */\n  export const valid: Record<string, (a: any) => boolean> = {\n    [Identities.string]: (a) => a.length > 0,\n    [Identities.boolean]: (a) => a === false,\n    [Identities.number]: (a) => !isNaN(a),\n    [Identities.array]: (a) => Array.isArray(a) && a.length > 0,\n    [Identities.object]: (a) => {\n      if (typeof a !== \"object\" || a === null) {\n        return true;\n      }\n      const keys = Object.keys(a);\n      if (keys.length === 0) {\n        return true;\n      }\n      const result = keys.every((key) => {\n        const value = a[key];\n        const valueType = identify(value);\n        return valid[valueType](value);\n      });\n      return result;\n    },\n    [Identities.regexp]: (a) => a instanceof RegExp,\n    [Identities.function]: (a) => typeof a === \"function\",\n    [Identities.null]: () => false,\n    [Identities.undefined]: () => false\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/goto.ts\n---\n\n/**\n * Navigate to a new path by using the browser's history API (pushState specifically).\n *\n * @param path - The path to navigate to (excluding the base URL).\n * @param queryParams - The query parameters to add to the URL.\n *\n * @category Helpers\n */\nexport const goto = (path: string, queryParams?: Record<string, unknown>): void => {\n  const url = new URL(path, window.location.origin);\n  if (queryParams) {\n    Object.entries(queryParams).forEach(([key, value]) => {\n      url.searchParams.set(key, value as string);\n    });\n  }\n  window.history.pushState({}, \"\", url.toString());\n};\n\n\n\n---\nFile: /src/lib/helpers/identify.ts\n---\n\nexport type Identity =\n  | string\n  | number\n  | boolean\n  | null\n  | undefined\n  | RegExp\n  | Function\n  | Object\n  | Array<unknown>\n  | Promise<unknown>;\n\nexport const Identities = {\n  string: \"string\",\n  number: \"number\",\n  boolean: \"boolean\",\n  null: \"null\",\n  undefined: \"undefined\",\n  regexp: \"regexp\",\n  function: \"function\",\n  object: \"object\",\n  array: \"array\",\n  promise: \"promise\",\n  unknown: \"unknown\"\n};\n\nexport const identify = (value: unknown): (typeof Identities)[keyof typeof Identities] => {\n  if (value === null) {\n    return Identities.null;\n  }\n  if (value === undefined) {\n    return Identities.undefined;\n  }\n  if (value instanceof RegExp) {\n    return Identities.regexp;\n  }\n  if (typeof value === \"string\") {\n    return Identities.string;\n  }\n  if (typeof value === \"number\") {\n    return Identities.number;\n  }\n  if (typeof value === \"boolean\") {\n    return Identities.boolean;\n  }\n  if (Array.isArray(value)) {\n    return Identities.array;\n  }\n  if (typeof value === \"function\") {\n    return Identities.function;\n  }\n  if (typeof value === \"object\") {\n    return Identities.object;\n  }\n  return Identities.unknown;\n};\n\n\n\n---\nFile: /src/lib/helpers/index.ts\n---\n\nexport * from \"./evaluators\";\nexport * from \"./goto\";\nexport * from \"./identify\";\nexport * from \"./logging\";\nexport * from \"./marshal\";\nexport * from \"./normalize\";\nexport * from \"./objects\";\nexport * from \"./query\";\nexport * from \"./regexp\";\nexport * from \"./runtime\";\nexport * from \"./tracing.svelte\";\nexport * from \"./urls\";\n\n\n\n---\nFile: /src/lib/helpers/logging.ts\n---\n\nimport { runtime } from \"./runtime\";\n\n/**\n * Logging facility.\n *\n * @category Helpers\n */\nexport namespace logging {\n  /**\n   * Acceptable log levels (applies to all logging methods).\n   */\n  export enum LogLevel {\n    FATAL = -1,\n    ERROR = 1,\n    INFO = 2,\n    DEBUG = 3,\n    TRACE = 4,\n    DISABLED = 5\n  }\n\n  /**\n   * A grouping of log messages.\n   */\n  export type Group = {\n    name: string;\n    messages: any | any[];\n  };\n\n  /**\n   * Acceptable log types typed out so that it's clearer what can be\n   * passed to the logging functions like groups of logs to combine them\n   * in the outputs.\n   */\n  export type Log = Group | Group[] | any | any[];\n\n  /**\n   * Convenience method for logging an info message.\n   */\n  export const info = (...msg: Log[]): void => {\n    log(LogLevel.INFO, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a debug message.\n   */\n  export const debug = (...msg: Log[]): void => {\n    log(LogLevel.DEBUG, ...msg);\n  };\n\n  /**\n   * Convenience method for logging an error message.\n   */\n  export const error = (...msg: any[]): void => {\n    log(LogLevel.ERROR, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a trace message.\n   */\n  export const trace = (...msg: any[]): void => {\n    log(LogLevel.TRACE, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a fatal error and finally throwing an error.\n   *\n   * @remarks\n   * This is used to stop the application from running if an error is encountered\n   * that is not recoverable.\n   */\n  export const fatal = (...msg: any[]): void => {\n    log(LogLevel.FATAL, ...msg);\n    throw new Error(\"Fatal error\");\n  };\n\n  /**\n   * Raw log method.\n   */\n  export const log = (level: LogLevel, ...msg: Log[]): void => {\n    if (level <= runtime.current.logging.level && level !== LogLevel.DISABLED) {\n      if (runtime.current.logging.console) {\n        if (msg.some((m) => m?.toConsole)) {\n          msg.forEach((m) => m?.toConsole?.(runtime.current.logging.level));\n        } else if (runtime.current.logging.console) {\n          console.log(...msg);\n        }\n      }\n      if (runtime.current.logging.sink) {\n        runtime.current.logging.sink(msg);\n      }\n    }\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/marshal.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\n\ndescribe(\"marshal\", () => {\n  test(\"should marshal an array from a string\", () => {\n    expect(marshal(\"a[0]=1\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with multiple values\", () => {\n    expect(marshal(\"a[0]=first&nonarray=true,a[999]=true&a[1]=second&a[2]=3&a[3]=fourth\")).toEqual({\n      identity: Identities.object,\n      value: {\n        nonarray: true,\n        a: [\"first\", \"second\", 3, \"fourth\", true]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with an empty value\", () => {\n    expect(marshal(\"a[0]=1&a[1]=b&a[2]=false&a[3]=true&a[4]=\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1, \"b\", false, true, \"\"]\n      }\n    });\n  });\n});\n\n\n\n---\nFile: /src/lib/helpers/marshal.ts\n---\n\nimport { Identities, type Identity } from \"./identify\";\n\nexport type MarshallableType = string | number | boolean | RegExp | Function | Promise<unknown>;\n\nexport type Marshalled<T> = {\n  identity: Identity;\n  value: T;\n};\n\n/**\n * Marshal a value to a specific type.\n *\n * @param value - The value to marshal\n *\n * @returns The marshaled value\n */\nexport const marshal = <T>(value: unknown): Marshalled<T> => {\n  // Most values will be strings, so we check for that first:\n  if (typeof value === \"string\") {\n    // Check for floats:\n    if (value.match(/^[\\d.-]+$/)) {\n      if (!Number.isNaN(Number.parseFloat(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseFloat(value) as T\n        };\n      }\n      // If the value is capable of being parsed as a number, we do that:\n      if (!Number.isNaN(Number.parseInt(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseInt(value) as T\n        };\n      } else {\n        return {\n          identity: Identities.string,\n          value: value as T\n        };\n      }\n    } else if (!value.includes(\",\") && (value.includes(\"&\") || value.includes(\"=\"))) {\n      // Handle both array notation (x[0]=1) and simple key-value pairs (a=1&b=2)\n      const pairs = value.split(/[&,]/);\n      const result: Record<string, unknown> = {};\n\n      for (const pair of pairs) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        // Remove array notation if present, otherwise use the key as is\n        const cleanKey = key.replace(/\\[\\d*\\]$/, \"\");\n        const marshalled = marshal(val);\n\n        if (key.includes(\"[\")) {\n          // Handle array case\n          if (!Array.isArray(result[cleanKey])) {\n            result[cleanKey] = [];\n          }\n          const index = key.match(/\\[(\\d+)\\]/)?.[1];\n          if (index) {\n            // Store index and value as tuple to sort later\n            if (!Array.isArray(result[cleanKey])) {\n              result[cleanKey] = [];\n            }\n            (result[cleanKey] as [number, unknown][]).push([parseInt(index), marshalled.value]);\n          } else {\n            (result[cleanKey] as unknown[]).push(marshalled.value);\n          }\n        } else {\n          // Handle simple key-value case\n          result[cleanKey] = marshalled.value;\n        }\n      }\n\n      // Transform arrays while preserving non-array values\n      for (const [key, value] of Object.entries(result)) {\n        if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {\n          // Sort by index and extract values only for array entries\n          result[key] = (value as [number, unknown][]).sort((a, b) => a[0] - b[0]).map(([, val]) => val);\n        }\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.includes(\"&\") && value.includes(\"=\")) {\n      const result: Record<string, unknown> = {};\n\n      for (const pair of value.split(\"&\")) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        result[key] = val;\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.match(/^[0-9a-z]+\\[\\d+\\]=.+$/)) {\n      // Handle single array element case (e.g., \"x[0]=1\")\n      const [, index, val] = value.match(/^[0-9a-z]+\\[(\\d+)\\]=(.+)$/) || [];\n      if (index !== undefined && val !== undefined) {\n        const result = [];\n        const marshalled = marshal(val);\n        result[parseInt(index, 10)] = marshalled.value;\n        return {\n          identity: Identities.array,\n          value: result as T\n        };\n      }\n    } else if (value.match(/^[0-9a-z]+\\[\\]$/)) {\n      return {\n        identity: Identities.array,\n        value: value as T\n      };\n    }\n\n    // If the value is a string that is not a number, we check if it's a boolean:\n    if (value.match(/^true$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: true as T\n      };\n    }\n    if (value.match(/^false$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: false as T\n      };\n    }\n\n    return {\n      identity: Identities.string,\n      value: value as T\n    };\n  } else if (typeof value === \"number\") {\n    return {\n      identity: Identities.number,\n      value: value as T\n    };\n  } else if (value instanceof RegExp) {\n    return {\n      identity: Identities.regexp,\n      value: value as T\n    };\n  } else if (typeof value === \"boolean\") {\n    return {\n      identity: Identities.boolean,\n      value: value as T\n    };\n  } else if (value === null) {\n    return {\n      identity: Identities.null,\n      value: null\n    };\n  } else if (value === undefined) {\n    return {\n      identity: Identities.undefined,\n      value: undefined\n    };\n  } else if (Array.isArray(value)) {\n    return {\n      identity: Identities.array,\n      value: value as T\n    };\n  } else if (typeof value === \"object\") {\n    const marshalled = Object.entries(value).reduce(\n      (acc, [key, val]) => {\n        acc[key] = marshal(val)?.value;\n        return acc;\n      },\n      {} as Record<string, unknown>\n    );\n    return {\n      identity: Identities.object,\n      value: marshalled as T\n    };\n  } else if (typeof value === \"function\") {\n    return {\n      identity: Identities.function,\n      value: value as T\n    };\n  } else if (value instanceof Promise) {\n    return {\n      identity: Identities.promise,\n      value: value as T\n    };\n  }\n\n  throw new Error(\n    `unable to marshal value: ${value} (it is neither a string, number, boolean, nor a regular expression)`\n  );\n};\n\n\n\n---\nFile: /src/lib/helpers/normalize.ts\n---\n\n/**\n * Normalize a path to ensure it starts with a slash.\n *\n * @param {string} path The path to normalize.\n *\n * @returns {string} The normalized path.\n *\n * @category Helpers\n */\nexport const normalize = (path: string): string => {\n  if (path && !path.startsWith(\"/\")) {\n    path = \"/\" + path;\n  }\n  return path;\n};\n\n\n\n---\nFile: /src/lib/helpers/objects.ts\n---\n\n/**\n * Convert a value to a primitive value.\n *\n * @param obj - The value to convert.\n *\n * @returns The primitive value.\n *\n * @category Helpers\n *\n */\nexport const toPrimitive = (obj: any): string | number | boolean | undefined | null | Array<any> => {\n  if (obj instanceof RegExp) {\n    return obj.toString();\n  }\n  if (obj instanceof Array) {\n    return obj.map((item) => toPrimitive(item));\n  }\n  if (typeof obj === \"object\") {\n    return Object.entries(obj).map(([key, value]) => {\n      return {\n        key,\n        value: toPrimitive(value)\n      };\n    });\n  }\n\n  // Return the value as is if it's not an object, array, or RegExp.\n  // TODO: Maybe we should throw an error here? (future mateo problem..)\n  return obj;\n};\n\n\n\n---\nFile: /src/lib/helpers/query.ts\n---\n\n/**\n * Get a query parameter from the current URL.\n *\n * @param key - The key of the query parameter to get\n *\n * @returns The value of the query parameter, or null if it doesn't exist\n *\n * @category Helpers\n */\nexport const query = (key: string): string | null => {\n  return new URLSearchParams(window.location.search).get(key);\n};\n\n\n\n---\nFile: /src/lib/helpers/regexp.ts\n---\n\n/**\n * Regular expression utilities.\n *\n * @module regexp\n * @category Helpers\n */\nexport namespace regexp {\n  /**\n   * Safely handle a capable value as a RegExp.\n   *\n   * If the value is a string, it will be converted to a RegExp.\n   * If the value is already a RegExp, it will be returned as is.\n   * Otherwise, an error will be thrown.\n   *\n   * @throws {Error} If the value is not a string or RegExp.\n   */\n  export const from = (v: string | RegExp): RegExp => {\n    if (typeof v === \"string\") {\n      return new RegExp(v);\n    } else if (v instanceof RegExp) {\n      return new RegExp(v.source);\n    }\n    throw new Error(\"invalid regexp expression\");\n  };\n\n  /**\n   * Check if a string contains regex syntax.\n   *\n   * @param v The string to check.\n   * @returns True if the string contains regex syntax, false otherwise.\n   */\n  export const can = (v: string): boolean => {\n    // Check for:\n    // - Special characters: [] {} () * + ? . \\ ^ $ |\n    // - Character classes: \\w \\d \\s and their negations\n    // - Anchors: ^ $\n    // - Quantifiers: + * ? {}\n    // - Groups: (? (?: (?= (?! (?<= (?<!\n    return /[[\\]{}()*+?.,\\\\^$|#\\s]|\\\\[wWdDsS]|\\(\\?[:!=<]?/.test(v);\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/runtime.ts\n---\n\nimport { logging } from \"./logging\";\n\n/**\n * Runtime level configuration functionality.\n *\n * @category Helpers\n */\nexport namespace runtime {\n  /**\n   * Runtime configuration.\n   */\n  export type Config = {\n    tracing: {\n      enabled: boolean;\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n    logging: {\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n  };\n\n  /**\n   * Retrieve the runtime configuration.\n   *\n   * This can be sourced from environment variables or passed in as an argument.\n   */\n  export const config = (config?: Config): Config => {\n    return {\n      tracing: config?.tracing || import.meta?.env?.SPA_ROUTER?.tracing || false,\n      logging: {\n        level: config?.logging?.level || import.meta?.env?.SPA_ROUTER?.logging?.level || 4,\n        console: config?.logging?.console || import.meta?.env?.SPA_ROUTER?.logging?.console,\n        sink: config?.logging?.sink || import.meta?.env?.SPA_ROUTER?.logging?.sink\n      }\n    };\n  };\n\n  /**\n   * The current runtime configuration.\n   *\n   * When first called, it will retrieve the runtime configuration from the environment variables.\n   * After that, it can be mutated and will not be retrieved from the environment variables again.\n   */\n  export let current: Config = config();\n}\n\n\n\n---\nFile: /src/lib/helpers/tracing.svelte.ts\n---\n\nimport { ReactiveMap } from \"../utilities.svelte\";\n\nimport { logging } from \"./logging\";\nimport { runtime } from \"./runtime\";\n\n/**\n * A span is a single trace in a trace collection.\n *\n * @category Helpers\n */\nexport class Span {\n  prefix?: string;\n  id?: string;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  traces?: ReactiveMap<string, Trace> = $state(new ReactiveMap());\n\n  constructor(span: Span, prefix?: string) {\n    this.prefix = prefix;\n    this.name = span.name;\n    this.id = span.id || Math.random().toString(36).substring(2, 25);\n    this.description = span.description;\n    this.metadata = span.metadata;\n    this.date = span.date || new Date();\n  }\n\n  trace?(trace: Trace, prefix?: string): Trace {\n    const id = trace.id || Math.random().toString(36).substring(2, 25);\n    trace = new Trace(trace, this.traces.size + 1, this, prefix);\n    this.traces.set(id, trace);\n\n    logging.trace(prefix, trace);\n\n    return trace;\n  }\n\n  get?(): MapIterator<Trace> {\n    return this.traces.values();\n  }\n}\n\n/**\n * A trace is a collection of spans.\n *\n * @category Helpers\n */\nexport class Trace {\n  prefix?: string;\n  id?: string;\n  index?: number;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  span?: Span;\n\n  constructor(trace: Trace, index?: number, span?: Span, prefix?: string) {\n    this.id = trace.id || Math.random().toString(36).substring(2, 25);\n    this.index = index;\n    this.date = trace.date || new Date();\n    this.name = trace.name;\n    this.description = trace.description;\n    this.metadata = trace.metadata;\n    this.span = span;\n    this.prefix = trace.prefix;\n  }\n\n  /**\n   * Wrapper method for logging a trace to the browser console.\n   *\n   * @category Helpers\n   */\n  toConsole?(level?: logging.LogLevel): void {\n    const out = [\n      \"%c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s\",\n      \"color: #505050\",\n      this.date?.toISOString(),\n      \"color: #7A7A7A\",\n      \"color: #915CF2; font-weight: bold\",\n      this.span?.name || this.id,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #C3F53B; font-weight: bold\",\n      this.index,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #3BAEF5; font-weight: bold\",\n      `${this.metadata?.router ? `[${this.metadata.router.id}] ` : \"\"}${this.name}`,\n      \"color: #06E96C\",\n      this.description\n    ];\n\n    if (this.prefix) {\n      out[0] = `${this.prefix} %c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s`;\n    }\n\n    if (runtime.current.tracing.level >= logging.LogLevel.TRACE) {\n      out[0] += \"\\n%c%s\";\n      out.push(\n        \"color: #6B757F\",\n        `attached trace metadata:\\n\\n${JSON.stringify(\n          {\n            span: this.span.metadata,\n            trace: this.metadata\n          },\n          null,\n          2\n        )}`\n      );\n    } else if (runtime.current.tracing.level >= logging.LogLevel.DEBUG) {\n      if (this.span) {\n        // @ts-ignore\n        out.push(this.span.metadata);\n      }\n      if (this.metadata) {\n        // @ts-ignore\n        out.push(this.metadata);\n      }\n    }\n\n    console.log(...out);\n  }\n}\n\n/**\n * A reactive map of spans.\n *\n * @category Helpers\n */\nexport const spans = new ReactiveMap<string, Span>();\n\n/**\n * Helper method for creating a new span.\n *\n * @category Helpers\n */\nexport const createSpan = (name: string, metadata?: Record<string, any>) => {\n  if (runtime.current.tracing) {\n    const span = new Span({ name, metadata });\n    spans.set(name, span);\n    return span;\n  }\n};\n\n\n\n---\nFile: /src/lib/helpers/urls.test.ts\n---\n\nimport { expect, test } from \"vitest\";\n\nimport { urls } from \"./urls\";\n\ntest.only(\"parses with no query parameters\", () => {\n  expect(urls.parse(\"http://localhost:5173/#/foo/bar\")).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/foo/bar\",\n    hash: \"/foo/bar\",\n    query: {\n      original: \"\",\n      params: {}\n    }\n  });\n});\n\ntest.only(\"parses key-value query parameters\", () => {\n  expect(urls.parse(\"http://localhost:5173/#/foo/bar?negative=-123&a=1&str=string&b=true\")).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/foo/bar\",\n    query: {\n      params: {\n        a: \"1\",\n        str: \"string\",\n        b: \"true\",\n        negative: \"-123\"\n      }\n    },\n    hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\"\n  });\n});\n\n// test.only(\"parses array parameter parsing\", () => {\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//     protocol: \"http\",\n//     host: \"localhost\",\n//     port: \"5173\",\n//     path: \"/foo/bar\",\n//     query: {\n//       params: {\n//         a: [\"first\", 3, false, 1.9, 9.99]\n//       }\n//     },\n//     hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//   });\n// });\n\n// test.only(\"query is undefined\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar\").query.toString()).toEqual(\"\");\n// });\n\n// test.only(\"query.toString() matches location.search\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n// });\n\n// test.only(\"query.toString() matches multiples (pagination=2,23&company=123)\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n// });\n\n\n\n---\nFile: /src/lib/helpers/urls.ts\n---\n\nimport { hash, type Hash } from \"../hash\";\nimport { Query } from \"../query.svelte\";\n\nimport { normalize } from \"./normalize\";\n\n/**\n * The returned param value types for paths.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type Param = string | number | boolean;\n\n/**\n * The returned param value types.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type ReturnParam =\n  | RegExp\n  | boolean\n  | boolean[]\n  | number\n  | number[]\n  | string\n  | string[]\n  | Record<string, string | number | boolean | string[] | number[] | boolean[]>;\n\nexport type URL = {\n  protocol: string;\n  host: string;\n  port: string;\n  path: string;\n  query: Query;\n  hash: Hash;\n};\n\nexport namespace urls {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): URL => {\n    if (url === undefined || url.length === 0) {\n      throw new Error(`invalid URL: ${url}`);\n    }\n    const isAbsoluteUrl = url.includes(\"://\");\n    if (isAbsoluteUrl) {\n      const [protocol, remaining] = url.split(\"://\");\n      const hostPortMatch = remaining.match(/^([^/:]+)(?::(\\d+))?(.*)$/);\n      const [host, port, path] = hostPortMatch?.slice(1) ?? [];\n\n      const [before, queryString = \"\"] = (path || \"\").split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol,\n        host,\n        port,\n        path: normalize(before) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    } else {\n      // Handle relative URLs\n      const [pathPart, queryString = \"\"] = url.split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol: window.location.protocol.replace(\":\", \"\"),\n        host: window.location.hostname,\n        port: window.location.port,\n        path: normalize(pathPart) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    }\n  };\n\n  export const path = (path: string): string => {\n    return path.split(\"?\")[0];\n  };\n}\n\n\n\n---\nFile: /src/lib/hash.test.ts\n---\n\n// import { describe, expect, test } from \"vitest\";\n\n// import { hash } from \"./hash\";\n\n// describe(\"simple params\", () => {\n//   test(\"simple params\", () => {\n//     expect(hash.parse(\"/foo/bar?negative=-123&a=1&str=string&b=true\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: 1,\n//           str: \"string\",\n//           b: true,\n//           negative: -123\n//         }\n//       },\n//       hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\"\n//     });\n//   });\n// });\n\n// describe(\"crazy params\", () => {\n//   test(\"crazy params\", () => {\n//     expect(hash.parse(\"/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: [\"first\", 3, false, 1.9, 9.99]\n//         }\n//       },\n//       hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//     });\n//   });\n// });\n\n\n\n---\nFile: /src/lib/hash.ts\n---\n\nimport { Query } from \"./query.svelte\";\n\nexport type Hash = {\n  path: string;\n  query: Query;\n  hash: string;\n};\n\nexport namespace hash {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): Hash => {\n    if (url) {\n      const [_, afterHash = \"\"] = url.split(\"#\");\n      const [path, queryString = \"\"] = afterHash.split(\"?\");\n      return {\n        path,\n        query: new Query(queryString),\n        hash: afterHash\n      };\n    }\n  };\n}\n\n\n\n---\nFile: /src/lib/hooks.ts\n---\n\nimport type { RouteResult } from \"./route.svelte\";\n\n/**\n * Hooks are functions that can be used to modify the behavior of routing\n * when a route is navigated to (before and/or after).\n *\n * The return value of the hook is a boolean that determines if the route should\n * be navigated to. If the hook returns `false` (or nothing), navigation will be cancelled.\n *\n * @category hooks\n */\nexport type HookReturn = void | boolean | Promise<boolean> | Promise<void>;\nexport type Hook = (route: RouteResult) => HookReturn;\n\n\n\n---\nFile: /src/lib/index.ts\n---\n\nexport * from \"./actions\";\nexport { active } from \"./actions/active.svelte\";\nexport { RouteOptions } from \"./actions/options\";\nexport { route } from \"./actions/route.svelte\";\nexport * from \"./helpers\";\nexport type { Condition, Evaluation, EvaluationResult } from \"./helpers/evaluators\";\nexport { goto } from \"./helpers/goto\";\nexport { identify, type Identities, type Identity } from \"./helpers/identify\";\nexport { logging } from \"./helpers/logging\";\nexport { marshal, type Marshalled } from \"./helpers/marshal\";\nexport { normalize } from \"./helpers/normalize\";\nexport { query } from \"./helpers/query\";\nexport { runtime } from \"./helpers/runtime\";\nexport { Span, Trace } from \"./helpers/tracing.svelte\";\nexport type { ReturnParam as Param } from \"./helpers/urls\";\nexport type { Hook } from \"./hooks\";\nexport { Query } from \"./query.svelte\";\nexport type { QueryEvaluationResult, QueryType } from \"./query.svelte\";\nexport { registry, type Registry } from \"./registry.svelte\";\nexport type { ApplyFn, Route, RouteConfig, RouteResult } from \"./route.svelte\";\nexport { RouterInstanceConfig } from \"./router-instance-config\";\nexport { RouterInstance } from \"./router-instance.svelte\";\nexport { default as Router } from \"./router.svelte\";\nexport { getStatusByValue, StatusCode, type Statuses } from \"./statuses\";\n\n\n\n---\nFile: /src/lib/path.ts\n---\n\n/**\n * @remarks\n * Future home of more path related functionality.\n */\nimport { Query } from \"./query.svelte\";\n\n/**\n * The types of values that can be used as a path.\n *\n * @category Router\n */\nexport type PathType = string | number | RegExp | Function | Promise<unknown>;\n\nexport class Path {\n  protocol: string;\n  host: string;\n  path: string;\n  query: Query;\n\n  constructor() {\n    this.protocol = location.protocol;\n    this.host = location.host;\n    this.path = location.pathname;\n    if (location.search.length > 0) {\n      this.query = new Query(Object.fromEntries(new URLSearchParams(location.search)));\n    }\n  }\n\n  toURL(): string {\n    return `${this.protocol}://${this.host}${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n\n  toURI(): string {\n    return `${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n}\n\nexport namespace paths {\n  export const base = (base: string, path: string): boolean => {\n    return path.match(new RegExp(`^${base}(/|$)`)) !== null;\n  };\n}\n\n\n\n---\nFile: /src/lib/query.svelte.ts\n---\n\nimport { evaluators, type Condition } from \"./helpers/evaluators\";\nimport { goto } from \"./helpers/goto\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport type { ReturnParam } from \"./helpers/urls\";\n\n/**\n * The types of values that can be used as a query.\n *\n * @category Router\n */\nexport type QueryType<T = unknown> = Record<string, string | number | RegExp | Function | Promise<T>>;\n\n/**\n * The types of values that the {Query} test method can return.\n *\n * @category Router\n */\nexport type QueryEvaluationResult = {\n  condition: Condition;\n  matches?: Record<string, ReturnParam>;\n};\n\n/**\n * Query string operations.\n *\n * @category Helpers\n */\nexport class Query {\n  params: Record<string, ReturnParam> = {};\n  original?: string;\n\n  constructor(query?: Record<string, string> | string | Query | Record<string, ReturnParam>) {\n    if (typeof query === \"string\") {\n      this.original = query;\n    }\n\n    if (query) {\n      const marshalled = marshal(query);\n      if (marshalled.value) {\n        this.params = marshalled.value as Record<string, ReturnParam>;\n      }\n    }\n  }\n\n  /**\n   * Get a value from the query string parameters and optionally provide\n   * a default value if the key is not found.\n   *\n   * @param key - The key to get the value from.\n   * @param defaultValue - The default value to return if the key is not found.\n   */\n  get<T>(key: string, defaultValue?: T): T {\n    return (this.params[key] as T) || defaultValue;\n  }\n\n  /**\n   * Set a value in the query string parameters.\n   */\n  set(key: string, value: string) {}\n\n  /**\n   * Delete a value from the query string parameters.\n   */\n  delete(key: string) {\n    delete this.params[key];\n  }\n\n  /**\n   * Clear the query string parameters.\n   */\n  clear() {\n    this.params = {};\n  }\n\n  goto(path: string) {\n    goto(path, this.params);\n  }\n\n  test(inbound: Query): QueryEvaluationResult {\n    if (typeof inbound === \"object\") {\n      const matches: Record<string, ReturnParam> = {};\n      for (const [key, test] of Object.entries(inbound.params)) {\n        if (this.params[key]) {\n          const marshalled = marshal(this.params[key]);\n          if (test instanceof RegExp) {\n            const res = evaluators.any[Identities.regexp](test, this.params[key]);\n            if (res) {\n              matches[key] = res;\n            } else {\n              return {\n                condition: \"no-match\"\n              };\n            }\n          } else if (marshalled.identity === Identities.number) {\n            if (marshalled.value === this.params[key]) {\n              matches[key] = marshalled.value as number;\n            }\n          } else if (marshalled.identity === Identities.string) {\n            matches[key] = marshalled.value === this.params[key];\n          } else if (marshalled.identity === Identities.boolean) {\n            matches[key] = marshalled.value === Boolean(this.params[key]);\n          } else if (marshalled.identity === Identities.array) {\n            matches[key] = (marshalled.value as Array<unknown>).includes(this.params[key]);\n          }\n        } else {\n          return {\n            condition: \"no-match\"\n          };\n        }\n      }\n\n      if (Object.keys(matches).length === Object.keys(inbound).length && evaluators.valid[Identities.object](matches)) {\n        return {\n          condition: \"exact-match\",\n          matches: marshal(matches).value as Record<string, ReturnParam>\n        };\n      }\n\n      return {\n        condition:\n          Object.keys(matches).length > 1 && Object.keys(inbound).length !== Object.keys(matches).length\n            ? \"exact-match\"\n            : \"no-match\",\n        matches: matches as Record<string, ReturnParam>\n      };\n    }\n  }\n\n  /**\n   * Convert the query string parameters to a string.\n   */\n  toString() {\n    const stringifyValue = (value: any): string => {\n      if (Array.isArray(value)) {\n        return value.map((v) => stringifyValue(v)).join(\",\");\n      }\n      if (typeof value === \"object\" && value !== null) {\n        return Object.entries(value)\n          .map(([k, v]) => `${k}:${stringifyValue(v)}`)\n          .join(\",\");\n      }\n      // console.log(\"stringifyValue\", value, typeof value);\n      return encodeURIComponent(value);\n    };\n\n    return Object.entries(this.params)\n      .map(([key, value]) => `${encodeURIComponent(key)}=${stringifyValue(value)}`)\n      .join(\"&\");\n    // return preserveOriginal ? this._original : \"\";\n  }\n\n  /**\n   * Convert the query string parameters to a JSON object given\n   * we may have parameter values that are not json serializable\n   * out of the box.\n   */\n  toJSON(preserveOriginal?: boolean) {\n    return Object.fromEntries(Object.entries(this.params).map(([key, value]) => [key, value.toString()]));\n  }\n}\n\n\n\n---\nFile: /src/lib/query.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { Query } from \"./query.svelte\";\n\ndescribe(\"query\", () => {\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"b=2&a=false&c=str\").params).toEqual({ a: false, b: 2, c: \"str\" });\n  });\n\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"nonarray=1&a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\").params).toEqual({\n      nonarray: 1,\n      a: [\"first\", 3, false, 1.9, 9.99]\n    });\n  });\n});\n\n\n\n---\nFile: /src/lib/registry.svelte.ts\n---\n\nimport type { ApplyFn } from \"./route.svelte\";\nimport { RouterInstanceConfig } from \"./router-instance-config\";\nimport { RouterInstance } from \"./router-instance.svelte\";\nimport { ReactiveMap } from \"./utilities.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * Handles the dynamic registration and deregistration of router instances.\n *\n * @remarks\n * This is a singleton and should not be instantiated directly.\n *\n * @category Registry\n */\nexport class Registry {\n  /**\n   * Container for router instances.\n   */\n  instances = new ReactiveMap<string, RouterInstance>();\n\n  constructor() {\n    // Prevent multiple instantiation during HMR by storing instance in window\n    if ((window as any).__SVELTE_SPA_ROUTER_REGISTERED__) {\n      return (window as any).__SVELTE_SPA_ROUTER_REGISTERED__;\n    }\n    (window as any).__SVELTE_SPA_ROUTER_REGISTERED__ = this;\n\n    const { pushState, replaceState } = window.history;\n\n    window.history.pushState = function (...args) {\n      pushState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"pushState\"));\n    };\n\n    window.history.replaceState = function (...args) {\n      replaceState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"replaceState\"));\n    };\n  }\n\n  /**\n   * Register a new router instance.\n   *\n   * @param {Instance} config The instance to register.\n   * @param {ApplyFn} applyFn The function to call for applying route changes.\n   *\n   * @see {@link deregister}: The opposite of this method.\n   */\n  register(config: RouterInstanceConfig, applyFn: ApplyFn, span?: Span): RouterInstance {\n    if (this.instances.has(config.id)) {\n      throw new Error(`router instance with id ${config.id} already registered`);\n    }\n\n    const instance = new RouterInstance(config, applyFn);\n\n    if (span) {\n      span.trace({\n        prefix: \"🔍\",\n        name: \"registry.register\",\n        description: \"registering a new router instance\",\n        metadata: {\n          router: {\n            id: config.id,\n            basePath: config.basePath\n          },\n          location: \"/src/lib/registry.svelte:register()\",\n          config\n        }\n      });\n    }\n\n    this.instances.set(config.id, instance);\n\n    return instance;\n  }\n\n  /**\n   * Deregister a router instance.\n   *\n   * @param {string} id The id of the instance to deregister.\n   */\n  deregister(id: string, span?: Span): void {\n    const instance = this.instances.get(id);\n    if (span) {\n      span.trace({\n        prefix: instance ? \"✅\" : \"❌\",\n        name: \"registry.deregister\",\n        description: \"deregistering a router instance\",\n        metadata: {\n          router: {\n            id,\n            basePath: instance.config.basePath\n          },\n          location: \"/src/lib/registry.svelte:deregister()\",\n          config: instance.config\n        }\n      });\n    }\n\n    if (instance) {\n      this.instances.delete(id);\n    } else {\n      throw new Error(`router instance with id ${id} not found`);\n    }\n  }\n\n  get(id: string): RouterInstance {\n    const instance = this.instances.get(id);\n\n    return instance;\n  }\n}\n\n/**\n * Expose a reference to the registry of router instances.\n *\n * This is used to register & unregister router instances and to get\n * the route for a given path.\n *\n * This is a singleton and should not be instantiated directly and should\n * never be accessed outside of the scope of this package in most cases.\n *\n * @category Registry\n */\nexport const registry = (window as any).__SVELTE_SPA_ROUTER_REGISTRY__ || new Registry();\n\n\n\n---\nFile: /src/lib/route.svelte.ts\n---\n\n/**\n * This is the doc comment for file1.ts\n *\n * @packageDocumentation\n */\nimport type { Component, Snippet } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { paths, type PathType } from \"./path\";\nimport { Query } from \"./query.svelte\";\n\nimport { evaluators, type Condition, type Evaluation } from \"./helpers/evaluators\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport { normalize } from \"./helpers/normalize\";\nimport { regexp } from \"./helpers/regexp\";\nimport type { Span, Trace } from \"./helpers/tracing.svelte\";\nimport { urls, type ReturnParam } from \"./helpers/urls\";\n\n/**\n * A route result that includes the evaluation results of the route.\n *\n * This type is necessary for the internal workings of the router to ensure that\n * the evaluation results are included in the route result and to avoid requiring\n * it to be merged in the original route instance.\n *\n * @since 2.0.0\n * @category Router\n * @example\n * ```ts\n * const routeResult = new RouteResult({\n *   router: myRouter,\n *   route: myRoute,\n *   result: {\n *     path: {\n *       condition: \"exact-match\",\n *       original: \"/users/123\",\n *       params: { id: \"123\" }\n *     },\n *     querystring: {\n *       condition: \"exact-match\",\n *       original: { filter: \"active\" },\n *       params: { filter: \"active\" }\n *     },\n *     component: UserComponent,\n *     status: 200\n *   }\n * });\n * ```\n */\nexport class RouteResult {\n  /**\n   * The route that was evaluated to render this result.\n   *\n   * @since 2.0.0\n   * @readonly\n   * @remarks This may be undefined if the route result was created without an associated route.\n   * @see {@link Route}\n   */\n  route?: Route;\n\n  /**\n   * The comprehensive result of routing evaluation that rendered this route.\n   *\n   * This object contains all the information gathered during the route matching process,\n   * including path evaluation, querystring parsing, component resolution, and status determination.\n   *\n   * @since 2.0.0\n   * @readonly\n   * @remarks The result object is immutable once created and represents a snapshot of the routing state.\n   *\n   * @example\n   * ```ts\n   * // Accessing route evaluation results\n   * console.log(routeResult.result.path.original); // \"/users/123\"\n   * console.log(routeResult.result.path.params?.id); // \"123\"\n   * console.log(routeResult.result.status); // 200\n   * ```\n   */\n  result: {\n    /**\n     * The path evaluation results containing the matched path information.\n     *\n     * This object provides detailed information about how the current URL path\n     * was matched against the route's path pattern, including any extracted parameters.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For a route with path \"/users/:id\" and URL \"/users/123\"\n     * const pathResult = {\n     *   condition: \"exact-match\",\n     *   original: \"/users/123\",\n     *   params: { id: \"123\" }\n     * };\n     * ```\n     */\n    path: {\n      /**\n       * The evaluation condition indicating how the path was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"base-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original path string that was evaluated during routing.\n       *\n       * This represents the actual path portion of the URL that was processed,\n       * without query parameters or hash fragments.\n       *\n       * @since 2.0.0\n       * @example \"/users/123/profile\"\n       * @remarks This path is normalized and may differ from the raw URL path\n       */\n      original: string;\n\n      /**\n       * The parameters extracted from the path during evaluation.\n       *\n       * Contains named parameters extracted from the path pattern matching,\n       * such as route parameters defined with `:paramName` syntax or regex groups.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @example\n       * ```ts\n       * // For route \"/users/:id\" matching \"/users/123\"\n       * params: { id: \"123\" }\n       *\n       * // For regex route \"^/posts/(?<slug>.+)$\" matching \"/posts/my-article\"\n       * params: { slug: \"my-article\" }\n       * ```\n       * @see {@link ReturnParam} For parameter value types\n       * @remarks This is undefined when no parameters are extracted from the path\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The querystring evaluation results containing parsed query parameters.\n     *\n     * This object provides information about how the URL's query string\n     * was processed and any parameters that were extracted or matched.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For URL \"?filter=active&page=2\"\n     * const querystringResult = {\n     *   condition: \"exact-match\",\n     *   original: { filter: \"active\", page: \"2\" },\n     *   params: { filter: \"active\", page: \"2\" }\n     * };\n     * ```\n     */\n    querystring: {\n      /**\n       * The evaluation condition indicating how the querystring was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"partial-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original querystring data that was evaluated during routing.\n       *\n       * This can be either a parsed object representation of query parameters\n       * or the raw querystring, depending on how the route was configured.\n       *\n       * @since 2.0.0\n       * @see {@link ReturnParam} For supported parameter types\n       * @example\n       * ```ts\n       * // Parsed object format\n       * original: { filter: \"active\", sort: \"name\" }\n       *\n       * // Raw string format\n       * original: \"filter=active&sort=name\"\n       * ```\n       */\n      original: ReturnParam;\n\n      /**\n       * The parameters extracted from the querystring during evaluation.\n       *\n       * Contains the processed query parameters after applying any route-specific\n       * querystring matching rules and transformations.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @see {@link ReturnParam} For parameter value types\n       * @example\n       * ```ts\n       * // Standard query parameters\n       * params: { search: \"term\", page: \"1\" }\n       *\n       * // With type conversion\n       * params: { page: 1, active: true }\n       * ```\n       * @remarks This may be undefined if no querystring processing was required\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The component that was resolved and rendered when the route became active.\n     *\n     * This represents the actual component instance, snippet, or component factory\n     * that was determined during the routing process. It can be a direct component\n     * reference, a lazy-loaded component function, or a Svelte snippet.\n     *\n     * @since 2.0.0\n     * @default undefined\n     * @see {@link Component} Svelte component type\n     * @see {@link Snippet} Svelte snippet type\n     * @example\n     * ```ts\n     * // Direct component reference\n     * component: UserProfile\n     *\n     * // Lazy-loaded component\n     * component: () => import('./UserProfile.svelte')\n     *\n     * // Svelte snippet\n     * component: mySnippet\n     * ```\n     * @remarks This is undefined when the route doesn't render a component directly,\n     * such as when using hooks for custom rendering logic\n     */\n    component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n    /**\n     * The HTTP-style status code representing the result of the routing operation.\n     *\n     * This numeric code indicates the outcome of the route evaluation process,\n     * following HTTP status code conventions for consistency and familiarity.\n     * The status helps determine how the route result should be handled by\n     * status handlers and middleware.\n     *\n     * @since 2.0.0\n     * @see {@link StatusCode} For predefined status code constants\n     * @see {@link Statuses} For status-specific handlers\n     * @example\n     * ```ts\n     * status: 200  // Successful route match\n     * status: 404  // Route not found\n     * status: 401  // Unauthorized access\n     * status: 500  // Internal routing error\n     * ```\n     * @remarks Common values include 200 (OK), 404 (Not Found), 401 (Unauthorized),\n     * 403 (Forbidden), and 500 (Internal Server Error)\n     */\n    status: number;\n  };\n\n  /**\n   * The constructor for the `RouteResult` class.\n   *\n   * @param result The result of the route evaluation.\n   */\n  constructor(result: RouteResult) {\n    this.route = result.route;\n    this.result = result.result;\n  }\n\n  /**\n   * The string representation of the route including the querystring.\n   */\n  toString?(): string {\n    let querystring = \"\";\n    if (this.result.querystring.original && typeof this.result.querystring.original === \"object\") {\n      const params = new URLSearchParams();\n      for (const [key, value] of Object.entries(this.result.querystring.original)) {\n        if (value !== undefined && value !== null) {\n          params.append(key, String(value));\n        }\n      }\n      querystring = params.toString();\n    } else if (this.result.querystring.original) {\n      querystring = String(this.result.querystring.original);\n    }\n\n    return `${this.result.path.original}${querystring ? `?${querystring}` : \"\"}`;\n  }\n}\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn = (result: RouteResult, span?: Span) => void;\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn2 = (result: RouteResult, span?: Span) => void;\n\n/**\n * A generic type that can be used to test the type of a value.\n * @category Router\n * @example\n * ```ts\n * const a: Testing<string> = \"asdf\";\n * const b: Testing<number> = 123;\n * ```\n */\nexport type Testing<T> = T;\n\nexport class RouteConfig {\n  name?: string | number;\n  basePath?: string;\n  path?: PathType;\n  querystring?: Record<string, ReturnParam>;\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n  props?: Record<string, any>;\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n  children?: RouteConfig[];\n  status?: number;\n\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = config.path;\n    this.querystring = config.querystring;\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n  }\n\n  toJSON?(): any {\n    return {\n      name: this.name,\n      basePath: this.basePath,\n      path: this.path,\n      props: this.props,\n      component: this.component,\n      querystring: this.querystring,\n      hooks: this.hooks,\n      children: this.children,\n      status: this.status\n    };\n  }\n}\n\n/**\n * A route that can be navigated to.\n * @example\n * ```ts\n * const routes: Route[] = [\n *   {\n *     component: Home\n *   },\n *   {\n *      path: \"(?<child>.*)\",\n *      component: ParseRouteParams\n *   }\n * ]\n * ```\n *\n * @category Router\n */\nexport class Route {\n  /**\n   * The unique identifier of this route.\n   * This is useful if you need to track routes outside of the router's scope.\n   *\n   * @optional If no value is provided, the route will not have a name.\n   */\n  name?: string | number;\n\n  /**\n   * The base path of the route.\n   *\n   * This is useful if you want to be declarative about the base path of the route\n   * and not depend on the router to determine the base path.\n   */\n  basePath?: string;\n\n  /**\n   * The path of the route to match against the current path.\n   *\n   * @optional If not provided, the route will match any path\n   * as it will be the default route.\n   */\n  path?: PathType;\n\n  /**\n   * The query params of the route.\n   *\n   * @optional If no value is provided, there are no query params.\n   */\n  querystring?: Query;\n\n  /**\n   * The component to render when the route is active.\n   *\n   * @optional If no value is provided, the route will not render a component.\n   * This is useful if you want to use pre or post hooks to render a component\n   * or snippet conditionally.\n   */\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n  /**\n   * The props to pass to the component.\n   *\n   * @optional If a value is provided, the component will receive this value in $props().\n   */\n  props?: Record<string, any>;\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The children routes of the route.\n   *\n   * This is useful if you want to be declarative about the routes that are direct\n   * children of this route and not depend on the router to determine the children\n   * when there are multiple <Router/> instances.\n   *\n   *\n   * @optional If no value is provided, there are no direct child routes. Routes may\n   * be mapped to children routes by the router when there are multiple <Router/> instances\n   * with overlapping `basePath` values.\n   *\n   * @example\n   * ```ts\n   * const routes: Route[] = [\n   *   ...\n   *   {\n   *     path: \"/users\",\n   *     children: [\n   *       {\n   *         path: \"/:id\",\n   *         component: User\n   *       }\n   *     ]\n   *   }\n   *   ...\n   * ]\n   * ```\n   */\n  children?: Route[];\n\n  /**\n   * The status of the route once it has been matched or otherwise processed.\n   */\n  status?: number;\n\n  /**\n   * Traces are a list of objects that describe the route's path and query params\n   * as it is processed by the router.\n   */\n  traces?: Trace[] = $state([]);\n\n  /**\n   * The constructor for the `Route` class.\n   *\n   * @param {Route} config An instance of the `Route` class.\n   */\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = typeof config.path === \"string\" ? normalize(config.path) : config.path;\n\n    if (config.querystring) {\n      this.querystring = new Query(config.querystring);\n    }\n\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n    this.children = config.children?.map((child) => new Route(child));\n  }\n\n  /**\n   * Parse the route against the given path.\n   * @param path The path to parse against the route.\n   */\n  test?(path: PathType): Evaluation {\n    const matcher = urls.path(path.toString());\n    // Handle string paths being passed in at the route.path level:\n    if (typeof this.path === \"string\") {\n      // Detect if this path contains regex syntax:\n      if (regexp.can(this.path)) {\n        // Path is a regex, so we need to test it against the path passed in:\n        const match = regexp.from(this.path).exec(matcher);\n        if (match) {\n          return {\n            condition: \"exact-match\",\n            params: match.groups\n          };\n        }\n      } else {\n        // Path is not a regex, so we then check if the path passed in is a direct match:\n        if (this.path === matcher) {\n          return {\n            condition: \"exact-match\",\n            params: this.path\n          };\n        } else if (paths.base(this.path, matcher)) {\n          return {\n            condition: \"base-match\",\n            params: {}\n          };\n        }\n      }\n    }\n    // Handle RegExp instances being passed in at the route.path level:\n    else if (this.path instanceof RegExp) {\n      const res = evaluators.any[Identities.regexp](this.path, matcher);\n      if (res) {\n        return {\n          condition: \"exact-match\",\n          params: res\n        };\n      }\n    }\n    // Handle numeric paths being passed in at the route.path level:\n    else if (typeof this.path === \"number\" && this.path === marshal(matcher).value) {\n      throw new Error(\"numbered route match not supported at the route.path level\");\n    }\n\n    return {\n      condition: \"no-match\",\n      params: {}\n    };\n  }\n\n  /**\n   * The absolute path of the route by combining the router's base path and\n   * the route's path.\n   */\n  absolute?(): string {\n    /**\n     * If the router has a base path, we need to combine it with the route's path\n     * otherwise it will have \"undefined\" as the base path and the path will be\n     * incorrect:\n     */\n    if (this.basePath) {\n      return `${this.basePath}${this.path}`;\n    }\n    return this.path.toString();\n  }\n}\n\n\n\n---\nFile: /src/lib/router-instance-config.ts\n---\n\nimport { type Component } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { RouteConfig } from \"./route.svelte\";\nimport type { Statuses } from \"./statuses\";\n\n/**\n * The configuration for a new router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstanceConfig {\n  /**\n   * The id for the router instance.\n   *\n   * @optional If no value is provided, the id will be a random string of characters.\n   */\n  id?: string;\n\n  /**\n   * The base path for the router instance.\n   *\n   * @optional If no value is provided, the base path will be \"/\".\n   */\n  basePath?: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes: RouteConfig[];\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The initial path for the router instance.\n   *\n   * @optional If no value is provided, the initial path will be the current path of the browser.\n   */\n  initialPath?: string;\n\n  /**\n   * The not found component for the router instance.\n   *\n   * @optional If no value is provided and no route could be found,\n   * the router will will not render anything.\n   */\n  notFoundComponent?: Component<any>;\n\n  /**\n   * The default components rendered when a route is not found and\n   * the status code is in one of the following:\n   * 400, 401, 403, 404, 500\n   * @optional If no value is provided, the default components will not be rendered.\n   */\n  statuses?: Statuses;\n\n  /**\n   * Whether to allow the same route to be rendered if the conditions are the\n   * same (taking in to account the path, query, and status code).\n   */\n  renavigation?: boolean;\n\n  /**\n   * The constructor for this router instance.\n   *\n   * @param {RouterInstanceConfig} config The config for this router instance.\n   */\n  constructor(config: RouterInstanceConfig) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.basePath = config.basePath;\n    this.hooks = config.hooks;\n    this.initialPath = config.initialPath;\n    this.notFoundComponent = config.notFoundComponent;\n    this.statuses = config.statuses;\n    this.routes = config.routes.map(\n      (route) =>\n        new RouteConfig({\n          ...route,\n          ...config\n        })\n    );\n  }\n\n  toJSON(): any {\n    return {\n      id: this.id,\n      basePath: this.basePath,\n      routes: this.routes,\n      hooks: this.hooks\n    };\n  }\n}\n\n\n\n---\nFile: /src/lib/router-instance.svelte.ts\n---\n\nimport { Query, registry, RouterInstanceConfig, Span, type ApplyFn, type Hook } from \".\";\nimport { Route, RouteResult } from \"./route.svelte\";\nimport { StatusCode } from \"./statuses\";\nimport { execute } from \"./utilities.svelte\";\n\nimport { SuccessfulConditions } from \"./helpers/evaluators\";\nimport { normalize } from \"./helpers/normalize\";\nimport { createSpan } from \"./helpers/tracing.svelte\";\nimport { urls } from \"./helpers/urls\";\n\n/**\n * The default routes that are used when no routes match.\n *\n * @category Router\n */\nexport const defaultRoutes = [\"\", \"/\", \"/*\", \"/^.*$/\", \"/.*/\"];\n\n/**\n * The handlers type that is used when registering a router instance.\n *\n * This is used to restore the original history methods when the last instance is destroyed\n * and to register & unregister the event listeners for the router instances to prevent memory leaks.\n *\n * @category Router\n */\nexport type RouterHandlers = {\n  /**\n   * The handler for the pushState event.\n   */\n  pushStateHandler: () => void;\n\n  /**\n   * The handler for the replaceState event.\n   */\n  replaceStateHandler: () => void;\n\n  /**\n   * The handler for the popState event.\n   */\n  popStateHandler: () => void;\n\n  /**\n   * The handler for the hashchange event.\n   */\n  hashChangeHandler: () => void;\n};\n\n/**\n * A class that represents a router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstance {\n  /**\n   * The id of the router instance.\n   */\n  id: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes = new Set<Route>();\n\n  /**\n   * The handlers for the router instance.\n   */\n  handlers: RouterHandlers;\n\n  /**\n   * The config for the router instance.\n   */\n  config: RouterInstanceConfig;\n\n  /**\n   * The apply function for the router instance.\n   */\n  applyFn: ApplyFn;\n\n  /**\n   * Whether the router instance is navigating.\n   */\n  navigating = $state(false);\n\n  /**\n   * The current route for the router instance.\n   */\n  current = $state<RouteResult>();\n\n  /**\n   * The constructor for the RouterInstance class.\n   *\n   * @param {RouterInstanceConfig} config The config for the router instance.\n   * @param {ApplyFn} applyFn The apply function for the router instance.\n   */\n  constructor(config: RouterInstanceConfig, applyFn: ApplyFn) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.config = config;\n    this.applyFn = applyFn;\n\n    this.handlers = {\n      pushStateHandler: () => this.handleStateChange(location.toString()),\n      replaceStateHandler: () => this.handleStateChange(location.toString()),\n      popStateHandler: () => this.handleStateChange(location.toString()),\n      hashChangeHandler: () => this.handleStateChange(location.toString())\n    };\n\n    window.addEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.addEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.addEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.addEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    for (let route of config.routes) {\n      this.routes.add(\n        new Route({\n          ...route,\n          /**\n           * If the route has no base path (because it's optional), use\n           * the router instance's base path.\n           */\n          basePath: route.basePath || this.config.basePath\n        })\n      );\n    }\n  }\n\n  /**\n   * Process a state change event from the browser history API.\n   *\n   * This method is called when the browser history API is used to change the\n   * current route via the `pushState`, `replaceState`, or `popState` methods.\n   *\n   * The method will evaluate the route for the given path and query, and apply\n   * the route to the router instance to ultimately call the `applyFn` function\n   * on the downstream router component to render the new route.\n   *\n   * @param {string} path The path to handle the state change for.\n   * @param {Query} query The query to handle the state change for.\n   * @param {Span} span @optional The span to attach traces to. If not provided,\n   * a new span will be created.\n   */\n  async handleStateChange(url: string, span?: Span): Promise<void> {\n    const { path, query } = urls.parse(url);\n    this.navigating = true;\n\n    if (!span) {\n      span = createSpan(\"detected history change event\");\n    }\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.handleStateChange\",\n      description: `attempting to handle a new state change for path \"${path}\"`,\n      metadata: {\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n        basePath: this.config.basePath,\n        path,\n        query,\n        url\n      }\n    });\n\n    const result = await this.get(path, query, span);\n\n    if (result && SuccessfulConditions.includes(result.result.path.condition)) {\n      span?.trace({\n        prefix: \"✅\",\n        name: \"router-instance.handleStateChange\",\n        description: `route found for path \"${path}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query: query?.params || false,\n          route: result,\n          url\n        }\n      });\n\n      // Run the global pre hooks:\n      if (this.config.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, this.config.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Run the route specific pre hooks:\n      if (result.route?.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Contact the downstream router component to apply the route:\n      this.applyFn(result, span);\n\n      // Run the route specific post hooks:\n      if (result && result.route?.hooks?.post) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.post))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Finally, run the global post hooks:\n      if (this.config.hooks?.post) {\n        await this.evaluateHooks(result, this.config.hooks.post);\n      }\n\n      this.current = result;\n    }\n\n    this.navigating = false;\n  }\n\n  async evaluateHooks(route: RouteResult, hooks: Hook | Hook[]): Promise<boolean> {\n    if (Array.isArray(hooks)) {\n      for (const hook of hooks) {\n        if (!(await execute(() => hook(route)))) {\n          return false;\n        }\n\n        /**\n         * Add small delay between hooks to prevent rapid History API\n         * calls causing the browser to halt (firefox specifically).\n         */\n        await new Promise((resolve) => setTimeout(resolve, 50));\n      }\n    } else {\n      if (!(await execute(() => hooks(route)))) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Retrieve a route for a given path.\n   *\n   * @param {string} path The path to get the route for.\n   *\n   * @returns {RegistryMatch} The matched route for the given path.\n   */\n  async get(path: string, query?: Query, span?: Span): Promise<RouteResult> {\n    path = path.replace(\"/#\", \"\");\n    const normalized = normalize(path.replace(this.config.basePath || \"/\", \"\"));\n    const renderDefaultRoute = (reason: string): RouteResult => {\n      let defaultRoute: Route;\n\n      for (const route of this.routes) {\n        if (!route.path || defaultRoutes.includes(route.path.toString())) {\n          defaultRoute = route;\n          break;\n        }\n      }\n\n      span?.trace({\n        prefix: defaultRoute ? \"✅\" : \"❌\",\n        name: \"router-instance.getDefaultRoute\",\n        description: `get default route because \"${reason}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:get()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query,\n          normalized,\n          route: defaultRoute\n        }\n      });\n\n      if (defaultRoute) {\n        return new RouteResult({\n          route: defaultRoute,\n          result: {\n            path: {\n              condition: \"default-match\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            component: defaultRoute.component,\n            status: StatusCode.OK\n          }\n        });\n      }\n    };\n\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.get\",\n      description: `${this.config.id} with base path \"${this.config.basePath || \"/\"}\" is attempting to get a route for path \"${path}\"`,\n      metadata: {\n        location: \"/src/lib/router-instance.svelte:get()\",\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        path,\n        query,\n        normalized\n      }\n    });\n\n    if (this.config.basePath === path) {\n      return renderDefaultRoute(\"base path is the same as the path\");\n    }\n\n    let candidate: RouteResult;\n\n    /**\n     * Now we check for router nesting:\n     */\n    for (const route of this.routes) {\n      const pathEvaluation = route.test(normalized);\n      if (pathEvaluation && SuccessfulConditions.includes(pathEvaluation.condition)) {\n        span?.trace({\n          prefix: \"✅\",\n          name: \"router-instance.get:routesloop\",\n          description: `${pathEvaluation.condition} for inbound path \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n          metadata: {\n            location: \"/src/lib/router-instance.svelte:get():forloop\",\n            router: {\n              id: this.config.id,\n              basePath: this.config.basePath\n            },\n            path,\n            query,\n            normalized,\n            route,\n            evaluation: {\n              path: pathEvaluation\n            }\n          }\n        });\n\n        if (route.querystring && query) {\n          const queryEvaluation = query.test(route.querystring);\n          if (SuccessfulConditions.includes(queryEvaluation?.condition)) {\n            span?.trace({\n              prefix: \"✅\",\n              name: \"router-instance.get.evaluateQuery\",\n              description: `${queryEvaluation?.condition} evaluating querystring \"${query?.toString()}\" for the route \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n              metadata: {\n                location: \"/src/lib/router-instance.svelte:get()\",\n                router: {\n                  id: this.config.id,\n                  basePath: this.config.basePath\n                },\n                path,\n                query,\n                normalized,\n                evaluation: {\n                  path: pathEvaluation,\n                  querystring: queryEvaluation\n                }\n              }\n            });\n            candidate = new RouteResult({\n              route,\n              result: {\n                path: {\n                  ...pathEvaluation,\n                  original: normalized\n                },\n                querystring: {\n                  ...queryEvaluation,\n                  original: query.toJSON()\n                },\n                component: route.component,\n                status: StatusCode.OK\n              }\n            });\n          }\n        } else {\n          /**\n           * No querystring is configured for this route, so we will\n           * use the querystring from the inbound path.\n           */\n          candidate = new RouteResult({\n            route,\n            result: {\n              path: {\n                ...pathEvaluation,\n                original: normalized\n              },\n              querystring: {\n                condition: \"permitted-no-conditions\",\n                original: query?.toJSON(),\n                params: query?.toJSON()\n              },\n              component: route.component,\n              status: StatusCode.OK\n            }\n          });\n        }\n      }\n    }\n\n    /**\n     * If we've made it this far, we should default to trying to find\n     * a route that has no path configured. This will be treated as\n     * the \"default\" route:\n     */\n    if (path === \"/\") {\n      return renderDefaultRoute(\"no routes match, last resort is to find a default route\");\n    }\n\n    /**\n     * We've exhaausted all options, so we will attempt to locate\n     * a 404 route from the statuses configuration applied to this\n     * router instance.\n     */\n    if (!candidate && this.config.statuses?.[404]) {\n      const status = this.config.statuses[404];\n      if (typeof status === \"function\") {\n        return {\n          result: {\n            ...status(\n              {\n                result: {\n                  path: {\n                    condition: \"permitted-no-conditions\",\n                    original: path\n                  },\n                  querystring: {\n                    condition: \"permitted-no-conditions\",\n                    original: query?.toJSON(),\n                    params: query?.toJSON()\n                  },\n                  status: StatusCode.NotFound\n                }\n              },\n              span\n            ),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      } else {\n        return {\n          result: {\n            ...(status as object),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      }\n    }\n\n    return candidate;\n  }\n\n  /**\n   * Deregister a router instance by removing it from the registry and\n   * restoring the original history methods.\n   *\n   * This is called when a router instance is removed from the DOM\n   * triggered by the `onDestroy` lifecycle method of the router instance.\n   */\n  deregister(span?: Span): void {\n    window.removeEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.removeEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.removeEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.removeEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    registry.deregister(this.config.id, span);\n  }\n\n  /**\n   * Get routes as an array for serialization purposes.\n   *\n   * @returns {Route[]} The routes as an array.\n   */\n  get routesArray(): Route[] {\n    return Array.from(this.routes);\n  }\n\n  /**\n   * Custom JSON serialization to handle Set objects properly.\n   *\n   * @returns {object} The serializable representation of the router instance.\n   */\n  toJSON(): any {\n    return {\n      id: this.id,\n      config: this.config\n    };\n  }\n}\n\n\n\n---\nFile: /src/lib/router.svelte\n---\n\n<script lang=\"ts\">\n  import { onDestroy, unmount, type Component } from \"svelte\";\n  import { createSpan, Span } from \"./helpers/tracing.svelte\";\n  import { registry } from \"./registry.svelte\";\n  import { type RouteResult } from \"./route.svelte\";\n  import { RouterInstanceConfig } from \"./router-instance-config\";\n  import type { RouterInstance } from \"./router-instance.svelte\";\n\n  let { instance = $bindable(), ...rest } = $props<\n    { instance?: RouterInstance } & Partial<RouterInstanceConfig> & Record<string, any>\n  >();\n\n  const span = createSpan(rest.id ? `[${rest.id}]` : \"router\");\n\n  let RenderableComponent = $state<Component | null>(null);\n  let router: RouterInstance;\n  let route: RouteResult = $state();\n  let additionalProps = $state<Record<string, any>>({});\n\n  const apply = async (r: RouteResult, span?: Span) => {\n    route = r;\n    span?.trace({\n      prefix: \"✅\",\n      name: \"apply\",\n      description: `<Router${router.config.id ? ` id=\"${router.config.id}\"` : \"\"}/> applying route ${r.result.path.original} (${r.result.path.condition})`,\n      metadata: {\n        location: \"/src/lib/router.svelte:apply()\",\n        router: {\n          id: router.config.id,\n          basePath: router.config.basePath\n        },\n        result: r\n      }\n    });\n    if (RenderableComponent) {\n      unmount(RenderableComponent, {});\n      RenderableComponent = null;\n    }\n\n    if (typeof r.result.component === \"function\" && r.result.component.constructor.name === \"AsyncFunction\") {\n      // Handle async component by first awaiting the import:\n      const module = await r.result.component();\n      RenderableComponent = module.default || module;\n    } else {\n      // Handle regular component by directly assigning the component:\n      RenderableComponent = r.result.component;\n    }\n    additionalProps = route.route?.props;\n  };\n\n  router = registry.register(new RouterInstanceConfig(rest), apply, span);\n\n  span?.trace({\n    prefix: \"✅\",\n    name: \"<Router/> Component\",\n    description: \"new component mounted\",\n    metadata: {\n      router: {\n        id: router.config.id,\n        basePath: router.config.basePath\n      },\n      location: \"/src/lib/router.svelte:mount()\"\n    }\n  });\n\n  instance = router;\n\n  if (span) {\n    span.metadata = {\n      router: router.config.id\n    };\n  }\n\n  router.handleStateChange(location.toString(), span);\n\n  onDestroy(() => {\n    router.deregister(span);\n  });\n\n  const { routes, basePath, ...restWithoutRoutes } = rest;\n</script>\n\n<RenderableComponent\n  {route}\n  {...additionalProps}\n  {...restWithoutRoutes} />\n\n\n\n---\nFile: /src/lib/statuses.ts\n---\n\nimport type { Component } from \"svelte\";\n\nimport type { Route, RouteResult } from \"./route.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * The available status codes that a route can have called out from the statuses\n * handler mapping.\n *\n * @see {@link Statuses}\n * @category Router\n */\nexport enum StatusCode {\n  OK = 200,\n  PermanentRedirect = 301,\n  TemporaryRedirect = 302,\n  BadRequest = 400,\n  Unauthorized = 401,\n  Forbidden = 403,\n  NotFound = 404,\n  InternalServerError = 500\n}\n\n/**\n * Route status handler mapping.\n *\n * Status handlers are called with a path and should return a new route\n * or a promise that resolves to a new route.\n *\n * @example\n * ```ts\n * const statuses: Statuses = {\n *   [StatusCode.NotFound]: {\n *     component: NotFound,\n *     props: {\n *       importantInfo: \"lets go!\"\n *     }\n *   },\n *   [StatusCode.BadRequest]: (path) => {\n *     notifySomething(path);\n *     return {\n *       component: BadRequest,\n *       props: {\n *         importantInfo: \"something went wrong...\"\n *       }\n *     };\n *   }\n * }\n * ```\n *\n * @see {@link Route}\n * @see {@link StatusCode}\n * @see {@link getStatusByValue}\n * @category Router\n */\nexport type Statuses = Partial<{\n  [K in StatusCode]: (\n    result: RouteResult,\n    span?: Span\n  ) => Route | Promise<Route> | Component<any> | { component: Component<any>; props?: Record<string, any> };\n}>;\n\n/**\n * Get the status by value.\n *\n * @param {number} value The value to get the status for.\n * @returns {StatusCode} The status.\n *\n * @see {@link StatusCode}\n * @category Router\n */\nexport const getStatusByValue = (value: number) => {\n  return Object.keys(StatusCode)[Object.values(StatusCode).indexOf(value)];\n};\n\n\n\n---\nFile: /src/lib/utilities.svelte.ts\n---\n\n/**\n * Wait for a predicate to become true with timeout handling.\n *\n * @param predicate Function that returns boolean to check for\n * @param timeout Time in milliseconds to wait before timing out\n * @throws Error if timeout is reached before predicate becomes true\n *\n * @category utilities\n */\nexport async function wait(predicate: () => boolean, timeout = 5000): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const timer = setTimeout(() => {\n      reject(new Error(`Timeout after ${timeout}ms waiting for predicate: ${predicate}`));\n    }, timeout);\n\n    const check = async () => {\n      if (predicate()) {\n        clearTimeout(timer);\n        resolve();\n      } else {\n        setTimeout(check, 50);\n      }\n    };\n\n    check();\n  });\n}\n\n/**\n * Check if a value is a promise.\n *\n * @param value - The value to check.\n *\n * @returns True if the value is a promise, false otherwise.\n *\n * @category utilities\n */\nexport function isPromise(value: any): boolean {\n  return !!value && (typeof value === \"object\" || typeof value === \"function\") && typeof value.then === \"function\";\n}\n\n/**\n * Execute a function and return a promise if the function is a promise.\n *\n * @param fn - The function to execute.\n *\n * @returns A promise if the function is a promise, otherwise the function result.\n *\n * @category utilities\n */\nexport const execute = async <T>(fn: () => T | Promise<T>): Promise<T> => {\n  if (isPromise(fn)) {\n    return await fn();\n  } else {\n    return fn();\n  }\n};\n\n/**\n * A reactive map that can be observed for changes using `$state()`.\n *\n * @category utilities\n */\nexport class ReactiveMap<K, V> extends Map<K, V> {\n  #state = $state(false);\n\n  get size() {\n    this.#state;\n    return super.size;\n  }\n\n  #trig() {\n    this.#state = !this.#state;\n  }\n\n  add(key: K, value: V) {\n    if (this.has(key)) {\n      throw new Error(`key ${key} already exists`);\n    }\n    return this.set(key, value);\n  }\n\n  get(key: K) {\n    this.#state;\n    return super.get(key);\n  }\n\n  set(key: K, value: V) {\n    const result = super.set(key, value);\n    this.#trig();\n    return result;\n  }\n\n  delete(key: K) {\n    const result = super.delete(key);\n    if (result) this.#trig();\n    return result;\n  }\n\n  clear() {\n    const result = super.clear();\n    this.#trig();\n    return result;\n  }\n\n  keys() {\n    this.#state;\n    return super.keys();\n  }\n  values() {\n    this.#state;\n    return super.values();\n  }\n  entries() {\n    this.#state;\n    return super.entries();\n  }\n  forEach(fn: (value: V, key: K, map: Map<K, V>) => void) {\n    this.#state;\n    return super.forEach(fn);\n  }\n\n  [Symbol.iterator]() {\n    this.#state;\n    return super[Symbol.iterator]();\n  }\n}\n\n\n\n---\nFile: /src/vite-env.d.ts\n---\n\n/// <reference types=\"vite/client\" />\n\nimport type { TracingConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  readonly SPA_ROUTER: {\n    logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n    tracing: TracingConfig;\n  };\n}\n\n\n\n---\nFile: /svelte.config.ts\n---\n\nimport { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n\tpreprocess: [vitePreprocess({})]\n};\n\n\n\n---\nFile: /vite.config.ts\n---\n\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\n\nimport { defineConfig } from \"vite\";\n\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nimport { sveltePreprocess } from \"svelte-preprocess\";\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte({\n      preprocess: [sveltePreprocess({ typescript: true })]\n    })\n  ]\n});\n\n\n\n---\nFile: /vitest.config.ts\n---\n\nimport { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      reporter: [\"json-summary\"],\n      reportsDirectory: \"tmp/coverage\"\n    }\n  }\n});\n\n\n\n---\nFile: /vitest.setup.ts\n---\n\nimport '@testing-library/jest-dom/vitest';\n\n"
  },
  {
    "path": "docs/makefile",
    "content": ".PHONY: llms.txt diagrams\n\nsetup:\n\t@if [ ! -d \"node_modules\" ]; then \\\n\t\techo \"Installing dependencies...\"; \\\n\t\tnpm install --legacy-peer-deps; \\\n\tfi\n\nwatch: setup ## Build the docs and watch for changes.\n\ttrap 'kill 0' SIGINT; \\\n\tnpm run docs:watch & \\\n\t(sleep 4 && cd ../tmp/build && npx httpserver -p 20000) & \\\n\twait\n\nllms.txt: setup\n\tcat docs/*.md docs/llms.txt > ../llms.txt\n\ndiagrams: setup\n\t@for file in diagrams/*.mmd; do \\\n\t\techo \"Generating diagram $$file\"; \\\n\t\tnode_modules/.bin/mmdc -t dark -b transparent -i $$file -o $${file%.mmd}.png --puppeteerConfigFile puppeteer.config.cjs; \\\n\tdone\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@mateothegreat/svelte5-router\",\n  \"version\": \"2.15.3\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"docs:build\": \"typedoc --footerLastModified --customTitle \\\"svelte 5 router\\\" \",\n    \"docs:watch\": \"typedoc --watch --footerLastModified --customTitle \\\"svelte 5 router\\\" \"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"^2.29.6\",\n    \"@mermaid-js/mermaid-cli\": \"^11.6.0\",\n    \"@sveltejs/package\": \"^2.3.11\",\n    \"@sveltejs/vite-plugin-svelte\": \"^5.1.0\",\n    \"@svitejs/changesets-changelog-github-compact\": \"^1.2.0\",\n    \"@tsconfig/svelte\": \"^5.0.4\",\n    \"@types/node\": \"^24.0.4\",\n    \"@typhonjs-typedoc/typedoc-theme-dmt\": \"^0.3.1\",\n    \"@vitest/coverage-v8\": \"^3.2.4\",\n    \"@vpalmisano/typedoc-plugin-ga\": \"^1.0.6\",\n    \"prettier\": \"^3.6.0\",\n    \"prettier-plugin-svelte\": \"^3.4.0\",\n    \"puppeteer\": \"^23.11.1\",\n    \"svelte\": \"^5.34.7\",\n    \"svelte-check\": \"^4.2.2\",\n    \"svelte-preprocess\": \"^6.0.3\",\n    \"typedoc\": \"^0.28.5\",\n    \"typedoc-github-theme\": \"^0.3.0\",\n    \"typedoc-plugin-coverage\": \"^4.0.1\",\n    \"typedoc-plugin-custom-validation\": \"^2.0.2\",\n    \"typedoc-plugin-dt-links\": \"^2.0.7\",\n    \"typedoc-plugin-extras\": \"^4.0.0\",\n    \"typedoc-plugin-ga\": \"^1.0.5\",\n    \"typedoc-plugin-include-example\": \"^2.1.2\",\n    \"typedoc-plugin-inline-sources\": \"^1.3.0\",\n    \"typedoc-plugin-mermaid\": \"^1.12.0\",\n    \"typedoc-plugin-pages\": \"^1.1.0\",\n    \"typedoc-plugin-version-header\": \"^1.0.0\",\n    \"typescript\": \"^5.8.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"peerDependencies\": {\n    \"svelte\": \"^5.0.0\"\n  },\n  \"dependencies\": {\n    \"@droppedcode/typedoc-plugin-copy-assets\": \"^1.0.11\"\n  }\n}\n"
  },
  {
    "path": "docs/props.md",
    "content": "# Passing Props\n\nYou can pass props to a route by using the `props` property on any route.\n\nThese props will be passed to the component via `$props()`:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: /\\/(?<child>.*)/,\n    component: DisplayParams,\n    props: {\n      randomId: Math.random().toString(36).substring(2, 15),\n      someUserStuff: {\n        username: \"mateothegreat\",\n        userAgent: navigator.userAgent\n      }\n    }\n  }\n];\n```\n\nThen, in your component, you can access the prop like this:\n\n```svelte\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<pre>{JSON.stringify(route, null, 2)}</pre>\n```\n\nWhen you navigate to `/props/bar?someQueryParam=123`, the output will be:\n\n```json\n{\n  \"route\": {\n    \"params\": {\n      \"child\": \"bar\"\n    },\n    \"props\": {\n      \"randomId\": \"y3pbfi1mgmg\",\n      \"someUserStuff\": {\n        \"username\": \"mateothegreat\",\n        \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\"\n      }\n    },\n    \"query\": {\n      \"someQueryParam\": \"123\"\n    },\n    \"name\": \"props-fancy-regex\",\n    \"path\": {\n      \"before\": \"/\\\\/(?<child>.*)/\",\n      \"after\": \"/props/bar\"\n    }\n  }\n}\n```\n"
  },
  {
    "path": "docs/puppeteer.config.cjs",
    "content": "module.exports = {\n  launch: {\n    args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"]\n  }\n};\n"
  },
  {
    "path": "docs/readme.md",
    "content": "# Svelte 5 SPA Router 🚀 🔥\n\n![logo](https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/logo-1000px.png)\n\n<img src=\"https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/coverage.svg?sanitize=true\" />\n\nAn SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.\n\n> [!NOTE]\n> Live demo: <https://demo.router.svelte.spa>\n>\n> API documentation: <https://docs.router.svelte.spa>\n\n## Features\n\n- Built for Svelte 5 🚀!\n- Divide & conquer - use nested routers all over the place.\n- Use components, snippets, or both 🔥!\n- Use regex paths (e.g. `/foo/(.*?)/bar`) and/or named parameters together.\n- Use async routes simply with `component: async () => import(\"./my-component.svelte\")`.\n- Add hooks to your routes to control the navigation flow 🔧.\n- Automagic styling of your anchor tags 💄.\n- Helper methods 🛠️ to make your life easier.\n- Debugging tools included 🔍.\n\n## Latest News\n\n### July 1, 2025\n\nVersion 2.15.4 released! 🎉 with some healthy updates!\n\n- 🔧 Added support for passing your own props down to the routed component ([#70](https://github.com/mateothegreat/svelte5-router/issues/70) - thanks [@inZaCz](https://github.com/inZaCz)).\n- 🐛 Fixed a bug where the router would not re-render the same component when the route changes.\n- 📊 Added [Router Architecture Diagrams](./diagrams.md) to give you a better understanding of how the router works.\n- 🎉 New demos for more patterns and use cases at <https://demo.router.svelte.spa/patterns>.\n\n> [!NOTE]\n> I'd like to share what svelte5-router is doing in the wild! If you're using it, please share your project with me by sending me a message on discord at [@mateothegreat](https://discord.com/users/505520869246763009) or just create a [new issue](https://github.com/mateothegreat/svelte5-router/issues/new) and I'll add it to the list. 🙏\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\nNow you can simply:\n\n```svelte\n<a href=\"/dashboard\" use:route>Dashboard</a>\n```\n\nBy adding `use:route` you will prevent the page from reloading and instead let the router take the wheel 🤸. Peruse the rest of the documentation at [docs/](docs/) for more details.\n\n> [!NOTE]\n> Without `use:route` the website will be reloaded after opening a new route. To prevent this use `goto()` or `use:route` so only the Route element get's changed.\n\nand `/ship-it`!\n\n> See [actions.md#route](actions.md#route) for the deets..\n\n## Table of Contents\n\n- [Getting Started](./getting-started.md)\n- [Routing](./routing.md)\n- [Hooks](./hooks.md)\n- [Actions](./actions.md)\n- [Helper Methods](./helpers.md)\n- [Default Status Mapping](./statuses.md)\n- [The Router Registry](./registry.md)\n- [Route Styling](./styling.md)\n- [Accessing Props](./props.md)\n- [Debugging](./debugging.md)\n- [Diagrams](./diagrams.md)\n\n> [!NOTE]\n> Include [llms.txt](../llms.txt) in your LLM prompt to get add rich context to your tasks by referencing `https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/main/llms.txt`.\n\n## How it works\n\nWhen the browser URL changes, the router instance is triggered. It then registers the route in the registry, evaluates the route matching, and resolves the route.\n\n<div align=\"center\">\n  <img src=\"./diagrams/router-architecture.png\" alt=\"Router Architecture\" />\n</div>\n\n> [!NOTE]\n> You can view more diagrams in [diagrams.md](./diagrams.md).\n"
  },
  {
    "path": "docs/registry.md",
    "content": "# Router Registry\n\nThe router [registry](../src/lib/registry.svelte.ts) is a global object that is used to store route\ninstances and their associated routing configuration.\n\nThis registry updates as you navigate through your application and as `<Router />` components\nare mounted and unmounted dynamically.\n\n![registry](./assets/registry.png)\n\n## Usage\n\nThough the registry is managed internally, though you can access it to debug your application.\n\nWhen `<Router />` is mounted, it will register itself in the registry.\n\nWhen `<Router />` is unmounted, it will unregister itself from the registry.\n\nYou can access the __global__ registry to debug your application by adding the following to your application:\n\n```svelte\n<script lang=\"ts\">\n  import { registry } from \"@mateothegreat/svelte5-router\";\n</script>\n\n<div>\n  {#each registry.instances.entries() as [id, instance]}\n    <div>\n      <pre>id: {id}</pre>\n      <pre>routes: {instance.routes.size}</pre>\n      <pre>current: {instance.current?.path || \"<default>\"}</pre>\n      <pre>navigating: {instance.navigating ? \"yes\" : \"no\"}</pre>\n    </div>\n  {/each}\n</div>\n```\n\nOr, you can access the __local__ registry directly from your `<Router />` component:\n\n```svelte\n<script lang=\"ts\">\n  import type { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Router } from \"@mateothegreat/svelte5-router\";\n\n  let instance = $state<RouterInstance>();\n\n  $inspect(instance); // Outputs the registry instance to the console.\n</script>\n\n<Router bind:instance {routes} />\n```\n"
  },
  {
    "path": "docs/routing-patterns.md",
    "content": "# Routing Patterns\n\nAs your application grows, you'll likely need to use more complex routing patterns. This guide will cover the most common patterns and how to use them.\n<p>The <abbr title=\"Hyper Text Markup Language\">HTML</abbr> specification\nis maintained by the <abbr title=\"World Wide Web Consortium\">W3C</abbr>.</p>\n\nasdf [^1]\n\n<h1 class=\"style-me\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n\n[<button>Click me</button>](https://www.google.com)\n\n[[docs/readme]]asdf\n\n## Table of Contents\n\n- [Routing Patterns](#routing-patterns)\n  - [Table of Contents](#table-of-contents)\n  - [Default Route](#default-route)\n  - [Single Path](#single-path)\n  - [Nested Paths](#nested-paths)\n  - [Parameter Extraction](#parameter-extraction)\n  - [Named Parameters](#named-parameters)\n\n## Default Route\n\nThis example demonstrates how to make a route be the default route under the following conditions:\n\nOrder of operations:\n\n1. If the path is empty, the route will be matched otherwise evaluation will continue.\n2. If no other route matches, the default route will be matched and evaluation will continue.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n> [!NOTE]\n> You can omit the `path` property to make the route the default route which is the\n> same as `path: \"/\"` and `path: \"\"`.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Single Path\n\nThis example will match any path that starts with `/path`.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Nested Paths\n\nThis example will match any path that starts with `/path/path/path` and can be nested further.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/nested>!\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Parameter Extraction\n\nCombine arbitrary paths and extractable parameters.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/paths-and-params>!\n\nOrder of operations:\n\n1. If there are arbitrary paths, the route **must** contain all of them.\n2. If there are parameters, the path **must** match the expression.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n**Unnamed Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(.*?)/path/(.*?)\",\n    component: ComponentToRender \n  }\n];\n```\n\n**Named Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Named Parameters\n\nThis example will match any path that starts with `/path/path/path/path` and can be nested further.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\na\n[^1]: This is a footnote\nb\n"
  },
  {
    "path": "docs/routing.md",
    "content": "# Routing Usage\n\nWe provide an array of `RouteConfig` objects to the `Router` component.\n\nEach `RouteConfig` object describes a route and its associated component.\n\n> [!NOTE]\n> If you are using the same component for multiple routes, you must add the `renavigation` prop to the\n\n## Pattern Matching\n\nYou can simply use static paths like `/foo` or dynamic paths like `/foo/(.*?)` with regex.\n\nExample patterns:\n\n| Pattern                                        | Description                                             |\n| ---------------------------------------------- | ------------------------------------------------------- |\n| `/`                                            | The root path.                                          |\n| `/foo`                                         | A static path.                                          |\n| `/foo/(.*?)`                                   | A dynamic path.                                         |\n| `/cool/(.*?)/(.*?)`                            | A dynamic path with two parameters.                     |\n| `(?<param1>.*)`                                | A dynamic path with a named parameter.                  |\n| `^/users/(?<id>[a-z0-9]{25})(?:/(?<tab>.*))?$` | A dynamic path with a named parameter and optional tab. |\n\n> When using named parameters, you can access them using the `$props()` function.\n>\n> For example, if the route is `/users/123/settings`, then `$props()` will be `{ id: \"123\", tab: \"settings\" }`.\n\n## Parameter Extraction\n\nParameters that are capable of being parsed from the path are passed to the component through the `route` prop:\n\n```svelte\n<script>\n  let { route } = $props();\n</script>\n```\n\nWhen using named parameters such as `(?<id>[a-z0-9]{25})`, the parameter value will be passed through the `route` prop as an object:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/users/(?<id>[a-z0-9]{25})\",\n    component: User\n  }\n];\n```\n\nand can be accessed like this:\n\n```svelte\n<script>\n  const userId = route.result.path.params.id;\n</script>\n```\n\nFor the full shape of `RouteResult` see the [API Reference](https://github.com/mateothegreat/svelte5-router/blob/main/src/lib/route.svelte.ts#L24).\n\n## Examples\n\n### Basic\n\nThe following example demonstrates a basic route configuration with two routes:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    // Notice that we don't need to specify the path.\n    // The router will use this route as the default route when \"/\" is visited.\n    component: Home\n  },\n  {\n    path: \"/about\",\n    component: About\n  }\n];\n```\n\nPassing the routes to the `<Router />` component:\n\n```svelte\n<Router {routes} />\n```\n\n## Full Example\n\nThe following example demonstrates a more complex route configuration with multiple routes and hooks:\n\n```ts\nconst routes: RouteConfig[] = [\n  // Example of a route that redirects to the home route:\n  {\n    path: \"\",\n    hooks: {\n      pre: () => {\n        goto(\"/home\");\n      }\n    }\n  },\n  {\n    // Here we use a regex to match the home route.\n    // This is useful if you want to match a route that has a dynamic path.\n    // The \"?:\" is used to group the regex without capturing the match:\n    path: /(?:^$|home)/,\n    component: Home,\n    // Use hooks to perform actions before and after the route is resolved:\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        console.log(\"pre hook #1 fired for route\");\n        return true; // Return true to continue down the route evaluation path.\n      },\n      // Hooks can also be an array of functions (async too):\n      post: [\n        // This is a post hook that will be executed after the route is resolved:\n        (route: RouteResult): boolean => {\n          console.log(\"post hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // This is an async post hook that will be executed after the route is resolved:\n        async (route: RouteResult): Promise<boolean> => {\n          console.log(\"post hook #2 (async) fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        }\n      ]\n    }\n  },\n  {\n    path: \"nested\",\n    component: Nested\n  },\n  {\n    path: \"async\",\n    // Routes can also be async functions that return a promise.\n    // This is useful if you want to load a component asynchronously aka \"lazy loading\":\n    component: async () => import(\"./lib/async/async.svelte\")\n  },\n  {\n    path: \"delayed\",\n    component: Delayed,\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        // Simulate a network delay by returning a promise that resolves after a second:\n        return new Promise((resolve) =>\n          setTimeout(() => {\n            resolve(true);\n          }, 1000)\n        );\n      }\n    }\n  },\n  {\n    path: \"props\",\n    component: Props\n  },\n  {\n    path: \"protected\",\n    component: Protected\n  },\n  {\n    path: \"query-redirect\",\n    component: QueryRedirect\n  },\n  {\n    path: \"context\",\n    component: Context\n  }\n];\n\n// This is a global pre hook that can be applied to all routes.\n// Here you could check if the user is logged in or perform some other\n// authentication checks:\nconst globalAuthGuardHook = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalAuthGuardHook\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\n## Renavigation\n\nIf you are using the same component for multiple routes, the `renavigation` prop is required to be set to `true` on the `<Router />` component.\n\n> [!NOTE]\n> This is now the default behavior. If you want to disable it, you can set the `renavigation` prop to `false` explicitly.\n\nFor example, if you have the following routes:\n\n```typescript\n<script>\n  import Router, { type RouteConfig } from \"@mateothegreat/svelte5-router/router.svelte\";\n\n  const routes: RouteConfig[] = [\n    {\n      path: \"/foo\",\n      component: SameComponent\n    },\n    {\n      path: \"/bar\",\n      component: SameComponent\n    }\n  ];\n</script>\n```\n\nAnd you want to disable it, you can do the following by setting the `renavigation` prop to `false`:\n\n```svelte\n<Router\n  id=\"renavigation-router\"\n  basePath=\"/renavigation\"\n  renavigation={false}\n  {routes} />\n```\n\nThis will prevent the same component to be used for multiple routes effectively being re-rendered when the route changes.\n"
  },
  {
    "path": "docs/statuses.md",
    "content": "# Route Statuses\n\nEach router instance can have a set of statuses that are rendered when a route\nreturns a specific status code such as 404 for \"Not Found\".\n\nWhen a route returns a status code, the router will render the component or execute the function\nspecified in the `statuses` prop for that status code.\n\n## Status Codes\n\nUsing the `StatusesMapping` enum, the following status codes are to be supported:\n\n> [!NOTE]\n> Currently, the `404` status code is implemented. We will be adding the\n> other status codes in the future.\n\n| Code    | Description           | Status          |\n| ------- | --------------------- | --------------- |\n| 301     | Permanent Redirect    | Coming Soon     |\n| 302     | Temporary Redirect    | Coming Soon     |\n| 400     | Bad Request           | Coming Soon     |\n| 401     | Unauthorized          | Coming Soon     |\n| 403     | Forbidden             | Coming Soon     |\n| __404__ | __Not Found__         | __Implemented__ |\n| 500     | Internal Server Error | Coming Soon     |\n\n## `BadRouted` Object\n\nWhen passing a function to the `statuses` value, the [`RouteResult`](../src/lib/route.ts) object is passed to that function.\n\nIt contains the following properties:\n\n- `path`: The path that was attempted to be accessed\n- `status`: The status code that was returned\n\n## Usage\n\n### Basic Usage\n\nIn this example, we will use the `NotFound` component to render when the router\nreturns a 404 status code because the route `/bad` does not exist.\n\nFirst, we will create the `NotFound` component:\n\n```svelte\n<script lang=\"ts\">\n  let props = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">\n    $props():\n    {JSON.stringify(props, null, 2)}\n  </pre>\n</div>\n```\n\nNext, we will create the `Router` component and pass the `NotFound` component\nto the `statuses` prop:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, Route, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  {routes}\n  statuses={{\n    [StatusCode.NotFound]: NotFound\n  }} />\n```\n\nWhen you navigate to `/bad`, the `NotFound` component will be rendered because\nthe route `/bad` does not exist.\n\n### Custom Usage\n\nYou can also pass a function to the `statuses` prop to have more control over the rendered component. The function receives a `BadRouted` object containing information about the failed route and must return an object with the component to render and any additional props:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig, StatusCode, getStatusByValue } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  id=\"my-main-router\"\n  bind:instance\n  {routes}\n  statuses={{\n     [StatusCode.NotFound]: (route: RouteResult) => {\n      console.warn(\n        `Route \"${route.result.path.original}\" could not be found :(`,\n        {\n          statusName: getStatusByValue(route.result.status),\n          statusValue: route.result.status\n        },\n        route\n      );\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n  }} />\n```\n\nIn this example, when a 404 error occurs:\n\n1. The function logs a warning with details about the failed route\n2. Returns the `NotFound` component with an additional prop `somethingExtra` containing the current timestamp\n3. The `NotFound` component will receive both its default props and the extra props specified in the function\n"
  },
  {
    "path": "docs/styling.md",
    "content": "# Routing Styling\n\nYou can have the router apply a class to the active route by setting the `active.class` option\nwhen configuring your routes.\n\nAs the routes change, the router will apply the class to the active route while removing it from the previous active route(s).\n\n## Configuration\n\nThis property can be a string or an array of strings:\n\n> [!NOTE]\n> This is a convenience feature and is not required. You can apply the class to the active route manually in your component.\n>\n> See <https://docs.router.svelte.spa/classes/RouteOptions.html> for more information.\n\nUsing a string:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: \"bg-yellow-500\"\n  }\n};\n```\n\nUsing an array of strings:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: [\n      \"bg-yellow-500\",\n      \"underline\"\n    ]\n  }\n};\n```\n\n## Usage\n\nWith our common configuration declared we can use it in our routes:\n\nImport the common configuration:\n\n```ts\n<script lang=\"ts\">\n  import { myDefaultRouteConfig } from \"./lib/common-stuff\";\n</script>\n```\n\n### Using `use:route`\n\n```svelte\n<a\n  use:route={myDefaultRouteConfig}\n  href=\"/props\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  /props\n</a>\n```\n\n### Using `use:active`\n\nYou can also be more prescriptive and pass in the active class as an object.\n\n> [!NOTE]\n> This is functionally equivalent to using `use:route` with the same configuration.\n> It is just a convenience method for when you don't need to pass in any other options.\n\n```svelte\n<a\n  use:route\n  use:active={{ active: { class: \"bg-pink-500\" } }}\n  href=\"/baz\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  Click Me\n</a>\n```\n\nHere we used two actions:\n\n- `use:route`\n  - This is the default action and is used to apply the active class to the active route.\n- `use:active`\n  - This is a convenience action that is used to apply the active class to the active route.\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\"\n}\n"
  },
  {
    "path": "docs/typedoc.json",
    "content": "// https://app.studyraid.com/en/courses/15016/typedoc-automated-api-documentation-for-typescript\n{\n  \"$schema\": \"https://typedoc.org/schema.json\",\n  \"blockTags\": [\n    \"@defaultValue\",\n    \"@deprecated\",\n    \"@example\",\n    \"@param\",\n    \"@privateRemarks\",\n    \"@remarks\",\n    \"@returns\",\n    \"@see\",\n    \"@throws\",\n    \"@typeParam\",\n    \"@author\",\n    \"@callback\",\n    \"@category\",\n    \"@categoryDescription\",\n    \"@default\",\n    \"@document\",\n    \"@extends\",\n    \"@augments\",\n    \"@yields\",\n    \"@group\",\n    \"@groupDescription\",\n    \"@import\",\n    \"@inheritDoc\",\n    \"@jsx\",\n    \"@license\",\n    \"@module\",\n    \"@mergeModuleWith\",\n    \"@prop\",\n    \"@property\",\n    \"@return\",\n    \"@satisfies\",\n    \"@since\",\n    \"@template\",\n    \"@type\",\n    \"@typedef\",\n    \"@summary\",\n    \"@optional\",\n    \"@source\",\n    \"@usage\",\n    \"@note\",\n    \"@todo\"\n  ],\n  \"categorizeByGroup\": false,\n  \"categoryOrder\": [\"actions\", \"components\", \"hooks\", \"utilities\"],\n  \"cleanOutputDir\": true,\n  \"commentStyle\": \"all\",\n  \"copyAssets\": {\n    \"copyHtmlImgTag\": true,\n    \"exclude\": [],\n    \"excludePath\": [\".*?exclude/.*?$\"],\n    \"include\": [],\n    \"includePath\": [\".(png|md)$\"],\n    \"onlyImages\": true\n  },\n  // \"coverageLabel\": \"Documentation Coverage\",\n  // \"coverageOutputPath\": \"docs/assets/coverage.svg\",\n  // \"coverageOutputType\": \"all\",\n  // \"coverageSvgWidth\": 190,\n  \"customValidation\": {\n    \"byKind\": [\n      {\n        \"kinds\": \"Function\",\n        \"summary\": true,\n        \"tags\": [\"example\"]\n      }\n    ]\n  },\n  \"entryPoints\": [\"../src/lib/index.ts\"],\n  \"exclude\": [\"**/*+(.spec|.e2e).ts\", \"**/*.svg\"],\n  \"excludeExternals\": true,\n  \"excludeInternal\": true,\n  \"excludeNotDocumented\": false,\n  \"externalPattern\": [\"**/*.md\"],\n  \"githubPages\": false,\n  \"groupReferencesByType\": false,\n  \"headings\": {\n    \"document\": false,\n    \"readme\": false\n  },\n  \"hideGenerator\": true,\n  \"highlightLanguages\": [\"typescript\", \"javascript\", \"svelte\", \"bash\", \"shell\", \"shellscript\"],\n  \"includeVersion\": true,\n  \"modifierTags\": [\"@alpha\", \"@beta\", \"@experimental\"],\n  \"navigation\": {\n    \"includeCategories\": true,\n    \"compactFolders\": true\n  },\n  \"navigationLinks\": {\n    \"Demo\": \"https://demo.router.svelte.spa\",\n    \"GitHub\": \"https://github.com/mateothegreat/svelte5-router\",\n    \"NPM\": \"https://www.npmjs.com/package/@mateothegreat/svelte5-router\",\n    \"Releases\": \"https://github.com/mateothegreat/svelte5-router/releases\",\n    \"Report Issue\": \"https://github.com/mateothegreat/svelte5-router/issues\"\n  },\n  \"out\": \"../tmp/build\",\n  \"plugin\": [\n    \"@droppedcode/typedoc-plugin-copy-assets\",\n    \"typedoc-github-theme\",\n    \"typedoc-plugin-inline-sources\",\n    \"typedoc-plugin-extras\",\n    \"typedoc-plugin-include-example\",\n    \"typedoc-plugin-mermaid\",\n    // \"typedoc-plugin-coverage\",\n    \"typedoc-plugin-custom-validation\",\n    \"typedoc-plugin-dt-links\",\n    \"@vpalmisano/typedoc-plugin-ga\",\n    \"typedoc-plugin-version-header\"\n  ],\n  \"versionHeaderFormat\": \"{{major}}.{{minor}}.{{patch}}\",\n  \"gaID\": \"G-FTT7485E5D\",\n  \"readme\": \"readme.md\",\n  \"searchInComments\": true,\n  \"searchInDocuments\": true,\n  \"sidebarLinks\": {\n    \"Demo\": \"https://demo.router.svelte.spa\",\n    \"GitHub\": \"https://github.com/mateothegreat/svelte5-router\",\n    \"Releases\": \"https://github.com/mateothegreat/svelte5-router/releases\",\n    \"Report Issue\": \"https://github.com/mateothegreat/svelte5-router/issues\"\n  },\n  \"useFirstParagraphOfCommentAsSummary\": true,\n  \"validation\": {\n    \"invalidLink\": false,\n    \"notDocumented\": false,\n    \"notExported\": false,\n    \"rewrittenLink\": false,\n    \"unusedMergeModuleWith\": false\n  },\n  \"visibilityFilters\": {\n    \"@alpha\": true,\n    \"@beta\": true,\n    \"external\": false,\n    \"inherited\": true,\n    \"private\": true,\n    \"protected\": true\n  }\n}\n"
  },
  {
    "path": "llms.txt",
    "content": "# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These actions are designed to be used with anchor (`<a>`) elements to handle navigation and manage active states.\n\n## Available Actions\n\n| Action | Description |\n|--------|-------------|\n| [`route`](#route) | Manages both navigation and active states of links. |\n| [`active`](#active) | Handles active state management for styling links. |  \n\n## Examples\n\n### Basic Navigation Link\n\n```svelte\n<a href=\"/home\" use:route>Home</a>\n```\n\n### Active State with Multiple Classes\n\n```svelte\n<a \n  href=\"/profile\" \n  use:route={{\n    default: { class: ['text-gray-600', 'hover:text-gray-900'] },\n    active: { class: ['text-blue-600', 'font-bold'] }\n  }}\n>\n  Profile\n</a>\n```\n\n### Exact Path Matching\n\n```svelte\n<a \n  href=\"/settings\" \n  use:route={{\n    active: {\n      class: 'active-link',\n      absolute: true // Only active when path exactly matches /settings\n    }\n  }}\n>\n  Settings\n</a>\n```\n\n### Query String Sensitive Navigation\n\n```svelte\n<a \n  href=\"/search?type=users\" \n  use:route={{\n    active: {\n      class: 'active-search',\n      querystring: true // Only active when querystring matches exactly\n    }\n  }}\n>\n  User Search\n</a>\n```\n\n### Notes\n\n- The `route` action automatically prevents default link behavior and handles navigation through the History API.\n- When using `active`, you'll need to handle navigation separately if needed.\n- Classes are applied dynamically based on the current route state.\n- The `absolute` option is useful for preventing parent routes from being marked as active when child routes are active.\n- The `querystring` option allows for precise matching including query parameters.\n\n---\n\n## `route`\n\nThe `route` action is the primary action for handling routing in your application. It manages both navigation and active states of links.\n\n```svelte\n<a href=\"/dashboard\" use:route>Dashboard</a>\n```\n\nThe `route` action accepts an options object with the following configuration:\n\n```typescript\n{\n  default?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  loading?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  disabled?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\n- `default`: Options applied when the route is inactive.\n- `active`: Options applied when the route is active.\n- `loading`: Options applied when the route is loading.\n- `disabled`: Options applied when the route is disabled.\n\nEach state accepts the following properties:\n\n- `absolute`: When `true`, effects only apply on exact path matches.\n- `querystring`: When `true`, effects only apply when querystring exactly matches.\n- `class`: CSS class(es) to apply when the state is active.\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:route={{\n    default: { class: 'text-gray-600' },\n    active: { \n      class: 'text-blue-600 font-bold',\n      absolute: true \n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n## `active`\n\nThe `active` action is a simplified version of `route` that only handles active state management without handling navigation events. This is useful when you want to style links based on the current route but handle navigation differently.\n\n```svelte\n<a href=\"/dashboard\" use:active>Dashboard</a>\n```\n\nThe `active` action accepts a subset of the route options, focusing only on the active state configuration:\n\n```typescript\n{\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:active={{\n    active: {\n      class: ['text-blue-600', 'font-bold'],\n      absolute: true,\n      querystring: true\n    }\n  }}\n>\n  Dashboard\n</a>\n```\n## [2.15.2] - 2025-05-26\n\n### 🐛 Bug Fixes\n\n- Support list of defaults for route; fixes #76\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.1] - 2025-05-25\n\n### 🐛 Bug Fixes\n\n- Route result serialization to string; closes #73\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.0] - 2025-05-24\n\n### 🚀 Features\n\n- Additional props support @ <Router/> -> [child], closes #70\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.14.1] - 2025-03-27\n\n### 🐛 Bug Fixes\n\n- Hooks return to false; closes #63\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- *(docs)* Better explain named params\n\n<!-- generated by git-cliff -->\n## [2.14.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.13.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.12.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.11.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.10.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.9.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.8.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.7.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.1] - 2025-03-05\n\n### ⚙️ Miscellaneous Tasks\n\n- Vercel cicd\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- *(demo)* Show random querystring usage\n- *(demo)* Show random querystring usage\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Clean up docs\n- Clean up docs\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n\n<!-- generated by git-cliff -->\n## [2.5.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.4.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.34] - 2025-03-05\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Better regexp instance handling\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.33] - 2025-03-04\n\n### 🐛 Bug Fixes\n\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.32] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.31] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.30] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.29] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.28] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.27] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.26] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.25] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.24] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.23] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.22] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.21] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.20] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.19] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.18] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.17] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.16] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.15] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.14] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.13] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.12] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.11] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.10] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.9] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.8] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.7] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.6] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.5] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.4] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.3] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.2] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.1] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.0] - 2025-03-04\n\n### 🚀 Features\n\n- Query param matchers; fix: same route, diff params #54\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.7] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.6] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.5] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.4] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.3] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.2] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.1] - 2025-02-28\n\n### 📚 Documentation\n\n- Update statuses doc\n- Update statuses doc\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.0] - 2025-02-28\n\n### 🚀 Features\n\n- Better status mapping; added callable status code handlers\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.1] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Options handling for <a> tags proper\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.0] - 2025-02-28\n\n### 🚀 Features\n\n- /protected demo\n- /protected demo\n- /protected demo\n- /protected demo\n\n### 💼 Other\n\n- Refactor\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.57] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.56] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.55] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.54] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.53] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.52] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.51] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.50] - 2025-02-27\n\n### 📚 Documentation\n\n- Add transitions demo\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.49] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.48] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.47] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.46] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.45] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.44] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.43] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.42] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.41] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.40] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n# Debugging\n\nThere are a few way to debug things.\n\n## Debug Logger\n\nIn your `vite.config.ts` file, you can add the following:\n\n```ts\nexport default defineConfig({\n  plugins: [svelte()],\n  build: {\n    sourcemap: true // If you want to use a debugger, add this!\n  },\n  define: {\n    // Tell the router to log when we're in debug mode.\n    // Otherwise, this statement is removed by the compiler (known as tree-shaking)\n    // and all subsequent log statements are removed at build time:\n    'import.meta.env.SPA_ROUTER': {\n      logLevel: \"debug\"\n    },\n  }\n});\n```\n\nThis allows us to log when we're in debug mode otherwise\nstatements like this are removed by the compiler (known astree-shaking):\n\n```ts\nif (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n  log.debug(this.config.id, \"unregistered router instance\", {\n    id: this.config.id,\n    routes: this.routes.size\n  });\n}\n```\n\nPutting it all together:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n\n  let instance: RouterInstance;\n\n  if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n    log.debug(instance.id, \"dumping routes\", {\n      config: instance.config,\n      routes: instance.routes,\n      current: instance.current,\n      navigating: instance.navigating\n    });\n  }\n</script>\n\n<Router bind:instance {routes}>\n```\n\nExample output:\n\n![debug](./assets/debugging-logger.png)\n# Router Architecture Diagrams\n\nThis document contains Mermaid diagrams that illustrate the architecture and flow of the Svelte 5 Router. These diagrams are designed to help you understand how the router works internally.\n\n## 1. Router Architecture\n\nShows the high-level architecture of the router, including how URL changes flow through the system to component rendering.\n\n<div align=\"center\">\n\n```mermaid\n%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\ngraph TB\n    A[Browser URL Change] -->|Triggers| B[RouterInstance]\n    B -->|Registers| C[Registry]\n    B -->|Evaluates| D[Route Matching]\n    D -->|Checks| E[Path Matching]\n    D -->|Checks| F[Query Matching]\n    E --> G[Route Resolution]\n    F --> G\n    G -->|Pre Hooks| H[Route Guards]\n    H -->|Success| I[Component Rendering]\n    H -->|Failure| J[Redirect/Deny]\n    I -->|Post Hooks| K[Final Render]\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style C fill:#f472b6,stroke:#db2777\n    style D fill:#4ade80,stroke:#16a34a\n    style G fill:#f472b6,stroke:#db2777\n    style H fill:#3b82f6,stroke:#1d4ed8\n    style I fill:#4ade80,stroke:#16a34a \n```\n\n</div>\n\n## 2. Routing Lifecycle\n\nA sequence diagram showing the temporal flow of routing operations from URL change to final render.\n\n<div align=\"center\">\n\n```mermaid\n%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\nsequenceDiagram\n    participant U as User/Browser\n    participant R as Router\n    participant RI as RouterInstance\n    participant RG as Registry\n    participant H as Hooks\n    participant C as Component\n\n    U->>R: URL Change\n    R->>RG: Check Registration\n    RG-->>R: Return Instance\n    R->>RI: Handle State Change\n    RI->>RI: Evaluate Route\n    RI->>H: Execute Pre-Hooks\n    alt Hook Success\n        H-->>RI: Continue\n        RI->>C: Render Component\n        RI->>H: Execute Post-Hooks\n        H-->>RI: Complete\n    else Hook Failure\n        H-->>RI: Abort/Redirect\n        RI->>U: Navigate Away\n    end \n```\n\n</div>\n\n## 3. Route Evaluation\n\nA detailed flowchart showing how routes are evaluated, including path matching, query parameter checking, and hook execution.\n\n<div align=\"center\">\n\n```mermaid\n%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\nflowchart TD\n    A[Route Evaluation Start] --> B{Check Path Type}\n    B -->|String| C[Direct Match]\n    B -->|RegExp| D[Pattern Match]\n    B -->|Function| E[Custom Match]\n    \n    C --> F{Path Matches?}\n    D --> F\n    E --> F\n    \n    F -->|Yes| G[Check Query Parameters]\n    F -->|No| H[Try Next Route]\n    \n    G --> I{Query Matches?}\n    I -->|Yes| J[Create Route Result]\n    I -->|No| H\n    \n    H --> K{More Routes?}\n    K -->|Yes| B\n    K -->|No| L[Use Default Route]\n    \n    J --> M[Execute Pre Hooks]\n    L --> M\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style F fill:#f472b6,stroke:#db2777\n    style I fill:#f472b6,stroke:#db2777\n    style J fill:#4ade80,stroke:#16a34a\n    style M fill:#3b82f6,stroke:#1d4ed8 \n```\n\n</div>\n\n## 4. Component Hierarchy\n\nShows the relationship between different components in the router system.\n\n<div align=\"center\">\n\n```mermaid\n%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\ngraph TD\n    A[Router Component] -->|Creates| B[RouterInstance]\n    B -->|Registers with| C[Registry]\n    B -->|Manages| D[Route Collection]\n    D -->|Contains| E[Route Configurations]\n    E -->|Has| F[Path Patterns]\n    E -->|Has| G[Query Patterns]\n    E -->|Has| H[Hooks]\n    E -->|Renders| I[Route Components]\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style C fill:#f472b6,stroke:#db2777\n    style D fill:#4ade80,stroke:#16a34a\n    style E fill:#f472b6,stroke:#db2777 \n```\n\n</div>\n# Getting Started\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Usage\n\nIn your `app.svelte` file, you can use the `Router` component to render your routes:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n\n  const routes: RouteConfig[] = [\n    {\n      component: Home\n    }\n    {\n      path: \"products\",\n      component: Products\n    },\n    {\n      path: \"settings\",\n      component: Settings\n    }\n  ];\n</script>\n\n<Router {routes} />\n```\n\nWhen you navigate to the root route, the `Home` component will be rendered.\n\nWhen you navigate to the `/products` route, the `Products` component will be rendered.\n\nWhen you navigate to the `/settings` route, the `Settings` component will be rendered.\n# Helpers\n\nThere are a few helpers that are available to you when using the router.\n\n## `goto(path: string, queryParams?: Record<string, string>)`\n\nNavigates to the given path by calling `goto(\"/path\")`.\n\nExample:\n\n```ts\ngoto(\"/foo\", { bar: \"baz\" });\n```\n\nThis will navigate to `/foo?bar=baz`.\n\n## `query(key: string): string | null`\n\nReturns the value of the query parameter for the given key or null if the key does not exist.\n\n## The `QueryString` class\n\nA helper class for working with the query string.\n\n> Check it out at [src/lib/query.svelte.ts](../src/lib/query.svelte.ts).\n> or import it with:\n>\n> ```ts\n> import { QueryString } from \"@mateothegreat/svelte5-router\";\n> ```\n>\n> and start using it now!\n\nBasic usage:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\nquery.get(\"foo\", \"bar\"); // \"bar\"\nquery.set(\"baz\", \"qux\");\nquery.toString();        // \"foo=bar&baz=qux\"\n```\n\nUsing it with navigation:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\n// ...\nquery.set(\"foo\", \"baz\");\nquery.set(\"baz\", \"qux\");\n// ...\n\nquery.goto(\"/test\"); // Navigates to \"/test?foo=baz&baz=qux\"\n```\n\nYou can also pass a query object to the `goto` method:\n\n```ts\ngoto(\"/test\", { foo: \"baz\" }); // Navigates to \"/test?foo=baz\"\n```\n# Routing Hooks\n\n | Order | Event  | Scope       | Description                                |\n | ----- | ------ | ----------- | ------------------------------------------ |\n | 1.    | `pre`  | `<Router/>` | Always runs *before* a route is attempted. |\n | 2.    | `pre`  | `Route`     | Runs *before* the route is rendered.       |\n | 3.    | `post` | `Route`     | Runs *after* the route is rendered.        |\n | 4.    | `post` | `<Router/>` | Always runs *after* a route is rendered.   |\n\n```ts\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nexport const authGuard = async (route: RouteResult): Promise<boolean> => {\n  console.log(\"simulating some login/auth check...\");\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  if (!localStorage.getItem(\"token\")) {\n    console.warn(\"redirecting to denied\");\n    goto(\"/protected/denied\");\n    return false;\n  }\n  return true;\n}\n\nconst globalPostHook1 = (route: RouteResult): boolean => {\n  console.warn(\"globalPostHook1\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n\nconst globalPostHook2 = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalPostHook2\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\nYou can pass an array or single method for the `pre` and `post` hooks and you can\nalso mix and match asynchronous and synchronous hooks.\n\n```svelte\n<Router\n  {routes}\n  hooks={{\n    pre: authGuard,\n    post: [\n      globalPostHook1,\n      globalPostHook2\n    ]\n  }}\n/>\n```\n# Passing Props\n\nYou can pass props to a route by using the `props` property on any route.\n\nThese props will be passed to the component via `$props()`:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: /\\/(?<child>.*)/,\n    component: DisplayParams,\n    props: {\n      randomId: Math.random().toString(36).substring(2, 15),\n      someUserStuff: {\n        username: \"mateothegreat\",\n        userAgent: navigator.userAgent\n      }\n    }\n  }\n];\n```\n\nThen, in your component, you can access the prop like this:\n\n```svelte\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<pre>{JSON.stringify(route, null, 2)}</pre>\n```\n\nWhen you navigate to `/props/bar?someQueryParam=123`, the output will be:\n\n```json\n{\n  \"route\": {\n    \"params\": {\n      \"child\": \"bar\"\n    },\n    \"props\": {\n      \"randomId\": \"y3pbfi1mgmg\",\n      \"someUserStuff\": {\n        \"username\": \"mateothegreat\",\n        \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\"\n      }\n    },\n    \"query\": {\n      \"someQueryParam\": \"123\"\n    },\n    \"name\": \"props-fancy-regex\",\n    \"path\": {\n      \"before\": \"/\\\\/(?<child>.*)/\",\n      \"after\": \"/props/bar\"\n    }\n  }\n}\n```\n# Svelte 5 SPA Router 🚀 🔥\n\n![logo](https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/logo-1000px.png)\n\n<img src=\"https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/coverage.svg?sanitize=true\" />\n\nAn SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.\n\n> [!NOTE]\n> Live demo: <https://demo.router.svelte.spa>\n>\n> API documentation: <https://docs.router.svelte.spa>\n\n## Features\n\n- Built for Svelte 5 🚀!\n- Divide & conquer - use nested routers all over the place.\n- Use components, snippets, or both 🔥!\n- Use regex paths (e.g. `/foo/(.*?)/bar`) and/or named parameters together.\n- Use async routes simply with `component: async () => import(\"./my-component.svelte\")`.\n- Add hooks to your routes to control the navigation flow 🔧.\n- Automagic styling of your anchor tags 💄.\n- Helper methods 🛠️ to make your life easier.\n- Debugging tools included 🔍.\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Table of Contents\n\n- [Getting Started](./getting-started.md)\n- [Routing](./routing.md)\n- [Hooks](./hooks.md)\n- [Actions](./actions.md)\n- [Helper Methods](./helpers.md)\n- [Default Status Mapping](./statuses.md)\n- [The Router Registry](./registry.md)\n- [Route Styling](./styling.md)\n- [Accessing Props](./props.md)\n- [Debugging](./debugging.md)\n- [Diagrams](./diagrams.md)\n\n> [!NOTE]\n> Include [llms.txt](./llms.txt) to your local LLM to get add rich context to your tasks by referencing `https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/main/llms.txt`.\n\n## How it works\n\nWhen the browser URL changes, the router instance is triggered. It then registers the route in the registry, evaluates the route matching, and resolves the route.\n\n<div align=\"center\">\n\n```mermaid\n%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    'primaryBorderColor': '#1e40af',\n    'lineColor': '#64748b',\n    'secondaryColor': '#4ade80',\n    'tertiaryColor': '#f472b6'\n  }\n}}%%\nflowchart TD\n    A[Route Evaluation Start] --> B{Check Path Type}\n    B -->|String| C[Direct Match]\n    B -->|RegExp| D[Pattern Match]\n    B -->|Function| E[Custom Match]\n    \n    C --> F{Path Matches?}\n    D --> F\n    E --> F\n    \n    F -->|Yes| G[Check Query Parameters]\n    F -->|No| H[Try Next Route]\n    \n    G --> I{Query Matches?}\n    I -->|Yes| J[Create Route Result]\n    I -->|No| H\n    \n    H --> K{More Routes?}\n    K -->|Yes| B\n    K -->|No| L[Use Default Route]\n    \n    J --> M[Execute Pre Hooks]\n    L --> M\n    \n    style A fill:#3b82f6,stroke:#1d4ed8\n    style B fill:#4ade80,stroke:#16a34a\n    style F fill:#f472b6,stroke:#db2777\n    style I fill:#f472b6,stroke:#db2777\n    style J fill:#4ade80,stroke:#16a34a\n    style M fill:#3b82f6,stroke:#1d4ed8 \n```\n\n</div>\n\n> [!NOTE]\n> You can view more diagrams in [diagrams.md](./diagrams.md).\n# Router Registry\n\nThe router [registry](../src/lib/registry.svelte.ts) is a global object that is used to store route\ninstances and their associated routing configuration.\n\nThis registry updates as you navigate through your application and as `<Router />` components\nare mounted and unmounted dynamically.\n\n![registry](./assets/registry.png)\n\n## Usage\n\nThough the registry is managed internally, though you can access it to debug your application.\n\nWhen `<Router />` is mounted, it will register itself in the registry.\n\nWhen `<Router />` is unmounted, it will unregister itself from the registry.\n\nYou can access the __global__ registry to debug your application by adding the following to your application:\n\n```svelte\n<script lang=\"ts\">\n  import { registry } from \"@mateothegreat/svelte5-router\";\n</script>\n\n<div>\n  {#each registry.instances.entries() as [id, instance]}\n    <div>\n      <pre>id: {id}</pre>\n      <pre>routes: {instance.routes.size}</pre>\n      <pre>current: {instance.current?.path || \"<default>\"}</pre>\n      <pre>navigating: {instance.navigating ? \"yes\" : \"no\"}</pre>\n    </div>\n  {/each}\n</div>\n```\n\nOr, you can access the __local__ registry directly from your `<Router />` component:\n\n```svelte\n<script lang=\"ts\">\n  import type { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Router } from \"@mateothegreat/svelte5-router\";\n\n  let instance = $state<RouterInstance>();\n\n  $inspect(instance); // Outputs the registry instance to the console.\n</script>\n\n<Router bind:instance {routes} />\n```\n# Routing Patterns\n\nAs your application grows, you'll likely need to use more complex routing patterns. This guide will cover the most common patterns and how to use them.\n<p>The <abbr title=\"Hyper Text Markup Language\">HTML</abbr> specification\nis maintained by the <abbr title=\"World Wide Web Consortium\">W3C</abbr>.</p>\n\nasdf [^1]\n\n<h1 class=\"style-me\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n\n[<button>Click me</button>](https://www.google.com)\n\n[[docs/readme]]asdf\n\n## Table of Contents\n\n- [Routing Patterns](#routing-patterns)\n  - [Table of Contents](#table-of-contents)\n  - [Default Route](#default-route)\n  - [Single Path](#single-path)\n  - [Nested Paths](#nested-paths)\n  - [Parameter Extraction](#parameter-extraction)\n  - [Named Parameters](#named-parameters)\n\n## Default Route\n\nThis example demonstrates how to make a route be the default route under the following conditions:\n\nOrder of operations:\n\n1. If the path is empty, the route will be matched otherwise evaluation will continue.\n2. If no other route matches, the default route will be matched and evaluation will continue.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n> [!NOTE]\n> You can omit the `path` property to make the route the default route which is the\n> same as `path: \"/\"` and `path: \"\"`.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Single Path\n\nThis example will match any path that starts with `/path`.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Nested Paths\n\nThis example will match any path that starts with `/path/path/path` and can be nested further.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/nested>!\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Parameter Extraction\n\nCombine arbitrary paths and extractable parameters.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/paths-and-params>!\n\nOrder of operations:\n\n1. If there are arbitrary paths, the route **must** contain all of them.\n2. If there are parameters, the path **must** match the expression.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n**Unnamed Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(.*?)/path/(.*?)\",\n    component: ComponentToRender \n  }\n];\n```\n\n**Named Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Named Parameters\n\nThis example will match any path that starts with `/path/path/path/path` and can be nested further.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\na\n[^1]: This is a footnote\nb\n# Routing Usage\n\nWe provide an array of `RouteConfig` objects to the `Router` component.\n\nEach `RouteConfig` object describes a route and its associated component.\n\n## Pattern Matching\n\nYou can simply use static paths like `/foo` or dynamic paths like `/foo/(.*?)` with regex.\n\nExample patterns:\n\n| Pattern                                        | Description                                             |\n| ---------------------------------------------- | ------------------------------------------------------- |\n| `/`                                            | The root path.                                          |\n| `/foo`                                         | A static path.                                          |\n| `/foo/(.*?)`                                   | A dynamic path.                                         |\n| `/cool/(.*?)/(.*?)`                            | A dynamic path with two parameters.                     |\n| `(?<param1>.*)`                                | A dynamic path with a named parameter.                  |\n| `^/users/(?<id>[a-z0-9]{25})(?:/(?<tab>.*))?$` | A dynamic path with a named parameter and optional tab. |\n\n> When using named parameters, you can access them using the `$props()` function.\n>\n> For example, if the route is `/users/123/settings`, then `$props()` will be `{ id: \"123\", tab: \"settings\" }`.\n\n## Parameter Extraction\n\nParameters that are capable of being parsed from the path are passed to the component through the `route` prop:\n\n```svelte\n<script>\n  let { route } = $props();\n</script>\n```\n\nWhen using named parameters such as `(?<id>[a-z0-9]{25})`, the parameter value will be passed through the `route` prop as an object:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/users/(?<id>[a-z0-9]{25})\",\n    component: User\n  }\n];\n```\n\nand can be accessed like this:\n\n```svelte\n<script>\n  const userId = route.result.path.params.id;\n</script>\n```\n\nFor the full shape of `RouteResult` see the [API Reference](https://github.com/mateothegreat/svelte5-router/blob/main/src/lib/route.svelte.ts#L24).\n\n## Examples\n\n### Basic\n\nThe following example demonstrates a basic route configuration with two routes:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    // Notice that we don't need to specify the path.\n    // The router will use this route as the default route when \"/\" is visited.\n    component: Home\n  },\n  {\n    path: \"/about\",\n    component: About\n  }\n];\n```\n\nPassing the routes to the `<Router />` component:\n\n```svelte\n<Router {routes} />\n```\n\n## Full Example\n\nThe following example demonstrates a more complex route configuration with multiple routes and hooks:\n\n```ts\nconst routes: RouteConfig[] = [\n  // Example of a route that redirects to the home route:\n  {\n    path: \"\",\n    hooks: {\n      pre: () => {\n        goto(\"/home\");\n      }\n    }\n  },\n  {\n    // Here we use a regex to match the home route.\n    // This is useful if you want to match a route that has a dynamic path.\n    // The \"?:\" is used to group the regex without capturing the match:\n    path: /(?:^$|home)/,\n    component: Home,\n    // Use hooks to perform actions before and after the route is resolved:\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        console.log(\"pre hook #1 fired for route\");\n        return true; // Return true to continue down the route evaluation path.\n      },\n      // Hooks can also be an array of functions (async too):\n      post: [\n        // This is a post hook that will be executed after the route is resolved:\n        (route: RouteResult): boolean => {\n          console.log(\"post hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // This is an async post hook that will be executed after the route is resolved:\n        async (route: RouteResult): Promise<boolean> => {\n          console.log(\"post hook #2 (async) fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        }\n      ]\n    }\n  },\n  {\n    path: \"nested\",\n    component: Nested\n  },\n  {\n    path: \"async\",\n    // Routes can also be async functions that return a promise.\n    // This is useful if you want to load a component asynchronously aka \"lazy loading\":\n    component: async () => import(\"./lib/async/async.svelte\")\n  },\n  {\n    path: \"delayed\",\n    component: Delayed,\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        // Simulate a network delay by returning a promise that resolves after a second:\n        return new Promise((resolve) =>\n          setTimeout(() => {\n            resolve(true);\n          }, 1000)\n        );\n      }\n    }\n  },\n  {\n    path: \"props\",\n    component: Props\n  },\n  {\n    path: \"protected\",\n    component: Protected\n  },\n  {\n    path: \"query-redirect\",\n    component: QueryRedirect\n  },\n  {\n    path: \"context\",\n    component: Context\n  }\n];\n\n// This is a global pre hook that can be applied to all routes.\n// Here you could check if the user is logged in or perform some other\n// authentication checks:\nconst globalAuthGuardHook = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalAuthGuardHook\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n# Route Statuses\n\nEach router instance can have a set of statuses that are rendered when a route\nreturns a specific status code such as 404 for \"Not Found\".\n\nWhen a route returns a status code, the router will render the component or execute the function\nspecified in the `statuses` prop for that status code.\n\n## Status Codes\n\nUsing the `StatusesMapping` enum, the following status codes are to be supported:\n\n> [!NOTE]\n> Currently, the `404` status code is implemented. We will be adding the\n> other status codes in the future.\n\n| Code    | Description           | Status          |\n| ------- | --------------------- | --------------- |\n| 301     | Permanent Redirect    | Coming Soon     |\n| 302     | Temporary Redirect    | Coming Soon     |\n| 400     | Bad Request           | Coming Soon     |\n| 401     | Unauthorized          | Coming Soon     |\n| 403     | Forbidden             | Coming Soon     |\n| __404__ | __Not Found__         | __Implemented__ |\n| 500     | Internal Server Error | Coming Soon     |\n\n## `BadRouted` Object\n\nWhen passing a function to the `statuses` value, the [`RouteResult`](../src/lib/route.ts) object is passed to that function.\n\nIt contains the following properties:\n\n- `path`: The path that was attempted to be accessed\n- `status`: The status code that was returned\n\n## Usage\n\n### Basic Usage\n\nIn this example, we will use the `NotFound` component to render when the router\nreturns a 404 status code because the route `/bad` does not exist.\n\nFirst, we will create the `NotFound` component:\n\n```svelte\n<script lang=\"ts\">\n  let props = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">\n    $props():\n    {JSON.stringify(props, null, 2)}\n  </pre>\n</div>\n```\n\nNext, we will create the `Router` component and pass the `NotFound` component\nto the `statuses` prop:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, Route, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  {routes}\n  statuses={{\n    [StatusCode.NotFound]: NotFound\n  }} />\n```\n\nWhen you navigate to `/bad`, the `NotFound` component will be rendered because\nthe route `/bad` does not exist.\n\n### Custom Usage\n\nYou can also pass a function to the `statuses` prop to have more control over the rendered component. The function receives a `BadRouted` object containing information about the failed route and must return an object with the component to render and any additional props:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig, StatusCode, getStatusByValue } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  id=\"my-main-router\"\n  bind:instance\n  {routes}\n  statuses={{\n     [StatusCode.NotFound]: (route: RouteResult) => {\n      console.warn(\n        `Route \"${route.result.path.original}\" could not be found :(`,\n        {\n          statusName: getStatusByValue(route.result.status),\n          statusValue: route.result.status\n        },\n        route\n      );\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n  }} />\n```\n\nIn this example, when a 404 error occurs:\n\n1. The function logs a warning with details about the failed route\n2. Returns the `NotFound` component with an additional prop `somethingExtra` containing the current timestamp\n3. The `NotFound` component will receive both its default props and the extra props specified in the function\n# Routing Styling\n\nYou can have the router apply a class to the active route by setting the `active.class` option\nwhen configuring your routes.\n\nAs the routes change, the router will apply the class to the active route while removing it from the previous active route(s).\n\n## Configuration\n\nThis property can be a string or an array of strings:\n\n> [!NOTE]\n> This is a convenience feature and is not required. You can apply the class to the active route manually in your component.\n>\n> See <https://docs.router.svelte.spa/classes/RouteOptions.html> for more information.\n\nUsing a string:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: \"bg-yellow-500\"\n  }\n};\n```\n\nUsing an array of strings:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: [\n      \"bg-yellow-500\",\n      \"underline\"\n    ]\n  }\n};\n```\n\n## Usage\n\nWith our common configuration declared we can use it in our routes:\n\nImport the common configuration:\n\n```ts\n<script lang=\"ts\">\n  import { myDefaultRouteConfig } from \"./lib/common-stuff\";\n</script>\n```\n\n### Using `use:route`\n\n```svelte\n<a\n  use:route={myDefaultRouteConfig}\n  href=\"/props\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  /props\n</a>\n```\n\n### Using `use:active`\n\nYou can also be more prescriptive and pass in the active class as an object.\n\n> [!NOTE]\n> This is functionally equivalent to using `use:route` with the same configuration.\n> It is just a convenience method for when you don't need to pass in any other options.\n\n```svelte\n<a\n  use:route\n  use:active={{ active: { class: \"bg-pink-500\" } }}\n  href=\"/baz\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  Click Me\n</a>\n```\n\nHere we used two actions:\n\n- `use:route`\n  - This is the default action and is used to apply the active class to the active route.\n- `use:active`\n  - This is a convenience action that is used to apply the active class to the active route.\nDirectory Structure:\n\n└── ./\n    ├── .github\n    │   └── ISSUE_TEMPLATE\n    │       ├── bug_report.md\n    │       └── feature_request.md\n    ├── demo\n    │   ├── cypress\n    │   │   ├── e2e\n    │   │   │   └── route-activation.cy.ts\n    │   │   └── support\n    │   │       ├── commands.ts\n    │   │       └── e2e.ts\n    │   ├── src\n    │   │   ├── lib\n    │   │   │   ├── components\n    │   │   │   │   ├── routes\n    │   │   │   │   │   ├── route-link.svelte\n    │   │   │   │   │   ├── route-title.svelte\n    │   │   │   │   │   └── route-wrapper.svelte\n    │   │   │   │   ├── badge.svelte\n    │   │   │   │   ├── code.svelte\n    │   │   │   │   ├── container.svelte\n    │   │   │   │   ├── default.svelte\n    │   │   │   │   ├── file-link.svelte\n    │   │   │   │   └── inline-code.svelte\n    │   │   │   ├── default-route-config.ts\n    │   │   │   ├── router-history.ts\n    │   │   │   └── session.svelte.ts\n    │   │   ├── routes\n    │   │   │   ├── extras\n    │   │   │   │   ├── dump.svelte\n    │   │   │   │   ├── extras.svelte\n    │   │   │   │   └── passing-down-props.svelte\n    │   │   │   ├── hash\n    │   │   │   │   └── hash.svelte\n    │   │   │   ├── nested\n    │   │   │   │   ├── level-1\n    │   │   │   │   │   ├── level-2\n    │   │   │   │   │   │   ├── level-3\n    │   │   │   │   │   │   │   └── level-3.svelte\n    │   │   │   │   │   │   └── level-2.svelte\n    │   │   │   │   │   └── level-1.svelte\n    │   │   │   │   └── nested.svelte\n    │   │   │   ├── paths-and-params\n    │   │   │   │   ├── custom-not-found.svelte\n    │   │   │   │   ├── display-params.svelte\n    │   │   │   │   ├── paths-and-params.svelte\n    │   │   │   │   └── querystring-matching.svelte\n    │   │   │   ├── patterns\n    │   │   │   │   ├── dump.svelte\n    │   │   │   │   ├── output.svelte\n    │   │   │   │   ├── parameter-extraction.svelte\n    │   │   │   │   └── patterns.svelte\n    │   │   │   ├── protected\n    │   │   │   │   ├── manage-account\n    │   │   │   │   │   ├── auth-guard-fast.ts\n    │   │   │   │   │   ├── auth-guard-slow.ts\n    │   │   │   │   │   ├── balance.svelte\n    │   │   │   │   │   ├── home.svelte\n    │   │   │   │   │   ├── manage-account.svelte\n    │   │   │   │   │   └── worker-client.svelte.ts\n    │   │   │   │   ├── account-state.svelte.ts\n    │   │   │   │   ├── denied.svelte\n    │   │   │   │   ├── login.svelte\n    │   │   │   │   └── main.svelte\n    │   │   │   ├── transitions\n    │   │   │   │   ├── fade.svelte\n    │   │   │   │   ├── slide.svelte\n    │   │   │   │   └── transitions.svelte\n    │   │   │   ├── delayed.svelte\n    │   │   │   ├── home.svelte\n    │   │   │   └── not-found.svelte\n    │   │   ├── app.css\n    │   │   ├── app.svelte\n    │   │   ├── main.ts\n    │   │   └── vite-env.d.ts\n    │   ├── cypress.config.ts\n    │   ├── index.html\n    │   ├── svelte.config.ts\n    │   ├── tailwind.config.ts\n    │   └── vite.config.ts\n    ├── docs\n    │   ├── actions.md\n    │   ├── changelog.md\n    │   ├── debugging.md\n    │   ├── getting-started.md\n    │   ├── helpers.md\n    │   ├── hooks.md\n    │   ├── props.md\n    │   ├── readme.md\n    │   ├── registry.md\n    │   ├── routing-patterns.md\n    │   ├── routing.md\n    │   ├── statuses.md\n    │   └── styling.md\n    ├── src\n    │   ├── lib\n    │   │   ├── actions\n    │   │   │   ├── active.svelte.ts\n    │   │   │   ├── apply-classes.ts\n    │   │   │   ├── index.ts\n    │   │   │   ├── options.ts\n    │   │   │   └── route.svelte.ts\n    │   │   ├── helpers\n    │   │   │   ├── evaluators.test.ts\n    │   │   │   ├── evaluators.ts\n    │   │   │   ├── goto.ts\n    │   │   │   ├── identify.ts\n    │   │   │   ├── index.ts\n    │   │   │   ├── logging.ts\n    │   │   │   ├── marshal.test.ts\n    │   │   │   ├── marshal.ts\n    │   │   │   ├── normalize.ts\n    │   │   │   ├── objects.ts\n    │   │   │   ├── query.ts\n    │   │   │   ├── regexp.ts\n    │   │   │   ├── runtime.ts\n    │   │   │   ├── tracing.svelte.ts\n    │   │   │   ├── urls.test.ts\n    │   │   │   └── urls.ts\n    │   │   ├── hash.test.ts\n    │   │   ├── hash.ts\n    │   │   ├── hooks.ts\n    │   │   ├── index.ts\n    │   │   ├── path.ts\n    │   │   ├── query.svelte.ts\n    │   │   ├── query.test.ts\n    │   │   ├── registry.svelte.ts\n    │   │   ├── route.svelte.ts\n    │   │   ├── router-instance-config.ts\n    │   │   ├── router-instance.svelte.ts\n    │   │   ├── router.svelte\n    │   │   ├── statuses.ts\n    │   │   └── utilities.svelte.ts\n    │   └── vite-env.d.ts\n    ├── svelte.config.ts\n    ├── vite.config.ts\n    ├── vitest.config.ts\n    └── vitest.setup.ts\n\n\n\n---\nFile: /.github/ISSUE_TEMPLATE/bug_report.md\n---\n\n---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: mateothegreat\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Logs**\nIf available please provide any available logs, screenshots, etc.\n\n**Additional context**\nAdd any other context about the problem here.\n\n\n\n---\nFile: /.github/ISSUE_TEMPLATE/feature_request.md\n---\n\n---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: mateothegreat\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n\n\n---\nFile: /demo/cypress/e2e/route-activation.cy.ts\n---\n\n/// <reference types=\"cypress\" />\n\nconst routes = require(\"../fixtures/routes.json\");\ndescribe.only(\"route activation\", () => {\n  beforeEach(() => {\n    cy.viewport(1500, 1500);\n    cy.visit(\"http://localhost:8173\");\n  });\n\n  routes.forEach((route) => {\n    it(`should activate ${route.id} when visiting ${route.path}`, () => {\n      cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, \"active\", route.active);\n    });\n  });\n\n  // it.only(\"displays two todo items by default\", () => {\n  //   // cy.get(\"a[href='/props']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"props.svelte\").should(\"exist\");\n\n  //   // cy.get(\"a[href='/props/foo']\").should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"foo\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar?\"]').should(\"have.length\", 1).click();\n  //   // cy.contains(\"display-params.svelte\").should(\"exist\");\n  //   // cy.contains(`\"child\": \"bar\"`).should(\"exist\");\n\n  //   // cy.get(\"a\").filter('[href=\"/props/foo\"]').should(\"not.have.class\", \"active\");\n  //   // cy.get(\"a\").filter('[href^=\"/props/bar\"]').should(\"have.class\", \"active\");\n\n  //   cy.clickAndValidateActiveClasses(\"a[href='/props']\", \"active\");\n  // });\n\n  // it(\"can add new todo items\", () => {\n  //   // We'll store our item text in a variable so we can reuse it\n  //   const newItem = \"Feed the cat\";\n\n  //   // Let's get the input element and use the `type` command to\n  //   // input our new list item. After typing the content of our item,\n  //   // we need to type the enter key as well in order to submit the input.\n  //   // This input has a data-test attribute so we'll use that to select the\n  //   // element in accordance with best practices:\n  //   // https://on.cypress.io/selecting-elements\n  //   cy.get(\"[data-test=new-todo]\").type(`${newItem}{enter}`);\n\n  //   // Now that we've typed our new item, let's check that it actually was added to the list.\n  //   // Since it's the newest item, it should exist as the last element in the list.\n  //   // In addition, with the two default items, we should have a total of 3 elements in the list.\n  //   // Since assertions yield the element that was asserted on,\n  //   // we can chain both of these assertions together into a single statement.\n  //   cy.get(\".todo-list li\")\n  //     .should(\"have.length\", 3)\n  //     .last()\n  //     .should(\"have.text\", newItem);\n  // });\n\n  // it(\"can check off an item as completed\", () => {\n  //   // In addition to using the `get` command to get an element by selector,\n  //   // we can also use the `contains` command to get an element by its contents.\n  //   // However, this will yield the <label>, which is lowest-level element that contains the text.\n  //   // In order to check the item, we'll find the <input> element for this <label>\n  //   // by traversing up the dom to the parent element. From there, we can `find`\n  //   // the child checkbox <input> element and use the `check` command to check it.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parent()\n  //     .find(\"input[type=checkbox]\")\n  //     .check();\n\n  //   // Now that we've checked the button, we can go ahead and make sure\n  //   // that the list element is now marked as completed.\n  //   // Again we'll use `contains` to find the <label> element and then use the `parents` command\n  //   // to traverse multiple levels up the dom until we find the corresponding <li> element.\n  //   // Once we get that element, we can assert that it has the completed class.\n  //   cy.contains(\"Pay electric bill\")\n  //     .parents(\"li\")\n  //     .should(\"have.class\", \"completed\");\n  // });\n\n  // context(\"with a checked task\", () => {\n  //   beforeEach(() => {\n  //     // We'll take the command we used above to check off an element\n  //     // Since we want to perform multiple tests that start with checking\n  //     // one element, we put it in the beforeEach hook\n  //     // so that it runs at the start of every test.\n  //     cy.contains(\"Pay electric bill\")\n  //       .parent()\n  //       .find(\"input[type=checkbox]\")\n  //       .check();\n  //   });\n\n  //   it(\"can filter for uncompleted tasks\", () => {\n  //     // We'll click on the \"active\" button in order to\n  //     // display only incomplete items\n  //     cy.contains(\"Active\").click();\n\n  //     // After filtering, we can assert that there is only the one\n  //     // incomplete item in the list.\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Walk the dog\");\n\n  //     // For good measure, let's also assert that the task we checked off\n  //     // does not exist on the page.\n  //     cy.contains(\"Pay electric bill\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can filter for completed tasks\", () => {\n  //     // We can perform similar steps as the test above to ensure\n  //     // that only completed tasks are shown\n  //     cy.contains(\"Completed\").click();\n\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .first()\n  //       .should(\"have.text\", \"Pay electric bill\");\n\n  //     cy.contains(\"Walk the dog\").should(\"not.exist\");\n  //   });\n\n  //   it(\"can delete all completed tasks\", () => {\n  //     // First, let's click the \"Clear completed\" button\n  //     // `contains` is actually serving two purposes here.\n  //     // First, it's ensuring that the button exists within the dom.\n  //     // This button only appears when at least one task is checked\n  //     // so this command is implicitly verifying that it does exist.\n  //     // Second, it selects the button so we can click it.\n  //     cy.contains(\"Clear completed\").click();\n\n  //     // Then we can make sure that there is only one element\n  //     // in the list and our element does not exist\n  //     cy.get(\".todo-list li\")\n  //       .should(\"have.length\", 1)\n  //       .should(\"not.have.text\", \"Pay electric bill\");\n\n  //     // Finally, make sure that the clear button no longer exists.\n  //     cy.contains(\"Clear completed\").should(\"not.exist\");\n  //   });\n  // });\n});\n\n\n\n---\nFile: /demo/cypress/support/commands.ts\n---\n\n/// <reference types=\"cypress\" />\n\n\n\n---\nFile: /demo/cypress/support/e2e.ts\n---\n\nimport \"./commands\";\n\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      /**\n       * Custom command to check if navigation link is active based on href.\n       *\n       * @param selector - The selector to check against\n       * @param attribute - The attribute to check against\n       * @param value - The value to check against\n       *\n       * @example cy.hasClassByAttr('nav a', 'href', '/about', 'foo')\n       */\n      allowedClassesByAttr(selector: string, attribute: string, allowed: string | string[], className: string): Chainable<Element>;\n\n      /**\n       * Custom command to navigate to a specific path and validate the number of active elements.\n       *\n       * @param selector - The selector to check against\n       * @param path - The path to navigate to\n       * @param expected - The expected number of active elements\n       *\n       * @example cy.clickAndValidateActiveClasses(\"a[href='/props/foo']\", \"active\", 1)\n       */\n      clickAndValidateActiveClasses(selector: string, path: string, expected: number): Chainable<Element>;\n    }\n  }\n}\n\nCypress.Commands.add(\"allowedClassesByAttr\", (selector: string, attribute: string, allowed: string | string[], className: string) => {\n  cy.get(selector).then(($elements) => {\n    $elements.each((_, $el) => {\n      cy.wrap($el).then(($el) => {\n        if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n          if (Array.isArray(allowed) && !allowed.includes($el.attr(attribute) || \"\")) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          } else if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {\n            throw new Error(`allowedClassesByAttr: ${attribute}=\"${$el.attr(attribute)}\" has class \"${className}\" (allowed: \"${allowed}\")`);\n          }\n        }\n      });\n    });\n  });\n});\n\nCypress.Commands.add(\"clickAndValidateActiveClasses\", (selector: string, className: string, expected: number) => {\n  cy.get(selector).then(($el) => {\n    if ($el.length !== 1) {\n      throw new Error(`clickAndValidateActiveClasses: ${selector} should match only 1 element, has ${$el.length}`);\n    }\n    cy.get(selector).click();\n    cy.allowedClassesByAttr(\"nav a\", \"href\", $el.attr(\"href\") || \"\", className);\n  });\n});\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-link.svelte\n---\n\n<script lang=\"ts\">\n  import { session } from \"$lib/session.svelte\";\n  import { route, RouteOptions } from \"@mateothegreat/svelte5-router\";\n\n  export type RouteLinkProps = {\n    options?: RouteOptions;\n    href: string;\n    label: string;\n  };\n\n  let { options, href, label }: RouteLinkProps = $props();\n  if (!options) {\n    options = new RouteOptions();\n  }\n\n  if (!options.active) {\n    options.active = {\n      class: [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"]\n    };\n  }\n  if (!options.default) {\n    options.default = {\n      class: [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"]\n    };\n  }\n  if (!options.loading) {\n    options.loading = {\n      class: [\"loading\", \"bg-orange-500\"]\n    };\n  }\n  if (!options.disabled) {\n    options.disabled = {\n      class: [\"disabled\", \"bg-gray-500\"]\n    };\n  }\n\n  if (!options.active.class) {\n    options.active.class = [\"active\", \"bg-indigo-600\", \"text-white\", \"border-indigo-400\"];\n  }\n\n  if (!options.default.class) {\n    options.default.class = [\"inactive\", \"text-slate-300\", \"border-slate-500/50\"];\n  }\n  if (!options.loading.class) {\n    options.loading.class = [\"loading\", \"bg-orange-500\"];\n  }\n  if (!options.disabled.class) {\n    options.disabled.class = [\"disabled\", \"bg-gray-500\"];\n  }\n</script>\n\n<a\n  use:route={options}\n  href={session.mode === \"hash\" ? `/#${href}` : href}\n  class=\"duration-400 flex items-center rounded-sm border-2 px-2.5 py-0.5 text-sm transition-all hover:border-green-300 hover:bg-green-600 hover:text-white\">\n  {#if session.mode === \"hash\"}\n    <span>/#</span>\n    <span>/{label.startsWith(\"/\") ? label.slice(1) : label}</span>\n  {:else}\n    <span>\n      {label}\n    </span>\n  {/if}\n</a>\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-title.svelte\n---\n\n<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowDown, ArrowRight, ArrowRightFromLine, StopCircle } from \"lucide-svelte\";\n  import FileLink from \"../file-link.svelte\";\n\n  export type RouteTitleProps = {\n    router?: RouterInstance;\n    route?: RouteResult;\n    file?: string;\n    content?: any;\n    end?: boolean;\n  };\n\n  let { router = $bindable(), route, file, content, end }: RouteTitleProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex items-center gap-3 rounded-md bg-black/50 p-1.5 px-2 border-2\">\n    {#if router}\n      <div class=\"flex flex-wrap items-center rounded-sm bg-gray-800 px-1.5 py-0.5 text-sm text-slate-500\">\n        <ArrowRightFromLine class=\"h-4 w-4 text-green-400 mr-1\" />\n        {router.config.id}\n        {#if router.navigating}\n          <span class=\"px-1 py-0.5 text-red-400\">(hooks firing)</span>\n        {:else}\n          <span class=\"px-1 py-0.5 text-slate-600\">(idle)</span>\n        {/if}\n        routed the path\n        <span class=\"px-1 py-0.5 text-green-400\">\n          {route?.absolute?.()}\n        </span>\n        and nesting&nbsp;\n        {#if end}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-red-400\">stopped</span>\n            <StopCircle class=\"h-4 w-4 text-red-400\" />\n          </span>\n        {:else}\n          <span class=\"flex items-center gap-1 whitespace-nowrap\">\n            <span class=\"text-green-400\">continued</span>\n            <ArrowDown class=\"h-4 w-4 text-green-400\" />\n          </span>\n        {/if}\n      </div>\n    {/if}\n    <ArrowRight class=\"h-4 w-4 text-slate-500\" />\n    <FileLink {file} />\n  </div>\n  {#if content}\n    <div class=\"p-2\">\n      {#if typeof content === \"string\"}\n        <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n          <div class=\"max-w-3xl text-sm text-slate-500\">\n            {content}\n          </div>\n        </div>\n      {:else}\n        <div class=\"flex items-center\">\n          {@render content()}\n        </div>\n      {/if}\n    </div>\n  {/if}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/routes/route-wrapper.svelte\n---\n\n<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Anchor } from \"lucide-svelte\";\n  import type { RouteLinkProps } from \"./route-link.svelte\";\n  import RouteLink from \"./route-link.svelte\";\n  import type { RouteTitleProps } from \"./route-title.svelte\";\n  import RouteTitle from \"./route-title.svelte\";\n\n  export type RouteWrapperProps = {\n    router?: RouterInstance;\n    name: string;\n    route?: RouteResult;\n    end?: boolean;\n    title: RouteTitleProps;\n    links: RouteLinkProps[];\n    children?: any;\n  };\n\n  let { router = $bindable(), name, route, title, links, children, end = $bindable() }: RouteWrapperProps = $props();\n</script>\n\n<div class=\"flex flex-col gap-3 border-2 rounded-md h-full p-2.5\">\n  <div class=\"flex flex-col gap-4\">\n    <RouteTitle\n      {router}\n      {route}\n      {end}\n      file={title.file}\n      content={title.content} />\n    <div class=\"flex w-fit items-center gap-2 rounded-md border-2 border-slate-900/80 bg-slate-700/80 p-1.5\">\n      <p class=\"flex items-center gap-1 text-sm text-slate-300\">\n        <Anchor class=\"h-4 w-4 text-indigo-500\" />\n        <span class=\"text-pink-400\">{router?.config.id}</span>\n        routes:\n      </p>\n      {#each links as link}\n        <RouteLink {...link} />\n      {/each}\n    </div>\n  </div>\n  <div class=\"flex-1\">\n    {@render children?.()}\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/badge.svelte\n---\n\n<script lang=\"ts\">\n  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n\n  let {\n    icon,\n    text,\n    variant = \"info\",\n    class: className,\n    children\n  }: {\n    icon?: any;\n    text?: string;\n    variant?: \"info\" | \"success\" | \"warning\" | \"error\";\n    class?: string;\n    children?: Snippet;\n  } = $props();\n\n  const variants = {\n    info: {\n      classes: \"bg-blue-900 text-white\",\n      icon: InfoIcon\n    },\n    success: {\n      classes: \"bg-green-600/80 text-white\",\n      icon: CheckCircleIcon\n    },\n    warning: {\n      classes: \"bg-yellow-200/80 text-black\",\n      icon: AlertTriangleIcon\n    },\n    error: {\n      classes: \"bg-red-900/80 text-white\",\n      icon: XCircleIcon\n    }\n  };\n\n  let Icon = icon ?? variants[variant].icon;\n</script>\n\n<span\n  class=\"inline-flex items-center gap-1.5 rounded-sm p-2 text-sm font-medium {variants[variant].classes} {className}\">\n  {#if Icon}\n    <Icon class=\"inline-block h-5 w-5\" />\n  {/if}\n  {text}\n  {@render children?.()}\n</span>\n\n\n\n---\nFile: /demo/src/lib/components/code.svelte\n---\n\n<script lang=\"ts\">\n  import { transformerColorizedBrackets } from \"@shikijs/colorized-brackets\";\n  import { Code2 } from \"lucide-svelte\";\n  import { createHighlighter, type Highlighter } from \"shiki\";\n  import { onMount, type Snippet } from \"svelte\";\n  import FileLink from \"./file-link.svelte\";\n\n  let {\n    title,\n    file,\n    class: className,\n    children,\n    language = \"typescript\",\n    theme = \"github-dark-dimmed\"\n  }: {\n    title?: string;\n    file?: string;\n    class?: string;\n    children?: Snippet;\n    language?: string;\n    theme?: string;\n  } = $props();\n\n  // State for the highlighted HTML content\n  let highlightedContent = $state<string>(\"\");\n  // State for the reference to the hidden temporary element\n  let tempElement: HTMLElement | undefined = $state(undefined);\n  // State for the Shiki highlighter instance\n  let shikiHighlighter: Highlighter | null = $state(null);\n\n  /**\n   * Initializes the Shiki highlighter instance once when the component mounts.\n   */\n  onMount(async () => {\n    shikiHighlighter = await createHighlighter({\n      themes: [theme],\n      langs: [\"typescript\", \"svelte\", \"html\", \"css\", \"json\"]\n    });\n  });\n\n  /**\n   * Reactive effect that updates the syntax highlighting whenever\n   * the children, language, or highlighter instance changes.\n   */\n  $effect(() => {\n    if (!shikiHighlighter || !children || !tempElement) {\n      if (!children) {\n        highlightedContent = \"\";\n      }\n      return;\n    }\n\n    /**\n     * At this point, Svelte has rendered the output of `children()` into `tempElement`\n     * due to the `{@render children()}` directive in the hidden div.\n     * We can now extract the raw text content from it.\n     */\n    const codeContent = (tempElement.textContent || \"\").trim();\n\n    if (codeContent) {\n      try {\n        // Convert the raw code string to HTML using Shiki.\n        highlightedContent = shikiHighlighter.codeToHtml(codeContent, {\n          lang: language,\n          theme: theme,\n          transformers: [transformerColorizedBrackets()]\n        });\n      } catch (error) {\n        console.error(\"Shiki highlighting error:\", error, { language, codeContent });\n        // Fallback to showing raw code (escaped) if highlighting fails.\n        highlightedContent = `<pre class=\"shiki-fallback\"><code>${escapeHtml(codeContent)}</code></pre>`;\n      }\n    } else {\n      // Clear highlighted content if there's no text content.\n      highlightedContent = \"\";\n    }\n  });\n\n  /**\n   * Helper function to escape HTML special characters.\n   * Used for the fallback when Shiki highlighting fails.\n   */\n  function escapeHtml(unsafe: string): string {\n    return unsafe\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\")\n      .replace(/\"/g, \"&quot;\")\n      .replace(/'/g, \"&#039;\");\n  }\n</script>\n\n<div class=\"code-block-container flex flex-col rounded-md border-2 border-slate-700 bg-black/80 {className}\">\n  <div class=\"header flex items-center justify-between bg-zinc-900/70 text-sm p-1.5 px-3 flex-shrink-0\">\n    <p class=\"flex items-center gap-1.5 font-mono text-xs text-slate-300\">\n      <Code2 class=\"h-4 w-4 text-orange-500 shrink-0\" />\n      {#if title}\n        <span>{title}</span>\n      {:else}\n        <span>Code</span>\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n\n  <!--\n    Temporary hidden element.\n    The `children` snippet is rendered here by Svelte using `{@render children()}`.\n    This component's `$effect` then reads `tempElement.textContent` to get the\n    raw string representation of the rendered children for Shiki to process.\n  -->\n  <div\n    bind:this={tempElement}\n    style=\"display: none;\"\n    aria-hidden=\"true\">\n    {#if children}\n      {@render children()}\n    {/if}\n  </div>\n\n  {#if highlightedContent}\n    <div class=\"p-2.5 text-[13px] leading-[18px] overflow-auto bg-[#0a0a0a] flex-1 min-h-0\">\n      {@html highlightedContent}\n    </div>\n  {:else if children}\n    <pre class=\"fallback-pre p-3 font-mono text-sm text-slate-200 overflow-auto flex-1 min-h-0\">\n      {@render children()}\n    </pre>\n  {:else}\n    <div class=\"p-3 text-slate-500 text-sm font-mono\">Loading syntax highlighter...</div>\n  {/if}\n</div>\n\n<style>\n  :global(.shiki) {\n    background: #090909 !important;\n  }\n</style>\n\n\n\n---\nFile: /demo/src/lib/components/container.svelte\n---\n\n<script lang=\"ts\">\n  import FileLink from \"./file-link.svelte\";\n\n  let { title, file, children }: { title?: string; file?: string; children: any } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1 rounded-md border-2 border-slate-700 bg-gray-900 text-slate-400\">\n  <div class=\"flex items-center justify-between bg-slate-800 p-2 text-sm text-slate-500\">\n    <p class=\"flex items-center gap-2 font-mono\">\n      {#if title}\n        &lt;{title} /&gt;\n      {/if}\n    </p>\n    {#if file}\n      <FileLink {file} />\n    {/if}\n  </div>\n  {@render children()}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/default.svelte\n---\n\n<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n  import Badge from \"./badge.svelte\";\n\n  let { class: className, children }: { class?: string; children?: Snippet } = $props();\n</script>\n\n<div class=\"flex flex-col gap-4 rounded-md border-4 border-slate-900/80 p-4 text-center text-gray-400 {className}\">\n  {#if children}\n    {@render children()}\n  {:else}\n    <p>\n      <Badge\n        variant=\"info\"\n        icon={BadgeInfo}>\n        There was no path provided to the router, so the default route was used (declared as a snippet).\n      </Badge>\n    </p>\n    Click on a link above to see the different effects!\n  {/if}\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/file-link.svelte\n---\n\n<script lang=\"ts\">\n  import { FileCode2, GithubIcon } from \"lucide-svelte\";\n\n  let { file } = $props();\n</script>\n\n<div class=\"flex gap-1\">\n  <div class=\"flex flex-1 items-center text-slate-500\">\n    <FileCode2 class=\"h-4 w-4\" />\n  </div>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-orange-400 hover:font-medium hover:text-indigo-400\">\n    {file}\n  </a>\n  <a\n    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}\n    target=\"_blank\"\n    class=\"flex cursor-pointer items-center gap-1 text-center text-sm text-slate-500 hover:font-medium hover:text-indigo-400\">\n    view source\n    <GithubIcon class=\"h-4 w-4\" />\n  </a>\n</div>\n\n\n\n---\nFile: /demo/src/lib/components/inline-code.svelte\n---\n\n<script lang=\"ts\">\n  export type InlineCodeProps = {\n    text: string;\n    class?: string;\n  };\n\n  let { text, class: className }: InlineCodeProps = $props();\n\n  if (!className) {\n    className = \"text-green-400 bg-black/50\";\n  }\n</script>\n\n<span class=\"whitespace-nowrap rounded-md p-1 px-2 font-mono text-sm {className}\">{text}</span>\n\n\n\n---\nFile: /demo/src/lib/default-route-config.ts\n---\n\nimport { StatusCode, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport NotFound from \"$routes/not-found.svelte\";\n\n/**\n * Surface a reusable configuration for routers to import\n * and apply to their router instances:\n *\n * @example\n * ```ts\n * <script lang=\"ts\">\n *   import { RouteConfig } from \"@mateothegreat/svelte5-router\";\n *   import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n *\n *   const routes: RouteConfig[] = [\n *     {\n *       path: \"/home\",\n *       component: Home\n *     }\n *   ];\n * </script>\n *\n * <Router\n *   id=\"my-main-router\"\n *   {routes}\n *   {...myDefaultRouterConfig} />\n * ```\n */\nexport const myDefaultRouterConfig = {\n  statuses: {\n    /**\n     * You can use a function to return a new route or a promise that\n     * resolves to a new route:\n     */\n    [StatusCode.NotFound]: (result: RouteResult) => {\n      console.log(result);\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n    /**\n     * You can also use an object to return a new route while having access\n     * to the path and querystring:\n     *\n     * [StatusCode.NotFound]: (path: RouteResult) => ({\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }),\n     *\n     *\n     * or simply return an object with a component and props:\n     *\n     * [StatusCode.NotFound]: {\n     *   component: NotFound,\n     *   props: {\n     *     somethingExtra: new Date().toISOString()\n     *   }\n     * }\n     */\n  }\n};\n\n\n\n---\nFile: /demo/src/lib/router-history.ts\n---\n\nimport type { Route } from \"@mateothegreat/svelte5-router\";\n\nexport const history = $state<Route[]>([]);\n\nexport const appendHistory = (route: Route) => {\n  history.push(route);\n};\n\n\n\n---\nFile: /demo/src/lib/session.svelte.ts\n---\n\nlet _state: {\n  mode: \"hash\" | \"path\";\n} = $state({\n  mode: (localStorage.getItem(\"mode\") as \"hash\" | \"path\") || \"path\"\n});\n\nexport const session = {\n  set mode(value: \"hash\" | \"path\") {\n    localStorage.setItem(\"mode\", value);\n    _state = {\n      ..._state,\n      mode: value\n    };\n    if (value === \"hash\") {\n      window.history.pushState({}, \"\", `/#/`);\n    } else {\n      window.history.pushState({}, \"\", \"/\");\n    }\n  },\n  get mode() {\n    return _state.mode;\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/extras/dump.svelte\n---\n\n<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/extras/extras.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { onDestroy } from \"svelte\";\n  import PassingDownProps from \"./passing-down-props.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  let randoms = $state({\n    float: (Math.random() * 1000).toFixed(2),\n    int: (Math.random() * 1000).toFixed(0),\n    string: (Math.random() * 1000).toFixed(2).toString()\n  });\n\n  const interval = setInterval(() => {\n    randoms.float = (Math.random() * 1000).toFixed(2);\n    randoms.int = (Math.random() * 1000).toFixed(0);\n    randoms.string = (Math.random() * 1000).toFixed(2).toString();\n  }, 750);\n\n  onDestroy(() => {\n    clearInterval(interval);\n  });\n\n  const routes: RouteConfig[] = [\n    {\n      component: overview\n    },\n    {\n      path: \"passing-down-props\",\n      component: PassingDownProps,\n      props: {\n        route: \"passing-down-props\"\n      },\n      hooks: {\n        pre: () => {\n          console.log(\"pre\");\n          return true;\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet overview()}\n  <div class=\"p-2\">\n    <div class=\"flex flex-col items-center gap-2 text-center text-slate-400\">\n      <div class=\"flex max-w-3xl flex-col gap-2 text-sm text-slate-500\">\n        <p class=\"text-fuchsia-500\">Extra & cool stuff can be demoed here.</p>\n        <p>Click a route above to see the different effects!</p>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"extras\"\n  {route}\n  title={{\n    file: \"src/routes/extras/extras.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"passing-down-props\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras\"\n    bind:instance={router}\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/extras/passing-down-props.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import Dump from \"./dump.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  const routes: RouteConfig[] = [\n    {\n      component: Dump,\n      props: {\n        foo: \"bar\",\n        baz: {\n          awesome: true\n        }\n      }\n    }\n  ];\n</script>\n\n<RouteWrapper\n  {router}\n  name=\"passing-down-props\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/extras/passing-down-props.svelte\"\n  }}\n  links={[\n    {\n      href: \"/extras/passing-down-props\",\n      label: \"default path\"\n    }\n  ]}>\n  <Router\n    basePath=\"/extras/passing-down-props\"\n    bind:instance={router}\n    myAdditionalProp=\"I was added to the <Router/> component directly.\"\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/hash/hash.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  console.log(location.hash);\n</script>\n\n<a\n  use:route\n  href=\"/hash/#b?test=4\">\n  b\n</a>\n\n<a\n  use:route\n  href=\"/hash/#a?test=123\">\n  a\n</a>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte\n---\n\n<script>\n  import Container from \"$lib/components/container.svelte\";\n</script>\n\n<Container\n  title={\"Level_3\"}\n  file=\"src/routes/nested/level-1/level-2/level-3/level-3.svelte\">\n  <div class=\"flex flex-col gap-3 bg-green-500 p-4 text-center font-medium text-white\">\n    You've reached the deepest level of nested routing (level-3)!\n  </div>\n</Container>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-2/level-2.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_3 from \"./level-3/level-3.svelte\";\n\n  const routes: Route[] = [\n    {\n      path: \"level-3\",\n      component: Level_3,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-2.svelte route)`);\n          return true;\n        }\n      }\n    },\n    {\n      component: snippet\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-2/level-2.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-2\"\n  end={true}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-2/level-2.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2/level-3\",\n      label: \"/nested/level-1/level-2/level-3\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-2-router\"\n    basePath=\"/nested/level-1/level-2\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/nested/level-1/level-1.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import Level_2 from \"./level-2/level-2.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route, foo } = $props();\n\n  /**\n   * Demonstrate how support for additional props is working.\n   */\n  console.log(\"additionalProps.foo in ../nested.svelte is passed to this component as:\", foo);\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/level-1/level-1.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested/level-1\"\n  {end}\n  {route}\n  title={{\n    file: \"src/routes/nested/level-1/level-1.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested/level-1\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1/level-2\",\n      label: \"/nested/level-1/level-2\"\n    }\n  ]}>\n  <Router\n    id=\"nested-level-1-router\"\n    basePath=\"/nested/level-1\"\n    bind:instance={router}\n    routes={[\n      {\n        path: \"level-2\",\n        component: Level_2,\n        hooks: {\n          pre: () => {\n            console.log(`Route \"/nested/level-1/level-2\" matched (I'm a pre hook in the level-1.svelte route)`);\n            return true;\n          }\n        }\n      },\n      /**\n       * Default routes can be placed anywhere in the routes array\n       * and will be matched if no other routes match regardless of\n       * their position in the array:\n       */\n      {\n        component: snippet\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/nested/nested.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Level_1 from \"./level-1/level-1.svelte\";\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"level-1\",\n      component: Level_1,\n      hooks: {\n        pre: () => {\n          console.log(`Route \"/nested/level-1\" matched (I'm a pre hook in the nested.svelte route)`);\n          return true;\n        }\n      }\n    }\n  ];\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(false);\n  $effect(() => {\n    end = router.current?.result.path.condition === \"default-match\";\n  });\n\n  const additionalProps = {\n    foo: {\n      bar: \"baz\"\n    }\n  };\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/nested/nested.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/nested\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/nested/nested.svelte\",\n    content:\n      \"This demo shows how to use nested routing with the router where multiple routers can be nested within each other.\"\n  }}\n  links={[\n    {\n      href: \"/nested\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/nested/level-1\",\n      label: \"/nested/level-1\"\n    }\n  ]}>\n  <Router\n    id=\"nested-router\"\n    basePath=\"/nested\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes}\n    {...additionalProps} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/custom-not-found.svelte\n---\n\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"custom-not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/display-params.svelte\n---\n\n<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<Code\n  title=\"params.route value:\"\n  file=\"src/routes/props/display-params.svelte\">\n  <div>{JSON.stringify(route, null, 2)}</div>\n</Code>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/paths-and-params.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { getStatusByValue, RouterInstance, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router/route.svelte\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { onDestroy } from \"svelte\";\n  import CustomNotFound from \"./custom-not-found.svelte\";\n  import DisplayParams from \"./display-params.svelte\";\n  import QuerystringMatching from \"./querystring-matching.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  let randoms = $state({\n    float: (Math.random() * 1000).toFixed(2),\n    int: (Math.random() * 1000).toFixed(0),\n    string: (Math.random() * 1000).toFixed(2).toString()\n  });\n\n  const interval = setInterval(() => {\n    randoms.float = (Math.random() * 1000).toFixed(2);\n    randoms.int = (Math.random() * 1000).toFixed(0);\n    randoms.string = (Math.random() * 1000).toFixed(2).toString();\n  }, 750);\n\n  onDestroy(() => {\n    clearInterval(interval);\n  });\n\n  const routes: RouteConfig[] = [\n    /**\n     * This route will be used if there is no matching routes we\n     * define below:\n     */\n    {\n      component: snippet\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?number=2&number-as-string=2\n     *   ✅ /paths-and-params/query-matcher?number=2.1&number-as-string=2.345\n     *   ❌ /paths-and-params/query-matcher?number=2&number-as-string=two\n     */\n    {\n      name: \"match-number-and-string\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"number\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        float: /^([\\d.]+)$/,\n        /**\n         * The \"number-as-string\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number or a string that can be converted to a number\n         */\n        string: \"123\"\n      }\n    },\n    /**\n     * For this route, use querystring to match against the current location.search value:\n     *\n     *   ✅ /paths-and-params/query-matcher?pagination=1,10\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=1&company=1234567\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=123\n     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=1234567\n     *   ❌ /paths-and-params/query-matcher?pagination=1,&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=&company=123\n     *   ❌ /paths-and-params/query-matcher?pagination=2,3,4\n     *   ❌ /paths-and-params/query-matcher?pagination=bad-value\n     */\n    {\n      name: \"match-pagination\",\n      path: \"query-matcher\",\n      component: QuerystringMatching,\n      querystring: {\n        /**\n         * The \"pagination\" querystring parameter:\n         *\n         *   - ✅ must be present\n         *   - ✅ must be a number\n         *   - ❔ and then be followed by an optional \"cursor\" parameter:\n         *     - ✅ it must have a comma delimiter\n         *     - ✅ it must be a string of alphanumeric characters only\n         */\n        pagination: /^(?<page>\\d+)(?:,(?<cursor>\\d+))?$/,\n        /**\n         * The \"company\" querystring parameter is optional, but if present:\n         *\n         *   - ✅ can be empty\n         *   - ✅ must be a single number\n         */\n        company: /^(\\d+)?$/\n      },\n      props: {\n        metadata: {\n          src: \"paths-and-params.svelte\"\n        }\n      }\n    },\n    /**\n     * This route will match any path and pass the pattern groups\n     * as an object to the component that is passed in $props().\n     *\n     * The component will access the params using $props() and the\n     * property \"child\" will contain the value extracted from the path.\n     */\n    {\n      path: \"extensions/(?<extension>.*)/(?<page>[^/]+)(?:/(?<rest>.*))?\",\n      component: DisplayParams\n    },\n    {\n      name: \"fancy-regex-capture-group\",\n      path: \"(foo|bar).*\",\n      component: DisplayParams,\n      props: {\n        randomId: Math.random().toString(36).substring(2, 15),\n        someUserStuff: {\n          username: \"mateothegreat\",\n          userAgent: navigator.userAgent\n        }\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/paths-and-params/paths-and-params.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/paths-and-params\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/paths-and-params/paths-and-params.svelte\",\n    content: \"This demo shows how to pass values downstream to the component that is rendered.\"\n  }}\n  links={[\n    {\n      href: \"/paths-and-params\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/paths-and-params/foo\",\n      label: \"foo\"\n    },\n    {\n      href: \"/paths-and-params/query-matcher?pagination=2,23&company=123\",\n      label: \"query-matcher?pagination=2,23&company=123\"\n    },\n    {\n      href: `/paths-and-params/query-matcher?string=123&float=${randoms.float}`,\n      label: `query-matcher?string=123&float=${randoms.float}`\n    }\n  ]}>\n  <Router\n    id=\"props-router\"\n    basePath=\"/paths-and-params\"\n    bind:instance={router}\n    {routes}\n    hooks={{\n      /**\n       * You could use a global auth guard here to run before every route:\n       *\n       * hooks={{\n       *   pre: (route: Routed) => {\n       *     if (!isAuthenticated()) {\n       *       console.warn(\"user is not authenticated, redirecting to login\", route);\n       *       return {\n       *         component: NotGonnaMakeIt,\n       *       };\n       *     }\n       *   }\n       * }}\n       *\n       * You could also use a global error handler here to run after every route:\n       *\n       * hooks={{\n       *   post: [\n       *     (route: Routed) => {\n       *       console.info(\"do some more work here\", route);\n       *       return true;\n       *     },\n       *     someLogMethod,\n       *     finalMethod,\n       *   ]\n       * }}\n       */\n    }}\n    statuses={{\n      [StatusCode.NotFound]: (result: RouteResult) => {\n        console.warn(`the path \"${result.result.path.original}\" could not be found :(`, {\n          /**\n           * You could use the status name to make something pretty:\n           */\n          status: getStatusByValue(StatusCode.NotFound),\n          /**\n           * You could also use the status code to something more dynamic:\n           */\n          code: StatusCode.NotFound\n        });\n        /**\n         * Now, we're going to return a new route that will be rendered by the router:\n         */\n        return {\n          component: CustomNotFound,\n          /**\n           * You can pass props to the component that is rendered if you need to\n           * share some extra information:\n           */\n          props: {\n            src: \"props.svelte\"\n          }\n        };\n      }\n    }} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/paths-and-params/querystring-matching.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n\n  let { route } = $props();\n</script>\n\n{#snippet content()}\n  The route uses the pattern <InlineCode text=\"/\\/?<child>.*)/\" /> which captures everything after the base path and passes\n  it to the component as the `params` prop.\n{/snippet}\n\n<div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n  <div class=\"flex flex-col gap-5\">\n    <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n      <InlineCode text=\"querystring\" />\n      match multiple values\n      <InlineCode text=\"RegExp\" />\n      <InlineCode text=\"number\" />\n      <InlineCode text=\"string\" />\n      <InlineCode text=\"boolean\" />\n      <InlineCode text=\"array\" />\n    </div>\n    <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n      <Badge\n        variant=\"warning\"\n        class=\"w-fit\">\n        Matching will occur if <em><strong>all</strong></em>\n        querystring keys and values match what was provided in the `querystring` configuration option.\n      </Badge>\n      <div class=\" gap-1\">\n        This demo shows how to use the route's\n        <InlineCode text=\"querystring\" />\n        configuration option to match against the current\n        <InlineCode text=\"location.search\" />\n        value passed in by the browser.\n      </div>\n      <Badge\n        variant=\"success\"\n        class=\"w-fit\">\n        There are 2 potential matches for the path \"/paths-and-params/query-matcher\". Notice how the active route is\n        highlighted in green for this route only + the querystring matching.\n      </Badge>\n    </div>\n  </div>\n  <div class=\"flex flex-col gap-6 border-2 bg-gray-900/60 rounded-lg p-4 border-slate-700\">\n    <Code\n      title=\"$props().route.result value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div>{JSON.stringify(route.result, null, 2)}</div>\n    </Code>\n    <Code\n      title=\"$props().route.route value:\"\n      file=\"src/routes/paths-and-params/querystring-matching.svelte\">\n      <div class=\"text-indigo-400\">{JSON.stringify(route.route, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/dump.svelte\n---\n\n<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n\n  import { Lightbulb } from \"lucide-svelte\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-3\">\n  <Code\n    title=\"usage:\"\n    file=\"src/routes/extras/dump.svelte\"\n    language=\"svelte\"\n    class=\"flex-1\">\n    {`<script lang=\"ts\">\n  let { route, ...rest } = $props();\n  console.log(route, rest);\n</script>`}\n  </Code>\n\n  <div class=\"flex gap-3\">\n    <Code\n      title=\"$props().route\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(route, null, 2)}</div>\n    </Code>\n\n    <Code\n      title=\"$props().rest\"\n      language=\"json\"\n      class=\"flex-1\">\n      <div>{JSON.stringify(rest, null, 2)}</div>\n    </Code>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/output.svelte\n---\n\n<div class=\"flex flex-col gap-4 p-4\">\n  <div>\n    <h1 class=\"text-xl font-semibold text-slate-200\">Nested Paths</h1>\n    <p class=\"text-slate-400\">\n      This example will match any path that starts with `/path/path/path` and can be nested further.\n    </p>\n  </div>\n  <div class=\"bg-slate-800/50 rounded-lg p-3\">\n    <h3 class=\"text-sm font-medium text-slate-300 mb-2\">Route Props:</h3>\n    <pre class=\"text-xs text-slate-400 overflow-auto\">{JSON.stringify(props, null, 2)}</pre>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/parameter-extraction.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  import Code from \"$lib/components/code.svelte\";\n  let { route: r, ...rest } = $props();\n</script>\n\n<div class=\"flex flex-col gap-1\">\n  <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">Parameter Extraction</h1>\n  <div class=\"text-slate-500 text-sm\">\n    <p>\n      Given the following route config, everything after the \"/parameter-extraction\" will be available as a named\n      parameter. This means that the following paths will be matched:\n    </p>\n    <ul class=\"list-disc list-inside\">\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/foo\">\n          parameter-extraction/foo\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/bar\">\n          parameter-extraction/bar\n        </a>\n      </li>\n      <li>\n        <a\n          use:route\n          href=\"/patterns/parameter-extraction/baz\">\n          parameter-extraction/baz\n        </a>\n      </li>\n    </ul>\n  </div>\n</div>\n\n<div class=\"flex gap-3\">\n  <Code\n    title=\"RouteConfig:\"\n    language=\"typescript\"\n    class=\"flex-1\">\n    {`  const routes: RouteConfig[] = [\n  {\n    path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n    component: ParameterExtraction\n  }\n];`}\n  </Code>\n  <Code\n    title=\"$props().route.result.path.params\"\n    language=\"json\"\n    class=\"flex-1\">\n    <div>{JSON.stringify(r.result.path.params, null, 2)}</div>\n  </Code>\n</div>\n\n\n\n---\nFile: /demo/src/routes/patterns/patterns.svelte\n---\n\n<script lang=\"ts\">\n  import { route, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import type { TableColumn } from \"@mateothegreat/svelte5-table\";\n  import { DropinTable } from \"@mateothegreat/svelte5-table\";\n  import { CirclePlay, Lightbulb, MousePointerClick } from \"lucide-svelte\";\n  import { writable, type Writable } from \"svelte/store\";\n  import Dump from \"./dump.svelte\";\n  import ParameterExtraction from \"./parameter-extraction.svelte\";\n\n  type Component = {\n    name: string;\n    description: string;\n    path: string;\n  };\n\n  const columns: TableColumn[] = [\n    {\n      field: \"name\",\n      header: \"Routing Pattern\"\n    },\n    {\n      field: \"description\",\n      header: \"Description\"\n    },\n    {\n      field: \"actions\",\n      header: customHeader,\n      class: \"w-[300px]\",\n      renderer: action\n    }\n  ];\n\n  const components: Writable<Component[]> = writable([\n    {\n      name: \"Default Route\",\n      description: \"If the path is empty, the route will be matched otherwise evaluation will continue.\",\n      path: \"/default-route\"\n    },\n    {\n      name: \"Single Path\",\n      description: \"This example will match any path that starts with `/path`.\",\n      path: \"/single-path\"\n    },\n    {\n      name: \"Nested Paths\",\n      description: \"This example will match any path that starts with `/path/path/path` and can be nested further.\",\n      path: \"/nested-paths\"\n    },\n    {\n      name: \"Parameter Extraction\",\n      description: \"Combine arbitrary paths and extractable parameters.\",\n      path: \"/parameter-extraction\"\n    },\n    {\n      name: \"Named Parameters\",\n      description:\n        \"This example will match any path that starts with `/path/path/path/path` and can be nested further.\",\n      path: \"/named-parameters\"\n    }\n  ]);\n\n  let selections = writable([]);\n  const routes: RouteConfig[] = [\n    {\n      path: \"default-route\",\n      component: Dump\n    },\n    {\n      path: \"single-path\",\n      component: Dump\n    },\n    {\n      path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n      component: ParameterExtraction\n    }\n  ];\n</script>\n\n{#snippet customHeader()}\n  <div class=\"flex items-center gap-1 pl-2\">\n    Navigate to Demo\n    <CirclePlay class=\"h-4 w-4 text-green-500\" />\n  </div>\n{/snippet}\n\n{#snippet action(row: any)}\n  <div class=\"flex flex-1 ml-2\">\n    <a\n      use:route\n      href={`/patterns${row.path}`}\n      class=\"bg-gray-800/50 flex items-center gap-1 rounded-sm border-2 border-purple-700 px-2 py-1 text-sm text-slate-500 transition-all hover:border-green-300 hover:bg-slate-200/20 hover:text-white duration-400\">\n      <MousePointerClick class=\"h-4 w-4\" />\n      <span class=\"text-green-500 flex items-center gap-0.5\">\n        goto(\"\n        <span class=\"text-sky-400\">\n          {row.path}\n        </span>\n        \")\n      </span>\n    </a>\n  </div>\n{/snippet}\n\n<div class=\"flex flex-col gap-4\">\n  <div class=\"flex flex-col gap-1\">\n    <h1 class=\"text-lg font-semibold text-slate-300 flex items-center gap-1\">\n      <Lightbulb class=\"h-5 w-5 text-cyan-400\" />\n      Routing Patterns\n    </h1>\n    <p class=\"text-slate-500 text-sm\">This page contains an assortment of routing patterns to help you get started.</p>\n  </div>\n  <div class=\"bg-slate-700/20 text-slate-600 py-2 border-2 border-slate-700 rounded-lg\">\n    <DropinTable\n      {columns}\n      data={$components}\n      bind:selections />\n  </div>\n  <div class=\"flex flex-1 flex-col gap-2\">\n    <Router\n      basePath=\"/patterns\"\n      myExtraRouterProp={{\n        calledFrom: \"patterns <Router />\"\n      }}\n      {routes} />\n  </div>\n</div>\n\n<style>\n  :global(td) {\n    padding: 8px !important;\n  }\n</style>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/auth-guard-fast.ts\n---\n\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardFast = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/auth-guard-slow.ts\n---\n\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n\nexport const authGuardSlow = async (route?: RouteResult): Promise<boolean> => {\n  console.log(\n    `🔍 route.hooks[\"pre\"] has been triggered for %c${route?.route.absolute()}`,\n    \"color: #F9A710; font-weight: bold;\"\n  );\n\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  console.log(\"🚧 %cdoing some work here...\", \"color: #2196f3; font-weight: bold; font-style: italic;\");\n\n  await new Promise((resolve) => setTimeout(resolve, 2000));\n\n  if (!localStorage.getItem(\"token\")) {\n    console.log(\"%c❌ redirecting to denied\", \"color: #f44336; font-weight: bold; font-size: 1.1em;\");\n    goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n    return false;\n  }\n\n  // If the user is logged in, return true so that the router can\n  // continue it's navigation to the requested route.\n  console.log(\n    `%c✅ allowed to continue to %c${route?.absolute()}`,\n    \"color: #53FF4D; font-weight: bold;\",\n    \"color: #F9A710; font-weight: bold;\"\n  );\n  console.log(\"%c✅ returning true\", \"background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;\");\n\n  return true;\n};\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/balance.svelte\n---\n\n<script lang=\"ts\">\n  import { ArrowDownRight, ArrowUpRight, DollarSign } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake account data\n  const accountBalance = (Math.random() * 100000).toFixed(2);\n  const transactions = [\n    { id: 1, type: \"credit\", amount: 2500.0, description: \"Salary Deposit\", date: \"2024-03-15\" },\n    { id: 2, type: \"debit\", amount: 85.5, description: \"Grocery Store\", date: \"2024-03-14\" },\n    { id: 3, type: \"debit\", amount: 125.0, description: \"Electric Bill\", date: \"2024-03-13\" },\n    { id: 4, type: \"credit\", amount: 500.0, description: \"Freelance Payment\", date: \"2024-03-12\" },\n    { id: 5, type: \"debit\", amount: 45.99, description: \"Online Shopping\", date: \"2024-03-11\" }\n  ];\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-8 rounded-xl bg-blue-600 p-8 text-white shadow-lg\">\n    <h2 class=\"mb-2 text-xl\">Current Balance</h2>\n    <div class=\"flex items-center gap-2\">\n      <DollarSign size={32} />\n      <span class=\"text-4xl font-bold\">{accountBalance}</span>\n    </div>\n  </div>\n\n  <div class=\"rounded-xl bg-white p-6 shadow-lg\">\n    <h3 class=\"mb-4 text-xl font-semibold text-gray-800\">Recent Transactions</h3>\n    <div class=\"space-y-4\">\n      {#each transactions as transaction}\n        <div\n          class=\"flex items-center justify-between rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-50\">\n          <div class=\"flex items-center gap-3\">\n            {#if transaction.type === \"credit\"}\n              <div class=\"rounded-full bg-green-100 p-2\">\n                <ArrowUpRight class=\"h-5 w-5 text-green-600\" />\n              </div>\n            {:else}\n              <div class=\"rounded-full bg-red-100 p-2\">\n                <ArrowDownRight class=\"h-5 w-5 text-red-600\" />\n              </div>\n            {/if}\n            <div>\n              <p class=\"font-medium text-gray-800\">{transaction.description}</p>\n              <p class=\"text-sm text-gray-500\">{transaction.date}</p>\n            </div>\n          </div>\n          <span class={transaction.type === \"credit\" ? \"text-green-600\" : \"text-red-600\"}>\n            {transaction.type === \"credit\" ? \"+\" : \"-\"}${transaction.amount.toLocaleString(\"en-US\", {\n              minimumFractionDigits: 2\n            })}\n          </span>\n        </div>\n      {/each}\n    </div>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/home.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { Activity, PiggyBank, Send, Wallet } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n\n  // Fake user data\n  const userData = {\n    name: \"Svelte Boss\",\n    lastLogin: new Date().toLocaleString(),\n    quickStats: [\n      { title: \"Total Balance\", amount: \"$45,250.80\", icon: Wallet },\n      { title: \"Monthly Savings\", amount: \"$2,450.00\", icon: PiggyBank },\n      { title: \"Recent Activity\", count: \"12 transactions\", icon: Activity }\n    ]\n  };\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mb-6\">\n    <h1 class=\"text-2xl text-gray-400\">\n      Welcome back,\n      <span class=\"text-indigo-400 font-bold\">{userData.name}</span>\n    </h1>\n    <p class=\"text-gray-400\">Last login: {userData.lastLogin}</p>\n  </div>\n\n  <div class=\"grid gap-6 md:grid-cols-3\">\n    {#each userData.quickStats as stat}\n      <div class=\"rounded-xl bg-slate-700/30 p-6 shadow-lg transition-transform hover:scale-[1.02]\">\n        <div class=\"mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-100\">\n          <svelte:component\n            this={stat.icon}\n            class=\"h-6 w-6 text-blue-600\" />\n        </div>\n        <h3 class=\"text-lg text-gray-400\">{stat.title}</h3>\n        <p class=\"mt-1 text-xl font-bold text-green-400\">{stat.amount || stat.count}</p>\n      </div>\n    {/each}\n  </div>\n\n  <div class=\"mt-8\">\n    <a\n      use:route\n      href=\"/protected/manage-account/balance\"\n      class=\"flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-6 py-4 font-semibold text-white transition-colors hover:bg-blue-700\">\n      <Send class=\"h-5 w-5\" />\n      View Balance\n    </a>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/manage-account.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import { goto, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Shield, Wallet } from \"lucide-svelte\";\n  import { client } from \"../account-state.svelte\";\n  import { authGuardSlow } from \"./auth-guard-slow\";\n  import Balance from \"./balance.svelte\";\n  import Home from \"./home.svelte\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected/manage-account\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/protected/manage-account/manage-account.svelte\",\n    content: \"This router demonstrates how you can restrict access to routes based on the user's authentication state.\"\n  }}\n  links={[\n    {\n      href: \"/protected/manage-account\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/manage-account/balance\",\n      label: \"/protected/manage-account/balance\"\n    },\n    {\n      href: \"/protected/manage-account/logout\",\n      label: \"/protected/manage-account/logout\"\n    }\n  ]}>\n  <Router\n    id=\"manage-account-router\"\n    basePath=\"/protected/manage-account\"\n    bind:instance={router}\n    routes={[\n      /**\n       * This route is optional, it's for demonstration purposes.\n       * It's used to redirect to the balance page when the user\n       * navigates to the manage-account route (the default path):\n       */\n      {\n        component: Home\n      },\n      {\n        path: \"/balance\",\n        component: Balance,\n        hooks: {\n          pre: authGuardSlow\n        }\n      },\n      {\n        path: \"/logout\",\n        hooks: {\n          pre: () => {\n            client.loggedIn = false;\n            setTimeout(() => {\n              goto(`${session.mode === \"hash\" ? \"#\" : \"\"}/protected/login`);\n            }, 100);\n          }\n        }\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/protected/manage-account/worker-client.svelte.ts\n---\n\nlet working = $state(false);\n\nexport const workerClient = {\n  get working() {\n    return working;\n  },\n  set working(value: boolean) {\n    working = value;\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/protected/account-state.svelte.ts\n---\n\nlet token = $state(localStorage.getItem(\"token\"));\n\nexport const client = {\n  get loggedIn() {\n    return token !== null;\n  },\n  set loggedIn(value: boolean) {\n    token = value ? \"true\" : null;\n    console.log(\"token\", token);\n    if (value) {\n      localStorage.setItem(\"token\", \"true\");\n    } else {\n      localStorage.removeItem(\"token\");\n    }\n  }\n};\n\n\n\n---\nFile: /demo/src/routes/protected/denied.svelte\n---\n\n<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { LockIcon, ShieldAlert } from \"lucide-svelte\";\n</script>\n\n<div class=\"flex items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8\">\n  <div class=\"w-full max-w-md space-y-8 rounded-lg bg-white p-8 shadow-lg\">\n    <div class=\"text-center\">\n      <div class=\"mb-4 flex justify-center\">\n        <ShieldAlert class=\"h-16 w-16 text-red-600\" />\n      </div>\n      <h1 class=\"mb-2 text-3xl font-bold text-gray-900\">Access Denied</h1>\n      <div class=\"mb-4 flex items-center justify-center gap-2 text-red-600\">\n        <LockIcon class=\"h-5 w-5\" />\n        <span class=\"font-semibold\">Secure Area</span>\n      </div>\n      <p class=\"mb-4 text-gray-600\">For your security, access to this banking area has been denied. This may be due to:</p>\n      <ul class=\"mb-6 ml-4 list-disc text-left text-gray-600\">\n        <li>Insufficient permissions</li>\n        <li>Invalid authentication</li>\n        <li>Session timeout</li>\n      </ul>\n      <p class=\"text-sm text-gray-500\">Please contact our support team or return to the homepage if you believe this is an error.</p>\n      <div class=\"mt-6\">\n        <a\n          use:route\n          href=\"/protected\"\n          class=\"inline-flex items-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\">\n          Return to Homepage\n        </a>\n      </div>\n    </div>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/login.svelte\n---\n\n<script lang=\"ts\">\n  import { goto } from \"@mateothegreat/svelte5-router\";\n  import { Shield } from \"lucide-svelte\";\n  import { fade } from \"svelte/transition\";\n  import { client } from \"./account-state.svelte\";\n</script>\n\n<div\n  class=\"p-6\"\n  in:fade={{ duration: 300 }}>\n  <div class=\"mx-auto max-w-md rounded-xl bg-white p-8 shadow-lg\">\n    <div class=\"mb-6 text-center\">\n      <div class=\"mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100\">\n        <Shield class=\"h-8 w-8 text-blue-600\" />\n      </div>\n      <h2 class=\"text-2xl font-bold text-gray-800\">Secure Login</h2>\n      <p class=\"mt-2 text-gray-600\">Please log in to access your account</p>\n    </div>\n\n    <button\n      class=\"w-full rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700\"\n      on:click={() => {\n        client.loggedIn = true;\n        goto(\"/protected/manage-account\");\n      }}>\n      Login to Your Account\n    </button>\n  </div>\n</div>\n\n\n\n---\nFile: /demo/src/routes/protected/main.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { goto, registry, Router, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowRight, Building2, Loader2, Shield, Wallet } from \"lucide-svelte\";\n  import Denied from \"./denied.svelte\";\n  import Login from \"./login.svelte\";\n  import { authGuardFast } from \"./manage-account/auth-guard-fast\";\n\n  let router: RouterInstance = $state();\n  let { route } = $props();\n\n  /**\n   * This is a helper state variable that can be used to determine if the\n   * current route is the same as the route that is being rendered so\n   * that we can show a badge to indicate this is the last router in the\n   * nested routing hierarchy.\n   */\n  let end = $state(true);\n\n  $effect(() => {\n    end =\n      router.current?.result.path.condition === \"default-match\" ||\n      location.pathname === \"/protected/login\" ||\n      location.pathname === \"/protected/denied\";\n  });\n</script>\n\n{#snippet snippet()}\n  <div class=\"rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300\">\n    <div class=\"mx-auto max-w-6xl px-4 py-12\">\n      <div class=\"mb-16 text-center\">\n        <h1 class=\"mb-6 text-4xl font-bold text-blue-500 md:text-6xl\">Welcome to SPA Router Bank!</h1>\n        <p class=\"mb-8 text-xl text-black\">Your trusted partner in routing.</p>\n        <button\n          onclick={() => goto(\"/protected/login\")}\n          class=\"mx-auto flex items-center gap-2 rounded-lg bg-blue-600 px-8 py-3 font-semibold text-white transition-colors hover:bg-blue-700\">\n          Login <ArrowRight size={20} />\n        </button>\n      </div>\n      <div class=\"mb-16 grid gap-8 md:grid-cols-3\">\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Shield class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Secure Banking</h3>\n          <p class=\"text-gray-600\">State-of-the-art security measures to protect your financial data</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Building2 class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Business Solutions</h3>\n          <p class=\"text-gray-600\">Comprehensive banking solutions for businesses of all sizes</p>\n        </div>\n        <div class=\"rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg\">\n          <Wallet class=\"mb-4 h-12 w-12 text-blue-600\" />\n          <h3 class=\"mb-2 text-xl font-semibold text-gray-800\">Personal Banking</h3>\n          <p class=\"text-gray-600\">Tailored financial services for your personal needs</p>\n        </div>\n      </div>\n    </div>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/protected\"\n  {route}\n  {end}\n  title={{\n    file: \"src/routes/protected/main.svelte\",\n    content: \"Demo to show how you can use hooks to control the navigation of your app to control authentication, etc.\"\n  }}\n  links={[\n    {\n      href: \"/protected\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/protected/login\",\n      label: \"/protected/login\"\n    },\n    {\n      href: \"/protected/manage-account\",\n      label: \"/protected/manage-account\"\n    },\n    {\n      href: \"/protected/denied\",\n      label: \"/protected/denied\"\n    }\n  ]}>\n  <Router\n    id=\"protected-router\"\n    basePath=\"/protected\"\n    bind:instance={router}\n    routes={[\n      {\n        component: snippet\n      },\n      {\n        path: \"login\",\n        component: Login\n      },\n      {\n        path: \"manage-account\",\n        component: async () => import(\"./manage-account/manage-account.svelte\"),\n        hooks: {\n          pre: authGuardFast\n        },\n      },\n      {\n        path: \"denied\",\n        component: Denied\n      }\n    ]}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n{#if registry.get(\"manage-account-router\")?.navigating}\n  <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n    <div class=\"flex flex-col items-center gap-4 rounded-md border-2 border-green-400 bg-black/70 px-20 py-6\">\n      <Loader2 class=\"h-12 w-12 text-green-500  animate-spin\" />\n      <div class=\"text-slate-300 font-bold\">Doing some work...</div>\n      <div class=\"text-slate-400 w-96 text-center\">\n        We've added some pre and post hooks to the manage-account router to simulate doing some work.\n      </div>\n    </div>\n  </div>\n{/if}\n\n\n\n---\nFile: /demo/src/routes/transitions/fade.svelte\n---\n\n<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { fade } from \"svelte/transition\";\n</script>\n\n<div in:fade={{ duration: 500 }}>\n  <Container\n    title=\"Fade\"\n    file=\"src/routes/transitions/fade.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-sky-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n\n\n\n---\nFile: /demo/src/routes/transitions/slide.svelte\n---\n\n<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import { slide } from \"svelte/transition\";\n</script>\n\n<div in:slide={{ duration: 500 }}>\n  <Container\n    title=\"Slide\"\n    file=\"src/routes/transitions/slide.svelte\">\n    <div class=\"flex flex-col items-center gap-6 bg-indigo-600 p-10 text-center\">\n      <Badge variant=\"info\">This is a transition that is applied to the component directly</Badge>\n      <div class=\"flex flex-col gap-3\">\n        This is a transition that is applied to the component directly:\n        <InlineCode text={\"<div in:fade={{ duration: 300 }}>..</div>\"} />\n      </div>\n    </div>\n  </Container>\n</div>\n\n\n\n---\nFile: /demo/src/routes/transitions/transitions.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { Router, RouterInstance, type Route } from \"@mateothegreat/svelte5-router\";\n  import Fade from \"./fade.svelte\";\n  import Slide from \"./slide.svelte\";\n\n  let { route } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      component: snippet\n    },\n    {\n      path: \"fade\",\n      component: Fade,\n      props: {\n        file: \"src/routes/transitions/fade.svelte\"\n      }\n    },\n    {\n      path: \"slide\",\n      component: Slide,\n      props: {\n        file: \"src/routes/transitions/slide.svelte\"\n      }\n    }\n  ];\n</script>\n\n{#snippet snippet()}\n  <Container\n    title={\"{#snippet snippet()}\"}\n    file=\"src/routes/transitions/transitions.svelte\">\n    <div class=\"flex flex-col items-center gap-6 p-10 text-center\">\n      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>\n      Click on a link above to see the different effects!\n    </div>\n  </Container>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"/transitions\"\n  {route}\n  end={true}\n  title={{\n    content:\n      \"Demo to show how to use transitions with the router (spoiler: they're applied at the content level rather than within the router itself).\"\n  }}\n  links={[\n    {\n      href: \"/transitions\",\n      label: \"default path\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/transitions/fade\",\n      label: \"/transitions/fade\"\n    },\n    {\n      href: \"/transitions/slide\",\n      label: \"/transitions/slide\"\n    }\n  ]}>\n  <Router\n    id=\"transitions-router\"\n    basePath=\"/transitions\"\n    bind:instance={router}\n    {routes}\n    {...myDefaultRouterConfig} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/delayed.svelte\n---\n\n<div class=\"bg-indigo-400 p-10\">\n  <h1>\n    a delayed route component, the text for \"Navigating\" will be \"Navigating: busy\" while this component is loading\n  </h1>\n</div>\n\n\n\n---\nFile: /demo/src/routes/home.svelte\n---\n\n<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-code.svelte\";\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { RouterInstance, type Route, type RouteResult } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mateothegreat/svelte5-router/router.svelte\";\n  import { Github, MessageCircleQuestion, Newspaper } from \"lucide-svelte\";\n\n  let { route }: { route: RouteResult } = $props();\n  let router: RouterInstance = $state();\n\n  const routes: Route[] = [\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/welcome\" or \"/home/welcome\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"/\",\n      component: welcome\n    },\n    {\n      // Starting paths with \"/\" is not required, but it's a good idea to\n      // do so for clarity in some cases.\n      //\n      // The router will match this route if the path is \"/with-query-params\" or \"/home/with-query-params\"\n      // because the base path is passed in as \"/home\" below.\n      path: \"with-query-params\",\n      component: displayRouteProps\n    }\n  ];\n</script>\n\n{#snippet welcome()}\n  <div class=\"flex flex-col gap-5 rounded-md border-2 border-gray-800 bg-gray-800/50 p-4 text-sm text-slate-300\">\n    <h1 class=\"text-xl font-bold\">Single Page Application Router (SPAR) for Svelte 5+</h1>\n    <p>\n      <InlineCode\n        text=\"@mateothegreat/svelte5-router\"\n        class=\"bg-black text-blue-500\" /> is an SPA router for Svelte that allows you to divide & conquer your app with nested\n      routers, snippets, and more.\n    </p>\n    <div class=\"flex gap-3\">\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-violet-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Newspaper class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/readme.md\"\n          class=\"\">\n          Documentation\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <Github class=\"h-5 w-5\" />\n        <a\n          target=\"_blank\"\n          href=\"https://github.com/mateothegreat/svelte5-router\"\n          class=\"\">\n          GitHub Repository\n        </a>\n      </div>\n      <div\n        class=\"flex h-9 cursor-pointer items-center gap-1 rounded-sm border-2 border-slate-400 bg-slate-600 p-2 transition-all duration-500 hover:bg-blue-600\">\n        <MessageCircleQuestion class=\"h-5 w-5\" />\n        <a href=\"https://github.com/mateothegreat/svelte5-router/issues\">GitHub Issues</a>\n      </div>\n    </div>\n    <div class=\"flex flex-col gap-2\">\n      <h2 class=\"text-lg font-semibold text-indigo-400\">Features</h2>\n      <ul class=\"list-disc space-y-1 pl-6 text-slate-300\">\n        <li>Built for Svelte 5 🚀!</li>\n        <li>Divide & conquer - use nested routers all over the place</li>\n        <li class=\"text-teal-400\">Use components, snippets, or both 🔥!</li>\n        <li>Use regex paths (e.g. /foo/(.*?)/bar) and/or named parameters together</li>\n        <li>Use async routes simply with component: async () => import(\"./my-component.svelte\")</li>\n        <li class=\"font-bold\">Add hooks to your routes to control the navigation flow 🔧</li>\n        <li>Automagic styling of your anchor tags 💄</li>\n        <li>Helper methods 🛠️ to make your life easier</li>\n        <li>Debugging tools included 🔍</li>\n      </ul>\n    </div>\n    <div class=\"flex items-center\">\n      Get started now with\n      <InlineCode\n        text=\"npm install @mateothegreat/svelte5-router\"\n        class=\"mx-1 bg-black\" />\n      and check out the\n      <a\n        class=\"mx-1 cursor-pointer text-violet-400 hover:text-green-500 hover:underline\"\n        href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md\">\n        getting started guide..\n      </a>\n    </div>\n  </div>\n{/snippet}\n\n{#snippet displayRouteProps()}\n  <div class=\"flex flex-col gap-4 border-t-2 border-slate-800 pt-4\">\n    <div class=\"flex flex-col gap-5\">\n      <div class=\"w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2\">\n        match path\n        <InlineCode text={route.route.path.toString()} />\n        to\n        <InlineCode text={`${route.route.path}?someQueryParam=123`} />\n      </div>\n      <div class=\"flex flex-col gap-4 text-sm text-gray-400\">\n        <div class=\" gap-1\">\n          This demo shows how to use the route's\n          <InlineCode text=\"querystring\" />\n          configuration option to match against the current\n          <InlineCode text=\"location.search\" />\n          value passed in by the browser.\n        </div>\n        <Badge\n          variant=\"success\"\n          class=\"w-fit\">\n          Because we did not specify any <InlineCode text=\"querystring\" /> parameters, the route render regardless of the\n          <InlineCode text=\"querystring\" /> and will be passed to the component as shown below.\n        </Badge>\n      </div>\n    </div>\n    <Code\n      title={\"{#snippet displayRouteProps()}\"}\n      file=\"src/routes/home.svelte\">\n      {JSON.stringify(route.result, null, 2)}\n    </Code>\n  </div>\n{/snippet}\n\n<RouteWrapper\n  {router}\n  name=\"home-router\"\n  {route}\n  end={true}\n  title={{\n    file: \"src/routes/home.svelte\",\n    content:\n      \"This route is a child of the main app router where you are redirected to /home/welcome when landing on /home using a `pre` hook.\"\n  }}\n  links={[\n    {\n      href: \"/home\",\n      label: \"/home\",\n      options: {\n        active: {\n          absolute: true\n        }\n      }\n    },\n    {\n      href: \"/home/with-query-params?someQueryParam=123\",\n      label: \"/home/with-query-params?someQueryParam=123\"\n    }\n  ]}>\n  <Router\n    id=\"home-router\"\n    basePath=\"/home\"\n    bind:instance={router}\n    {...myDefaultRouterConfig}\n    {routes} />\n</RouteWrapper>\n\n\n\n---\nFile: /demo/src/routes/not-found.svelte\n---\n\n<script lang=\"ts\">\n  let { route } = $props();\n  $inspect(route);\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">$props():\n\n{JSON.stringify(route, null, 2)}\n</pre>\n</div>\n\n\n\n---\nFile: /demo/src/app.css\n---\n\n@import \"tailwindcss\";\n@config '../tailwind.config.ts';\n\n/*\n  The default border color has changed to `currentColor` in Tailwind CSS v4,\n  so we've added these compatibility styles to make sure everything still\n  looks the same as it did with Tailwind CSS v3.\n\n  If we ever want to remove these styles, we need to add an explicit border\n  color utility to any element that depends on these defaults.\n*/\n@layer base {\n  *,\n  ::after,\n  ::before,\n  ::backdrop,\n  ::file-selector-button {\n    border-color: var(--color-gray-200, currentColor);\n  }\n}\n\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 222.2 84% 4.9%;\n    --muted: 210 40% 96.1%;\n    --muted-foreground: 215.4 16.3% 46.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 222.2 84% 4.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 222.2 84% 4.9%;\n    --border: 214.3 31.8% 91.4%;\n    --input: 214.3 31.8% 91.4%;\n    --primary: 222.2 47.4% 11.2%;\n    --primary-foreground: 210 40% 98%;\n    --secondary: 210 40% 96.1%;\n    --secondary-foreground: 222.2 47.4% 11.2%;\n    --accent: 210 40% 96.1%;\n    --accent-foreground: 222.2 47.4% 11.2%;\n    --destructive: 0 72.2% 50.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 222.2 84% 4.9%;\n    --radius: 0.5rem;\n    --sidebar-background: 0 0% 98%;\n    --sidebar-foreground: 240 5.3% 26.1%;\n    --sidebar-primary: 240 5.9% 10%;\n    --sidebar-primary-foreground: 0 0% 98%;\n    --sidebar-accent: 240 4.8% 95.9%;\n    --sidebar-accent-foreground: 240 5.9% 10%;\n    --sidebar-border: 220 13% 91%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n\n  .dark {\n    --background: 0 0% 0%;\n    --foreground: 210 40% 98%;\n    --muted: 217.2 32.6% 17.5%;\n    --muted-foreground: 215 20.2% 65.1%;\n    --popover: 222.2 84% 4.9%;\n    --popover-foreground: 210 40% 98%;\n    --card: 222.2 84% 4.9%;\n    --card-foreground: 210 40% 98%;\n    --border: 217.2 32.6% 17.5%;\n    --input: 217.2 32.6% 17.5%;\n    --primary: 210 40% 98%;\n    --primary-foreground: 222.2 47.4% 11.2%;\n    --secondary: 217.2 32.6% 17.5%;\n    --secondary-foreground: 210 40% 98%;\n    --accent: 217.2 32.6% 17.5%;\n    --accent-foreground: 210 40% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 210 40% 98%;\n    --ring: 212.7 26.8% 83.9%;\n    --sidebar-background: 240 5.9% 10%;\n    --sidebar-foreground: 240 4.8% 95.9%;\n    --sidebar-primary: 224.3 76.3% 48%;\n    --sidebar-primary-foreground: 0 0% 100%;\n    --sidebar-accent: 240 3.7% 15.9%;\n    --sidebar-accent-foreground: 240 4.8% 95.9%;\n    --sidebar-border: 240 3.7% 15.9%;\n    --sidebar-ring: 217.2 91.2% 59.8%;\n  }\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n\n\n---\nFile: /demo/src/app.svelte\n---\n\n<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterConfig } from \"$lib/default-route-config\";\n  import { session } from \"$lib/session.svelte\";\n  import Extras from \"$routes/extras/extras.svelte\";\n  import Home from \"$routes/home.svelte\";\n  import Nested from \"$routes/nested/nested.svelte\";\n  import PathsAndParams from \"$routes/paths-and-params/paths-and-params.svelte\";\n  import Patterns from \"$routes/patterns/patterns.svelte\";\n  import Protected from \"$routes/protected/main.svelte\";\n  import Transitions from \"$routes/transitions/transitions.svelte\";\n  import type { RouteConfig, RouteResult } from \"@mateothegreat/svelte5-router\";\n  import { goto, logging, registry, type Route, Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { BookHeart, Github, HelpCircle, MousePointerClick } from \"lucide-svelte\";\n\n  /**\n   * Only needed for the demo environment development.\n   *\n   * It is not needed for including the router package in your project.\n   */\n  if (import.meta.hot) {\n    import.meta.hot.accept(() => {\n      import.meta.hot!.invalidate();\n    });\n  }\n\n  /**\n   * This is a state variable that will hold the router instance.\n   *\n   * It can be used to access the current route, navigate, etc:\n   */\n  let router: RouterInstance = $state();\n\n  /**\n   * Get notified when the current route changes:\n   */\n  const route = $derived(router.current);\n  $effect(() => {\n    if (router.current) {\n      logging.info(\n        `🚀 I'm an $effect in app.svelte and i'm running because the current route is now ${router.current.result.path.original}!`\n      );\n    }\n  });\n\n  /**\n   * Let's declare our routes for the main app router:\n   */\n  const routes: RouteConfig[] = [\n    {\n      // You can name your routes anything you want for tracking or debugging:\n      name: \"default-route\",\n      hooks: {\n        pre: async (route: RouteResult) => {\n          console.error(`redirecting to ${session.mode === \"hash\" ? \"/#\" : \"\"}/home using a pre hook!`, route);\n          goto(`${session.mode === \"hash\" ? \"/#\" : \"\"}/home`);\n        },\n        post: async (route: RouteResult) => {\n          console.error(`post hook fired for route`, route);\n        }\n      }\n    },\n    {\n      path: \"/patterns\",\n      component: Patterns\n    },\n    {\n      // Here we use a regex to match the home route.\n      // This is useful if you want to match a route that has a dynamic path.\n      // The \"?:\" is used to group the regex without capturing the match:\n      // path: /^\\/($|home)$/,\n      path: \"home\",\n      component: Home,\n      // Use hooks to perform actions before and after the route is resolved:\n      hooks: {\n        pre: async (route: Route): Promise<boolean> => {\n          // console.log(\"pre hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // Hooks can also be an array of functions (async too):\n        post: [\n          // This is a post hook that will be executed after the route is resolved:\n          (route: Route): boolean => {\n            // console.log(\"post hook #1 fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          },\n          // This is an async post hook that will be executed after the route is resolved:\n          async (route: Route): Promise<boolean> => {\n            // console.log(\"post hook #2 (async) fired for route\");\n            return true; // Return true to continue down the route evaluation path.\n          }\n        ]\n      }\n    },\n    {\n      path: \"nested\",\n      component: Nested\n    },\n    {\n      path: \"paths-and-params\",\n      component: PathsAndParams\n    },\n    {\n      path: \"protected\",\n      component: Protected\n    },\n    {\n      path: \"transitions\",\n      component: Transitions\n    },\n    {\n      path: \"extras\",\n      component: Extras\n    }\n  ];\n\n  // This is a global pre hook that can be applied to all routes.\n  // Here you could check if the user is logged in or perform some other\n  // authentication checks:\n  const globalAuthGuardHook = async (route: Route): Promise<boolean> => {\n    console.warn(\"globalAuthGuardHook\", route);\n    // Return true so that the route can continue down its evaluation path.\n    return true;\n  };\n</script>\n\n<div class=\"flex h-screen flex-col gap-4 bg-zinc-700/25 p-4\">\n  <div class=\"flex items-start\">\n    <div class=\"flex flex-col gap-4\">\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <Github class=\"h-6 w-6\" />\n      </a>\n      <a\n        href=\"https://github.com/mateothegreat/svelte5-router\"\n        class=\"text-slate-400 hover:text-green-500\"\n        target=\"_blank\">\n        <BookHeart class=\"h-6 w-6 text-fuchsia-500\" />\n      </a>\n    </div>\n    <div class=\"logo h-51 w-60\">\n      <img\n        src=\"https://github.com/mateothegreat/svelte5-router/raw/dev/docs/assets/logo.png\"\n        alt=\"logo\" />\n    </div>\n    <div class=\"m-4 flex-1 flex justify-end text-indigo-400 gap-2 text-xs\">\n      <div class=\"text-slate-500 text-sm mb-3.5 rounded-md border-2 bg-black px-2 py-1.5\">\n        demo version: <a\n          href=\"https://github.com/mateothegreat/svelte5-router/tree/{window.__SVELTE5_ROUTER_VERSION__}\"\n          class=\"text-emerald-500 hover:text-blue-400 cursor-pointer\"\n          target=\"_blank\">\n          {window.__SVELTE5_ROUTER_VERSION__}\n        </a>\n      </div>\n    </div>\n  </div>\n  <div class=\"flex-1 gap-4 flex flex-col\">\n    <div\n      class=\"text-slate-500 justify-end items-center text-sm flex gap-2 rounded-md border-2 border-slate-700/75 bg-black/30 px-2 py-1.5\">\n      <MousePointerClick class=\"h-4 w-4 text-slate-500\" />\n      change url mode:\n      <button\n        class=\"flex items-center gap-1 rounded-md border-2 border-purple-600 bg-slate-900/50 font-semibold px-3 py-0.5 cursor-pointer hover:bg-slate-800 hover:border-green-600\"\n        class:text-orange-400={session.mode === \"hash\"}\n        class:text-green-400={session.mode === \"path\"}\n        onclick={() => {\n          session.mode = session.mode === \"hash\" ? \"path\" : \"hash\";\n        }}>\n        {session.mode === \"hash\" ? \"path\" : \"hash\"}\n      </button>\n    </div>\n    <RouteWrapper\n      name=\"main app router\"\n      title={{\n        file: \"src/app.svelte\",\n        content:\n          \"This is the main app component, it contains the top level router and then uses nested routers to divide and conquer your complex routing requirements! 🚀\"\n      }}\n      {router}\n      {route}\n      links={[\n        {\n          href: \"/\",\n          label: \"/\",\n          options: {\n            active: {\n              absolute: true\n            }\n          }\n        },\n        {\n          href: \"/home\",\n          label: \"/home\"\n        },\n        {\n          href: \"/patterns\",\n          label: \"/patterns\"\n        },\n        {\n          href: \"/protected\",\n          label: \"/protected\"\n        },\n        {\n          href: \"/paths-and-params\",\n          label: \"/paths-and-params\"\n        },\n        {\n          href: \"/nested\",\n          label: \"/nested\"\n        },\n        {\n          href: \"/transitions\",\n          label: \"/transitions\"\n        },\n        {\n          href: \"/404\",\n          label: \"/404\"\n        },\n        {\n          href: \"/extras\",\n          label: \"/extras\"\n        }\n      ]}>\n      <div class=\"flex-1\">\n        <Router\n          id=\"my-main-router\"\n          bind:instance={router}\n          {routes}\n          {...myDefaultRouterConfig} />\n      </div>\n    </RouteWrapper>\n  </div>\n</div>\n<div\n  class=\"fixed bottom-0 right-10 overflow-hidden rounded-t-md border-2 border-b-0 bg-neutral-950 text-xs text-gray-400\">\n  <p class=\"flex items-center gap-1.5 bg-black/80 p-2.5 text-sm font-medium text-slate-400\">\n    <a\n      href=\"https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md\"\n      class=\"text-yellow-300/70 hover:text-pink-500\"\n      target=\"_blank\">\n      <HelpCircle class=\"h-5 w-5\" />\n    </a>\n    router registry\n  </p>\n  <table class=\"divide-y divide-gray-900 overflow-hidden rounded-md border-2 text-xs text-gray-400\">\n    <thead>\n      <tr class=\"text-center tracking-wider text-slate-500\">\n        <th class=\"px-3 py-2 font-medium\">Router Name</th>\n        <th class=\"px-3 py-2 font-medium\">Routes</th>\n        <th class=\"px-3 py-2 font-medium\">State</th>\n        <th class=\"px-3 py-2 font-medium\">Current Path</th>\n      </tr>\n    </thead>\n    <tbody class=\"divide-y divide-gray-800 font-mono\">\n      {#each registry.instances.entries() as [key, instance]}\n        <tr>\n          <td class=\"px-3 py-2 text-left text-indigo-400\">\n            {key}\n          </td>\n          <td class=\"px-3 py-2 text-pink-500\">\n            {instance.routes.size}\n          </td>\n          <td class=\"px-3 py-2\">\n            {#if instance.navigating}\n              <span class=\"text-green-500\">busy</span>\n            {:else}\n              <span class=\"text-gray-500\">idle</span>\n            {/if}\n          </td>\n          <td class=\"px-3 py-2 text-green-500\">\n            {instance.current?.path || \"<default>\"}\n          </td>\n        </tr>\n      {/each}\n    </tbody>\n  </table>\n</div>\n\n\n\n---\nFile: /demo/src/main.ts\n---\n\nimport { mount } from \"svelte\";\n\nimport './app.css';\nimport App from './app.svelte';\n\nconst app = mount(App, {\n  target: document.getElementById('app')!,\n})\n\nexport default app\n\n\n\n---\nFile: /demo/src/vite-env.d.ts\n---\n\n/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n\nimport type { CompilerConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  SPA_ROUTER: CompilerConfig;\n}\n\n\n\n---\nFile: /demo/cypress.config.ts\n---\n\nimport { defineConfig } from \"cypress\";\n\nexport default defineConfig({\n  component: {\n    devServer: {\n      framework: \"svelte\",\n      bundler: \"vite\",\n      viteConfig: {\n        server: {\n          port: 8173,\n        },\n      },\n    },\n  },\n  e2e: {\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n\n\n\n---\nFile: /demo/index.html\n---\n\n<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Vite + Svelte + TS</title>\n\t</head>\n\t<body class=\"dark\">\n\t\t<div id=\"app\"></div>\n\t\t<script type=\"module\" src=\"/src/main.ts\"></script>\n\t</body>\n</html>\n\n\n\n---\nFile: /demo/svelte.config.ts\n---\n\nimport { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess(),\n  vitePlugin: {\n    inspector: {\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"top-right\"\n    }\n  }\n};\n\n\n\n---\nFile: /demo/tailwind.config.ts\n---\n\n\n/** @type {import('tailwindcss').Config} */\nconst config = {\n  darkMode: [\"class\"],\n  content: [\"./src/**/*.{html,js,svelte,ts}\", \"../../src/**/*.{html,js,svelte,ts}\"],\n  safelist: [\"dark\"],\n  theme: {\n    container: {\n      center: true,\n      padding: \"2rem\",\n      screens: {\n        \"2xl\": \"1400px\"\n      }\n    },\n    extend: {\n      colors: {\n        border: \"hsl(var(--border) / <alpha-value>)\",\n        input: \"hsl(var(--input) / <alpha-value>)\",\n        ring: \"hsl(var(--ring) / <alpha-value>)\",\n        background: \"hsl(var(--background) / <alpha-value>)\",\n        foreground: \"hsl(var(--foreground) / <alpha-value>)\",\n        primary: {\n          DEFAULT: \"hsl(var(--primary) / <alpha-value>)\",\n          foreground: \"hsl(var(--primary-foreground) / <alpha-value>)\"\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary) / <alpha-value>)\",\n          foreground: \"hsl(var(--secondary-foreground) / <alpha-value>)\"\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive) / <alpha-value>)\",\n          foreground: \"hsl(var(--destructive-foreground) / <alpha-value>)\"\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted) / <alpha-value>)\",\n          foreground: \"hsl(var(--muted-foreground) / <alpha-value>)\"\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent) / <alpha-value>)\",\n          foreground: \"hsl(var(--accent-foreground) / <alpha-value>)\"\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover) / <alpha-value>)\",\n          foreground: \"hsl(var(--popover-foreground) / <alpha-value>)\"\n        },\n        card: {\n          DEFAULT: \"hsl(var(--card) / <alpha-value>)\",\n          foreground: \"hsl(var(--card-foreground) / <alpha-value>)\"\n        }\n      },\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\"\n      },\n      fontFamily: {\n        sans: [\"Inter\"]\n      }\n    }\n  }\n};\nexport default config;\n\n\n\n---\nFile: /demo/vite.config.ts\n---\n\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-inspector\";\n\nimport tailwindcss from \"@tailwindcss/vite\";\n\nimport path from \"path\";\n\nimport { defineConfig } from \"vite\";\n\nimport { vitePluginVersionMark } from \"vite-plugin-version-mark\";\n\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte(),\n    svelteInspector({\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"bottom-left\"\n    }),\n    tailwindcss(),\n    vitePluginVersionMark({\n      // name: 'test-app',\n      // version: '0.0.1',\n      // command: 'git describe --tags',\n      name: \"svelte5-router\",\n      ifGitSHA: true,\n      ifShortSHA: true,\n      ifMeta: true,\n      ifLog: true,\n      ifGlobal: true\n    })\n  ],\n  build: {\n    sourcemap: true\n  },\n  define: {\n    /** @type {import('@mateothegreat/svelte5-router').runtime.Config} */\n    /**\n     * The (optional) router package configuration for the compiler.\n     */\n    \"import.meta.env.SPA_ROUTER\": {\n      /**\n       * If enabled, tracing will be enabled providing rich tracing capabilities.\n       */\n      tracing: {\n        level: 3,\n        enabled: true,\n        console: true\n      },\n      /**\n       * The logging configuration for the router.\n       */\n      logging: {\n        /**\n         * The logging level that is applied.\n         */\n        level: 4,\n        /**\n         * Whether to log the trace to the browser console (optional).\n         */\n        console: false,\n        /**\n         * This method is called when a new trace is created (optional).\n         *\n         * You could use this to send the trace to a remote server, or store it\n         * in a local database.\n         *\n         * This example uses a promise in the event that you are needing to\n         * use async functionality.\n         */\n        sink: async (trace: any) => {\n          await new Promise((resolve) => {\n            console.log(trace);\n            resolve(void 0);\n          });\n        }\n      }\n    }\n  },\n  resolve: {\n    /**\n     * This is only needed for the demo environment.\n     *\n     * It is not needed for including the router package in your project.\n     */\n    alias: {\n      $lib: path.resolve(__dirname, \"./src/lib\"),\n      $routes: path.resolve(__dirname, \"./src/routes\"),\n      \"@mateothegreat/svelte5-router\": path.resolve(__dirname, \"../src/lib\")\n    }\n  }\n});\n\n\n\n---\nFile: /docs/actions.md\n---\n\n# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These actions are designed to be used with anchor (`<a>`) elements to handle navigation and manage active states.\n\n## Available Actions\n\n| Action | Description |\n|--------|-------------|\n| [`route`](#route) | Manages both navigation and active states of links. |\n| [`active`](#active) | Handles active state management for styling links. |  \n\n## Examples\n\n### Basic Navigation Link\n\n```svelte\n<a href=\"/home\" use:route>Home</a>\n```\n\n### Active State with Multiple Classes\n\n```svelte\n<a \n  href=\"/profile\" \n  use:route={{\n    default: { class: ['text-gray-600', 'hover:text-gray-900'] },\n    active: { class: ['text-blue-600', 'font-bold'] }\n  }}\n>\n  Profile\n</a>\n```\n\n### Exact Path Matching\n\n```svelte\n<a \n  href=\"/settings\" \n  use:route={{\n    active: {\n      class: 'active-link',\n      absolute: true // Only active when path exactly matches /settings\n    }\n  }}\n>\n  Settings\n</a>\n```\n\n### Query String Sensitive Navigation\n\n```svelte\n<a \n  href=\"/search?type=users\" \n  use:route={{\n    active: {\n      class: 'active-search',\n      querystring: true // Only active when querystring matches exactly\n    }\n  }}\n>\n  User Search\n</a>\n```\n\n### Notes\n\n- The `route` action automatically prevents default link behavior and handles navigation through the History API.\n- When using `active`, you'll need to handle navigation separately if needed.\n- Classes are applied dynamically based on the current route state.\n- The `absolute` option is useful for preventing parent routes from being marked as active when child routes are active.\n- The `querystring` option allows for precise matching including query parameters.\n\n---\n\n## `route`\n\nThe `route` action is the primary action for handling routing in your application. It manages both navigation and active states of links.\n\n```svelte\n<a href=\"/dashboard\" use:route>Dashboard</a>\n```\n\nThe `route` action accepts an options object with the following configuration:\n\n```typescript\n{\n  default?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  loading?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  },\n  disabled?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\n- `default`: Options applied when the route is inactive.\n- `active`: Options applied when the route is active.\n- `loading`: Options applied when the route is loading.\n- `disabled`: Options applied when the route is disabled.\n\nEach state accepts the following properties:\n\n- `absolute`: When `true`, effects only apply on exact path matches.\n- `querystring`: When `true`, effects only apply when querystring exactly matches.\n- `class`: CSS class(es) to apply when the state is active.\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:route={{\n    default: { class: 'text-gray-600' },\n    active: { \n      class: 'text-blue-600 font-bold',\n      absolute: true \n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n## `active`\n\nThe `active` action is a simplified version of `route` that only handles active state management without handling navigation events. This is useful when you want to style links based on the current route but handle navigation differently.\n\n```svelte\n<a href=\"/dashboard\" use:active>Dashboard</a>\n```\n\nThe `active` action accepts a subset of the route options, focusing only on the active state configuration:\n\n```typescript\n{\n  active?: {\n    absolute?: boolean;\n    querystring?: boolean;\n    class?: string | string[];\n  }\n}\n```\n\nExample with options:\n\n```svelte\n<a \n  href=\"/dashboard\" \n  use:active={{\n    active: {\n      class: ['text-blue-600', 'font-bold'],\n      absolute: true,\n      querystring: true\n    }\n  }}\n>\n  Dashboard\n</a>\n```\n\n\n\n---\nFile: /docs/changelog.md\n---\n\n## [2.15.2] - 2025-05-26\n\n### 🐛 Bug Fixes\n\n- Support list of defaults for route; fixes #76\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.1] - 2025-05-25\n\n### 🐛 Bug Fixes\n\n- Route result serialization to string; closes #73\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.15.0] - 2025-05-24\n\n### 🚀 Features\n\n- Additional props support @ <Router/> -> [child], closes #70\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.14.1] - 2025-03-27\n\n### 🐛 Bug Fixes\n\n- Hooks return to false; closes #63\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- *(docs)* Better explain named params\n\n<!-- generated by git-cliff -->\n## [2.14.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.13.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.12.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.11.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.10.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.9.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.8.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.7.0] - 2025-03-06\n\n### 🚀 Features\n\n- Hash support\n- Hash support\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.1] - 2025-03-05\n\n### ⚙️ Miscellaneous Tasks\n\n- Vercel cicd\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.6.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- *(demo)* Show random querystring usage\n- *(demo)* Show random querystring usage\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Clean up docs\n- Clean up docs\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n- Vercel cicd\n\n<!-- generated by git-cliff -->\n## [2.5.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.4.0] - 2025-03-05\n\n### 🚀 Features\n\n- *(demo)* Awesomify /protected demo with making it rainr\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.34] - 2025-03-05\n\n### 🐛 Bug Fixes\n\n- Cicd\n- Better regexp instance handling\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.33] - 2025-03-04\n\n### 🐛 Bug Fixes\n\n- Cicd\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.32] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.31] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.30] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.29] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.28] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.27] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.26] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.25] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.24] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.23] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.22] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.21] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.20] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.19] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.18] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.17] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.16] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.15] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.14] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.13] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.12] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.11] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.10] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.9] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.8] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.7] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.6] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.5] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.4] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.3] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.2] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.1] - 2025-03-04\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.3.0] - 2025-03-04\n\n### 🚀 Features\n\n- Query param matchers; fix: same route, diff params #54\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n- Query param matchers; fix: same route, diff params #54; tracing\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.7] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.6] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.5] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.4] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.3] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.2] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Statuses signature\n- Statuses signature\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.1] - 2025-02-28\n\n### 📚 Documentation\n\n- Update statuses doc\n- Update statuses doc\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.2.0] - 2025-02-28\n\n### 🚀 Features\n\n- Better status mapping; added callable status code handlers\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.1] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Options handling for <a> tags proper\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.1.0] - 2025-02-28\n\n### 🚀 Features\n\n- /protected demo\n- /protected demo\n- /protected demo\n- /protected demo\n\n### 💼 Other\n\n- Refactor\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.57] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.56] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.55] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.54] - 2025-02-28\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.53] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.52] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.51] - 2025-02-28\n\n### 🐛 Bug Fixes\n\n- Package.json exports causing errors\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.50] - 2025-02-27\n\n### 📚 Documentation\n\n- Add transitions demo\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.49] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.48] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.47] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.46] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.45] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.44] - 2025-02-27\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.43] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n\n<!-- generated by git-cliff -->\n## [2.0.42] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- Prep cicd for v2 release\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.41] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n## [2.0.40] - 2025-02-26\n\n### ⚙️ Miscellaneous Tasks\n\n- *(release)* Update changelog and bumping package.json\n- Prep cicd for v2 release\n\n<!-- generated by git-cliff -->\n\n\n\n---\nFile: /docs/debugging.md\n---\n\n# Debugging\n\nThere are a few way to debug things.\n\n## Debug Logger\n\nIn your `vite.config.ts` file, you can add the following:\n\n```ts\nexport default defineConfig({\n  plugins: [svelte()],\n  build: {\n    sourcemap: true // If you want to use a debugger, add this!\n  },\n  define: {\n    // Tell the router to log when we're in debug mode.\n    // Otherwise, this statement is removed by the compiler (known as tree-shaking)\n    // and all subsequent log statements are removed at build time:\n    'import.meta.env.SPA_ROUTER': {\n      logLevel: \"debug\"\n    },\n  }\n});\n```\n\nThis allows us to log when we're in debug mode otherwise\nstatements like this are removed by the compiler (known astree-shaking):\n\n```ts\nif (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n  log.debug(this.config.id, \"unregistered router instance\", {\n    id: this.config.id,\n    routes: this.routes.size\n  });\n}\n```\n\nPutting it all together:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n\n  let instance: RouterInstance;\n\n  if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === \"debug\") {\n    log.debug(instance.id, \"dumping routes\", {\n      config: instance.config,\n      routes: instance.routes,\n      current: instance.current,\n      navigating: instance.navigating\n    });\n  }\n</script>\n\n<Router bind:instance {routes}>\n```\n\nExample output:\n\n![debug](./assets/debugging-logger.png)\n\n\n\n---\nFile: /docs/getting-started.md\n---\n\n# Getting Started\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Usage\n\nIn your `app.svelte` file, you can use the `Router` component to render your routes:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n\n  const routes: RouteConfig[] = [\n    {\n      component: Home\n    }\n    {\n      path: \"products\",\n      component: Products\n    },\n    {\n      path: \"settings\",\n      component: Settings\n    }\n  ];\n</script>\n\n<Router {routes} />\n```\n\nWhen you navigate to the root route, the `Home` component will be rendered.\n\nWhen you navigate to the `/products` route, the `Products` component will be rendered.\n\nWhen you navigate to the `/settings` route, the `Settings` component will be rendered.\n\n\n\n---\nFile: /docs/helpers.md\n---\n\n# Helpers\n\nThere are a few helpers that are available to you when using the router.\n\n## `goto(path: string, queryParams?: Record<string, string>)`\n\nNavigates to the given path by calling `goto(\"/path\")`.\n\nExample:\n\n```ts\ngoto(\"/foo\", { bar: \"baz\" });\n```\n\nThis will navigate to `/foo?bar=baz`.\n\n## `query(key: string): string | null`\n\nReturns the value of the query parameter for the given key or null if the key does not exist.\n\n## The `QueryString` class\n\nA helper class for working with the query string.\n\n> Check it out at [src/lib/query.svelte.ts](../src/lib/query.svelte.ts).\n> or import it with:\n>\n> ```ts\n> import { QueryString } from \"@mateothegreat/svelte5-router\";\n> ```\n>\n> and start using it now!\n\nBasic usage:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\nquery.get(\"foo\", \"bar\"); // \"bar\"\nquery.set(\"baz\", \"qux\");\nquery.toString();        // \"foo=bar&baz=qux\"\n```\n\nUsing it with navigation:\n\n```ts\nimport { QueryString } from \"@mateothegreat/svelte5-router\";\n\nconst query = new QueryString();\n\n// ...\nquery.set(\"foo\", \"baz\");\nquery.set(\"baz\", \"qux\");\n// ...\n\nquery.goto(\"/test\"); // Navigates to \"/test?foo=baz&baz=qux\"\n```\n\nYou can also pass a query object to the `goto` method:\n\n```ts\ngoto(\"/test\", { foo: \"baz\" }); // Navigates to \"/test?foo=baz\"\n```\n\n\n\n---\nFile: /docs/hooks.md\n---\n\n# Routing Hooks\n\n | Order | Event  | Scope       | Description                                |\n | ----- | ------ | ----------- | ------------------------------------------ |\n | 1.    | `pre`  | `<Router/>` | Always runs *before* a route is attempted. |\n | 2.    | `pre`  | `Route`     | Runs *before* the route is rendered.       |\n | 3.    | `post` | `Route`     | Runs *after* the route is rendered.        |\n | 4.    | `post` | `<Router/>` | Always runs *after* a route is rendered.   |\n\n```ts\nimport { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nexport const authGuard = async (route: RouteResult): Promise<boolean> => {\n  console.log(\"simulating some login/auth check...\");\n  // Crude example of checking if the user is logged in. A more\n  // sophisticated example would use a real authentication system\n  // and a server-side API.\n  if (!localStorage.getItem(\"token\")) {\n    console.warn(\"redirecting to denied\");\n    goto(\"/protected/denied\");\n    return false;\n  }\n  return true;\n}\n\nconst globalPostHook1 = (route: RouteResult): boolean => {\n  console.warn(\"globalPostHook1\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n\nconst globalPostHook2 = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalPostHook2\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\nYou can pass an array or single method for the `pre` and `post` hooks and you can\nalso mix and match asynchronous and synchronous hooks.\n\n```svelte\n<Router\n  {routes}\n  hooks={{\n    pre: authGuard,\n    post: [\n      globalPostHook1,\n      globalPostHook2\n    ]\n  }}\n/>\n```\n\n\n\n---\nFile: /docs/props.md\n---\n\n# Passing Props\n\nYou can pass props to a route by using the `props` property on any route.\n\nThese props will be passed to the component via `$props()`:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: /\\/(?<child>.*)/,\n    component: DisplayParams,\n    props: {\n      randomId: Math.random().toString(36).substring(2, 15),\n      someUserStuff: {\n        username: \"mateothegreat\",\n        userAgent: navigator.userAgent\n      }\n    }\n  }\n];\n```\n\nThen, in your component, you can access the prop like this:\n\n```svelte\n<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<pre>{JSON.stringify(route, null, 2)}</pre>\n```\n\nWhen you navigate to `/props/bar?someQueryParam=123`, the output will be:\n\n```json\n{\n  \"route\": {\n    \"params\": {\n      \"child\": \"bar\"\n    },\n    \"props\": {\n      \"randomId\": \"y3pbfi1mgmg\",\n      \"someUserStuff\": {\n        \"username\": \"mateothegreat\",\n        \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36\"\n      }\n    },\n    \"query\": {\n      \"someQueryParam\": \"123\"\n    },\n    \"name\": \"props-fancy-regex\",\n    \"path\": {\n      \"before\": \"/\\\\/(?<child>.*)/\",\n      \"after\": \"/props/bar\"\n    }\n  }\n}\n```\n\n\n\n---\nFile: /docs/readme.md\n---\n\n# Svelte 5 SPA Router 🚀 🔥\n\n![logo](https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/logo-1000px.png)\n\n<img src=\"https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/assets/coverage.svg?sanitize=true\" />\n\nAn SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.\n\n> [!NOTE]\n> Live demo: <https://demo.router.svelte.spa>\n>\n> API documentation: <https://docs.router.svelte.spa>\n\n## Features\n\n- Built for Svelte 5 🚀!\n- Divide & conquer - use nested routers all over the place.\n- Use components, snippets, or both 🔥!\n- Use regex paths (e.g. `/foo/(.*?)/bar`) and/or named parameters together.\n- Use async routes simply with `component: async () => import(\"./my-component.svelte\")`.\n- Add hooks to your routes to control the navigation flow 🔧.\n- Automagic styling of your anchor tags 💄.\n- Helper methods 🛠️ to make your life easier.\n- Debugging tools included 🔍.\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Table of Contents\n\n- [Getting Started](https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md)\n- [Routing](https://github.com/mateothegreat/svelte5-router/blob/main/docs/routing.md)\n- [Hooks](https://github.com/mateothegreat/svelte5-router/blob/main/docs/hooks.md)\n- [Actions](https://github.com/mateothegreat/svelte5-router/blob/main/docs/actions.md)\n- [Helper Methods](https://github.com/mateothegreat/svelte5-router/blob/main/docs/helpers.md)\n- [Default Status Mapping](https://github.com/mateothegreat/svelte5-router/blob/main/docs/statuses.md)\n- [The Router Registry](https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md)\n- [Route Styling](https://github.com/mateothegreat/svelte5-router/blob/main/docs/styling.md)\n- [Accessing Props](https://github.com/mateothegreat/svelte5-router/blob/main/docs/props.md)\n- [Debugging](https://github.com/mateothegreat/svelte5-router/blob/main/docs/debugging.md)\n\n\n\n---\nFile: /docs/registry.md\n---\n\n# Router Registry\n\nThe router [registry](../src/lib/registry.svelte.ts) is a global object that is used to store route\ninstances and their associated routing configuration.\n\nThis registry updates as you navigate through your application and as `<Router />` components\nare mounted and unmounted dynamically.\n\n![registry](./assets/registry.png)\n\n## Usage\n\nThough the registry is managed internally, though you can access it to debug your application.\n\nWhen `<Router />` is mounted, it will register itself in the registry.\n\nWhen `<Router />` is unmounted, it will unregister itself from the registry.\n\nYou can access the __global__ registry to debug your application by adding the following to your application:\n\n```svelte\n<script lang=\"ts\">\n  import { registry } from \"@mateothegreat/svelte5-router\";\n</script>\n\n<div>\n  {#each registry.instances.entries() as [id, instance]}\n    <div>\n      <pre>id: {id}</pre>\n      <pre>routes: {instance.routes.size}</pre>\n      <pre>current: {instance.current?.path || \"<default>\"}</pre>\n      <pre>navigating: {instance.navigating ? \"yes\" : \"no\"}</pre>\n    </div>\n  {/each}\n</div>\n```\n\nOr, you can access the __local__ registry directly from your `<Router />` component:\n\n```svelte\n<script lang=\"ts\">\n  import type { RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Router } from \"@mateothegreat/svelte5-router\";\n\n  let instance = $state<RouterInstance>();\n\n  $inspect(instance); // Outputs the registry instance to the console.\n</script>\n\n<Router bind:instance {routes} />\n```\n\n\n\n---\nFile: /docs/routing-patterns.md\n---\n\n# Routing Patterns\n\nAs your application grows, you'll likely need to use more complex routing patterns. This guide will cover the most common patterns and how to use them.\n<p>The <abbr title=\"Hyper Text Markup Language\">HTML</abbr> specification\nis maintained by the <abbr title=\"World Wide Web Consortium\">W3C</abbr>.</p>\n\nasdf [^1]\n\n<h1 class=\"style-me\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n\n[<button>Click me</button>](https://www.google.com)\n\n[[docs/readme]]asdf\n\n## Table of Contents\n\n- [Routing Patterns](#routing-patterns)\n  - [Table of Contents](#table-of-contents)\n  - [Default Route](#default-route)\n  - [Single Path](#single-path)\n  - [Nested Paths](#nested-paths)\n  - [Parameter Extraction](#parameter-extraction)\n  - [Named Parameters](#named-parameters)\n\n## Default Route\n\nThis example demonstrates how to make a route be the default route under the following conditions:\n\nOrder of operations:\n\n1. If the path is empty, the route will be matched otherwise evaluation will continue.\n2. If no other route matches, the default route will be matched and evaluation will continue.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n> [!NOTE]\n> You can omit the `path` property to make the route the default route which is the\n> same as `path: \"/\"` and `path: \"\"`.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Single Path\n\nThis example will match any path that starts with `/path`.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Nested Paths\n\nThis example will match any path that starts with `/path/path/path` and can be nested further.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/nested>!\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/path/path/path\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Parameter Extraction\n\nCombine arbitrary paths and extractable parameters.\n\n> [!NOTE]\n> This example has a demo available at <https://demo.router.svelte.spa/paths-and-params>!\n\nOrder of operations:\n\n1. If there are arbitrary paths, the route **must** contain all of them.\n2. If there are parameters, the path **must** match the expression.\n3. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\nExamples:\n\n**Unnamed Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(.*?)/path/(.*?)\",\n    component: ComponentToRender \n  }\n];\n```\n\n**Named Parameters**:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n## Named Parameters\n\nThis example will match any path that starts with `/path/path/path/path` and can be nested further.\n\nOrder of operations:\n\n1. If the path matches exactly, the route will be matched otherwise evaluation will continue.\n2. If [statuses](./statuses.md) is present at the `<Router />` component and `404` is set, this route will then be matched and evaluation will end.\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/path/(?<myParam>.*)/path/(?<myParam2>.*)\",\n    component: ComponentToRender \n  }\n];\n```\n\na\n[^1]: This is a footnote\nb\n\n\n\n---\nFile: /docs/routing.md\n---\n\n# Routing Usage\n\nWe provide an array of `RouteConfig` objects to the `Router` component.\n\nEach `RouteConfig` object describes a route and its associated component.\n\n## Pattern Matching\n\nYou can simply use static paths like `/foo` or dynamic paths like `/foo/(.*?)` with regex.\n\nExample patterns:\n\n| Pattern                                        | Description                                             |\n| ---------------------------------------------- | ------------------------------------------------------- |\n| `/`                                            | The root path.                                          |\n| `/foo`                                         | A static path.                                          |\n| `/foo/(.*?)`                                   | A dynamic path.                                         |\n| `/cool/(.*?)/(.*?)`                            | A dynamic path with two parameters.                     |\n| `(?<param1>.*)`                                | A dynamic path with a named parameter.                  |\n| `^/users/(?<id>[a-z0-9]{25})(?:/(?<tab>.*))?$` | A dynamic path with a named parameter and optional tab. |\n\n> When using named parameters, you can access them using the `$props()` function.\n>\n> For example, if the route is `/users/123/settings`, then `$props()` will be `{ id: \"123\", tab: \"settings\" }`.\n\n## Parameter Extraction\n\nParameters that are capable of being parsed from the path are passed to the component through the `route` prop:\n\n```svelte\n<script>\n  let { route } = $props();\n</script>\n```\n\nWhen using named parameters such as `(?<id>[a-z0-9]{25})`, the parameter value will be passed through the `route` prop as an object:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    path: \"/users/(?<id>[a-z0-9]{25})\",\n    component: User\n  }\n];\n```\n\nand can be accessed like this:\n\n```svelte\n<script>\n  const userId = route.result.path.params.id;\n</script>\n```\n\nFor the full shape of `RouteResult` see the [API Reference](https://github.com/mateothegreat/svelte5-router/blob/main/src/lib/route.svelte.ts#L24).\n\n## Examples\n\n### Basic\n\nThe following example demonstrates a basic route configuration with two routes:\n\n```ts\nconst routes: RouteConfig[] = [\n  {\n    // Notice that we don't need to specify the path.\n    // The router will use this route as the default route when \"/\" is visited.\n    component: Home\n  },\n  {\n    path: \"/about\",\n    component: About\n  }\n];\n```\n\nPassing the routes to the `<Router />` component:\n\n```svelte\n<Router {routes} />\n```\n\n## Full Example\n\nThe following example demonstrates a more complex route configuration with multiple routes and hooks:\n\n```ts\nconst routes: RouteConfig[] = [\n  // Example of a route that redirects to the home route:\n  {\n    path: \"\",\n    hooks: {\n      pre: () => {\n        goto(\"/home\");\n      }\n    }\n  },\n  {\n    // Here we use a regex to match the home route.\n    // This is useful if you want to match a route that has a dynamic path.\n    // The \"?:\" is used to group the regex without capturing the match:\n    path: /(?:^$|home)/,\n    component: Home,\n    // Use hooks to perform actions before and after the route is resolved:\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        console.log(\"pre hook #1 fired for route\");\n        return true; // Return true to continue down the route evaluation path.\n      },\n      // Hooks can also be an array of functions (async too):\n      post: [\n        // This is a post hook that will be executed after the route is resolved:\n        (route: RouteResult): boolean => {\n          console.log(\"post hook #1 fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        },\n        // This is an async post hook that will be executed after the route is resolved:\n        async (route: RouteResult): Promise<boolean> => {\n          console.log(\"post hook #2 (async) fired for route\");\n          return true; // Return true to continue down the route evaluation path.\n        }\n      ]\n    }\n  },\n  {\n    path: \"nested\",\n    component: Nested\n  },\n  {\n    path: \"async\",\n    // Routes can also be async functions that return a promise.\n    // This is useful if you want to load a component asynchronously aka \"lazy loading\":\n    component: async () => import(\"./lib/async/async.svelte\")\n  },\n  {\n    path: \"delayed\",\n    component: Delayed,\n    hooks: {\n      pre: async (route: RouteResult): Promise<boolean> => {\n        // Simulate a network delay by returning a promise that resolves after a second:\n        return new Promise((resolve) =>\n          setTimeout(() => {\n            resolve(true);\n          }, 1000)\n        );\n      }\n    }\n  },\n  {\n    path: \"props\",\n    component: Props\n  },\n  {\n    path: \"protected\",\n    component: Protected\n  },\n  {\n    path: \"query-redirect\",\n    component: QueryRedirect\n  },\n  {\n    path: \"context\",\n    component: Context\n  }\n];\n\n// This is a global pre hook that can be applied to all routes.\n// Here you could check if the user is logged in or perform some other\n// authentication checks:\nconst globalAuthGuardHook = async (route: RouteResult): Promise<boolean> => {\n  console.warn(\"globalAuthGuardHook\", route);\n  // Return true so that the route can continue down its evaluation path.\n  return true;\n};\n```\n\n\n\n---\nFile: /docs/statuses.md\n---\n\n# Route Statuses\n\nEach router instance can have a set of statuses that are rendered when a route\nreturns a specific status code such as 404 for \"Not Found\".\n\nWhen a route returns a status code, the router will render the component or execute the function\nspecified in the `statuses` prop for that status code.\n\n## Status Codes\n\nUsing the `StatusesMapping` enum, the following status codes are to be supported:\n\n> [!NOTE]\n> Currently, the `404` status code is implemented. We will be adding the\n> other status codes in the future.\n\n| Code    | Description           | Status          |\n| ------- | --------------------- | --------------- |\n| 301     | Permanent Redirect    | Coming Soon     |\n| 302     | Temporary Redirect    | Coming Soon     |\n| 400     | Bad Request           | Coming Soon     |\n| 401     | Unauthorized          | Coming Soon     |\n| 403     | Forbidden             | Coming Soon     |\n| __404__ | __Not Found__         | __Implemented__ |\n| 500     | Internal Server Error | Coming Soon     |\n\n## `BadRouted` Object\n\nWhen passing a function to the `statuses` value, the [`RouteResult`](../src/lib/route.ts) object is passed to that function.\n\nIt contains the following properties:\n\n- `path`: The path that was attempted to be accessed\n- `status`: The status code that was returned\n\n## Usage\n\n### Basic Usage\n\nIn this example, we will use the `NotFound` component to render when the router\nreturns a 404 status code because the route `/bad` does not exist.\n\nFirst, we will create the `NotFound` component:\n\n```svelte\n<script lang=\"ts\">\n  let props = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n  <pre class=\"rounded-md bg-gray-800 p-2 text-sm text-emerald-500\">included from \"not-found.svelte\":</pre>\n  <h1 class=\"text-2xl font-bold\">404 not found :(</h1>\n  <p class=\"text-sm text-gray-500\">The page you are looking for does not exist.</p>\n  <pre class=\"rounded-md bg-gray-900 p-2 text-sm text-gray-400\">\n    $props():\n    {JSON.stringify(props, null, 2)}\n  </pre>\n</div>\n```\n\nNext, we will create the `Router` component and pass the `NotFound` component\nto the `statuses` prop:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, Route, StatusCode } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  {routes}\n  statuses={{\n    [StatusCode.NotFound]: NotFound\n  }} />\n```\n\nWhen you navigate to `/bad`, the `NotFound` component will be rendered because\nthe route `/bad` does not exist.\n\n### Custom Usage\n\nYou can also pass a function to the `statuses` prop to have more control over the rendered component. The function receives a `BadRouted` object containing information about the failed route and must return an object with the component to render and any additional props:\n\n```svelte\n<script lang=\"ts\">\n  import { Router, type RouteConfig, StatusCode, getStatusByValue } from \"@mateothegreat/svelte5-router\";\n  import NotFound from \"./lib/not-found.svelte\";\n\n  const routes: RouteConfig[] = [\n    // add routes here\n  ];\n</script>\n\n<Router\n  id=\"my-main-router\"\n  bind:instance\n  {routes}\n  statuses={{\n     [StatusCode.NotFound]: (route: RouteResult) => {\n      console.warn(\n        `Route \"${route.result.path.original}\" could not be found :(`,\n        {\n          statusName: getStatusByValue(route.result.status),\n          statusValue: route.result.status\n        },\n        route\n      );\n      return {\n        component: NotFound,\n        props: {\n          somethingExtra: new Date().toISOString()\n        }\n      };\n    }\n  }} />\n```\n\nIn this example, when a 404 error occurs:\n\n1. The function logs a warning with details about the failed route\n2. Returns the `NotFound` component with an additional prop `somethingExtra` containing the current timestamp\n3. The `NotFound` component will receive both its default props and the extra props specified in the function\n\n\n\n---\nFile: /docs/styling.md\n---\n\n# Routing Styling\n\nYou can have the router apply a class to the active route by setting the `active.class` option\nwhen configuring your routes.\n\nAs the routes change, the router will apply the class to the active route while removing it from the previous active route(s).\n\n## Configuration\n\nThis property can be a string or an array of strings:\n\n> [!NOTE]\n> This is a convenience feature and is not required. You can apply the class to the active route manually in your component.\n>\n> See <https://docs.router.svelte.spa/classes/RouteOptions.html> for more information.\n\nUsing a string:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: \"bg-yellow-500\"\n  }\n};\n```\n\nUsing an array of strings:\n\n```ts\nexport const myDefaultRouteConfig = {\n  active: {\n    class: [\n      \"bg-yellow-500\",\n      \"underline\"\n    ]\n  }\n};\n```\n\n## Usage\n\nWith our common configuration declared we can use it in our routes:\n\nImport the common configuration:\n\n```ts\n<script lang=\"ts\">\n  import { myDefaultRouteConfig } from \"./lib/common-stuff\";\n</script>\n```\n\n### Using `use:route`\n\n```svelte\n<a\n  use:route={myDefaultRouteConfig}\n  href=\"/props\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  /props\n</a>\n```\n\n### Using `use:active`\n\nYou can also be more prescriptive and pass in the active class as an object.\n\n> [!NOTE]\n> This is functionally equivalent to using `use:route` with the same configuration.\n> It is just a convenience method for when you don't need to pass in any other options.\n\n```svelte\n<a\n  use:route\n  use:active={{ active: { class: \"bg-pink-500\" } }}\n  href=\"/baz\"\n  class=\"py-1 hover:bg-blue-800 rounded bg-blue-600 px-3\">\n  Click Me\n</a>\n```\n\nHere we used two actions:\n\n- `use:route`\n  - This is the default action and is used to apply the active class to the active route.\n- `use:active`\n  - This is a convenience action that is used to apply the active class to the active route.\n\n\n\n---\nFile: /src/lib/actions/active.svelte.ts\n---\n\nimport { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Add the `active` class to the node if the current route matches the node's href.\n *\n * > Similar to {@link route}.\n *\n * Add `use:active` to an anchor element to manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n *\n * @category Actions\n * @source\n */\nexport const active = (node: HTMLAnchorElement, options: Pick<RouteOptions, \"active\"> = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  window.addEventListener(\"pushState\", apply);\n\n  return {\n    destroy() {\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n\n\n\n---\nFile: /src/lib/actions/apply-classes.ts\n---\n\nimport { urls, type URL } from \"../helpers/urls\";\n\nimport { RouteOptions } from \"./options\";\n\n/**\n * Applies the active class to the node if the href is the same as the current location.\n *\n * @param href - The href to check if it is the same as the current location.\n * @param options - The options to apply to the node.\n * @param node - The node to apply the active class to.\n *\n * @category Actions\n */\nexport const applyActiveClass = (href: URL, options: RouteOptions, node: HTMLAnchorElement) => {\n  const url = urls.parse(location.toString());\n  if (\n    (href.path === url.path ||\n      href.path === url.hash.path ||\n      href.hash.path === url.path ||\n      (!options.active?.absolute && url.path.startsWith(href.path))) &&\n    (options.active?.querystring || options.active?.querystring === undefined) &&\n    (href.query.original == \"\" ||\n      href.query.original === location.search.replace(\"?\", \"\") ||\n      href.query.original === url.hash.query.original)\n  ) {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.add(...options.active?.class);\n    } else {\n      node.classList.add(options.active?.class);\n    }\n    if (options.default?.class) {\n      node.classList.remove(...options.default?.class);\n    }\n  } else {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.remove(...options.active?.class);\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    } else {\n      if (options.active?.class) {\n        node.classList.remove(options.active?.class);\n      }\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    }\n  }\n};\n\n\n\n---\nFile: /src/lib/actions/index.ts\n---\n\nexport * from \"./apply-classes\";\nexport * from \"./options\";\nexport * from \"./route.svelte\";\n\n\n\n---\nFile: /src/lib/actions/options.ts\n---\n\n/**\n * Options that are applied to the html element when the route is active, inactive,\n * loading, or disabled.\n *\n * @category Router\n */\nexport type RouteOptionState = {\n  /**\n   * When true, the effects will only be applied if the path is an exact match.\n   *\n   * This is useful for when you want to apply the effects to a specific route, but\n   * not when it's part of a parent route.\n   */\n  absolute?: boolean;\n\n  /**\n   * When true, the effects will only be applied if the querystring is an exact match.\n   */\n  querystring?: boolean;\n\n  /**\n   * The css class(es) to add when this state is currently active.\n   */\n  class?: string | string[];\n};\n\n/**\n * Options for the route action.\n *\n * @category Router\n */\nexport class RouteOptions {\n  /**\n   * When the route is inactive, these options are applied.\n   */\n  default?: RouteOptionState;\n\n  /**\n   * When the route is active, these options are applied.\n   */\n  active?: RouteOptionState;\n\n  /**\n   * The css class(es) to add when route is loading.\n   */\n  loading?: RouteOptionState;\n\n  /**\n   * When the route is disabled, these options are applied.\n   */\n  disabled?: RouteOptionState;\n\n  constructor(options?: Partial<RouteOptions>) {\n    if (options) {\n      Object.assign(this, options);\n    }\n  }\n}\n\n\n\n---\nFile: /src/lib/actions/route.svelte.ts\n---\n\nimport { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Svelte action to handle routing with optional active state.\n *\n * Similar to {@link active}\n *\n * Add `use:route` to an anchor element to handle routing and optionally manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n * @param {RouteOptions} options Options for the route action (optional).\n * @category Actions\n * @includeExample demo/src/app.svelte\n * @source\n */\nexport const route = (node: HTMLAnchorElement, options: RouteOptions = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  /**\n   * Handle click events on the anchor element.\n   * @param event - The click event.\n   */\n  const handleClick = (event: Event) => {\n    event.preventDefault();\n    window.history.pushState({}, \"\", node.href);\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  node.addEventListener(\"click\", handleClick);\n  window.addEventListener(\"pushState\", apply);\n  return {\n    destroy() {\n      node.removeEventListener(\"click\", handleClick);\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n\n\n\n---\nFile: /src/lib/helpers/evaluators.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { evaluators } from \"./evaluators\";\nimport { Identities } from \"./identify\";\nimport { regexp } from \"./regexp\";\n\ndescribe(\"regexp\", () => {\n  test(\"should convert ^home$ to a RegExp\", () => {\n    expect(regexp.from(\"^home$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /^home$/ to a RegExp\", () => {\n    expect(regexp.from(\"/^home$/\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert a RegExp to a RegExp\", () => {\n    expect(regexp.from(/^home$/)).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert ^/($|home)$ to a RegExp\", () => {\n    expect(regexp.from(\"^/($|home)$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /(^home$/ to a RegExp\", () => {\n    expect(() => regexp.from(\"/(^home$/\")).toThrowError();\n  });\n});\n\ndescribe(\"evaluators\", () => {\n  test(\"should return true for a non-empty object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 1 })).toBe(true);\n  });\n\n  test(\"should return false for an empty-ish object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined, b: true })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 0, b: { c: null } })).toBe(false);\n  });\n});\n\n\n\n---\nFile: /src/lib/helpers/evaluators.ts\n---\n\nimport { identify, Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\nimport type { ReturnParam } from \"./urls\";\n\n/**\n * Path or querystring evaluation result.\n *\n * @category Router\n */\nexport type Condition =\n  | \"exact-match\"\n  | \"base-match\"\n  | \"default-match\"\n  | \"no-match\"\n  | \"permitted-no-conditions\"\n  | \"one-or-more-missing\";\n\n/**\n * The conditions that are considered successful.\n *\n * @category Router\n */\nexport const SuccessfulConditions: Condition[] = [\n  \"exact-match\",\n  \"base-match\",\n  \"default-match\",\n  \"permitted-no-conditions\"\n];\n\n/**\n * The conditions that are considered failed.\n *\n * @category Router\n */\nexport const FailedConditions: Condition[] = [\"no-match\", \"one-or-more-missing\"];\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type Evaluation = {\n  condition: Condition;\n  params?: ReturnParam;\n};\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type EvaluationResult = {\n  path: Evaluation;\n  querystring: Evaluation;\n  original: ReturnParam;\n};\n\nexport namespace evaluators {\n  /**\n   * Composite evaluator function that can handle different types of values.\n   *\n   * @param a - The first value to evaluate.\n   * @param b - The second value to evaluate {a} against.\n   * @returns A boolean, string[], or object.\n   */\n  export const any: Record<\n    string,\n    (\n      a: any,\n      b: any\n    ) =>\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | string\n      | string[]\n      | { [key: string]: boolean | boolean[] | number | number[] | string | string[] }\n  > = {\n    [Identities.string]: (a, b) => a === b,\n    [Identities.number]: (a, b) => a === b,\n    [Identities.boolean]: (a, b) => a === b,\n    [Identities.promise]: (a, b) => a === b,\n    [Identities.function]: (a, b) => a === b,\n    [Identities.null]: (a, b) => a === b,\n    [Identities.undefined]: (a, b) => a === b,\n    [Identities.unknown]: (a, b) => a === b,\n    [Identities.array]: (a, b) =>\n      Array.isArray(a) &&\n      Array.isArray(b) &&\n      a.length === b.length &&\n      a.every((value, index) => any[identify(value)](value, b[index])),\n    [Identities.object]: (a, b) => {\n      if (typeof a !== \"object\" || typeof b !== \"object\") {\n        return false;\n      }\n      const aKeys = Object.keys(a);\n      const bKeys = Object.keys(b);\n      if (aKeys.length !== bKeys.length) {\n        return false;\n      }\n      return aKeys.every((key) => any[identify(a[key])](a[key], b[key]));\n    },\n    [Identities.regexp]: (a, b) => {\n      const result = (a as RegExp).exec(b);\n      if (result) {\n        if (result.groups) {\n          return marshal(result.groups).value as { [key: string]: string };\n        } else {\n          if (result.length === 1 && result[0] === result.input) {\n            return true;\n          }\n          return marshal(result.slice(1)[0]).value as string[];\n        }\n      }\n      return false;\n    }\n  };\n\n  /**\n   * Evaluator function that checks if a value is empty recursively.\n   *\n   * @category Router\n   */\n  export const valid: Record<string, (a: any) => boolean> = {\n    [Identities.string]: (a) => a.length > 0,\n    [Identities.boolean]: (a) => a === false,\n    [Identities.number]: (a) => !isNaN(a),\n    [Identities.array]: (a) => Array.isArray(a) && a.length > 0,\n    [Identities.object]: (a) => {\n      if (typeof a !== \"object\" || a === null) {\n        return true;\n      }\n      const keys = Object.keys(a);\n      if (keys.length === 0) {\n        return true;\n      }\n      const result = keys.every((key) => {\n        const value = a[key];\n        const valueType = identify(value);\n        return valid[valueType](value);\n      });\n      return result;\n    },\n    [Identities.regexp]: (a) => a instanceof RegExp,\n    [Identities.function]: (a) => typeof a === \"function\",\n    [Identities.null]: () => false,\n    [Identities.undefined]: () => false\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/goto.ts\n---\n\n/**\n * Navigate to a new path by using the browser's history API (pushState specifically).\n *\n * @param path - The path to navigate to (excluding the base URL).\n * @param queryParams - The query parameters to add to the URL.\n *\n * @category Helpers\n */\nexport const goto = (path: string, queryParams?: Record<string, unknown>): void => {\n  const url = new URL(path, window.location.origin);\n  if (queryParams) {\n    Object.entries(queryParams).forEach(([key, value]) => {\n      url.searchParams.set(key, value as string);\n    });\n  }\n  window.history.pushState({}, \"\", url.toString());\n};\n\n\n\n---\nFile: /src/lib/helpers/identify.ts\n---\n\nexport type Identity =\n  | string\n  | number\n  | boolean\n  | null\n  | undefined\n  | RegExp\n  | Function\n  | Object\n  | Array<unknown>\n  | Promise<unknown>;\n\nexport const Identities = {\n  string: \"string\",\n  number: \"number\",\n  boolean: \"boolean\",\n  null: \"null\",\n  undefined: \"undefined\",\n  regexp: \"regexp\",\n  function: \"function\",\n  object: \"object\",\n  array: \"array\",\n  promise: \"promise\",\n  unknown: \"unknown\"\n};\n\nexport const identify = (value: unknown): (typeof Identities)[keyof typeof Identities] => {\n  if (value === null) {\n    return Identities.null;\n  }\n  if (value === undefined) {\n    return Identities.undefined;\n  }\n  if (value instanceof RegExp) {\n    return Identities.regexp;\n  }\n  if (typeof value === \"string\") {\n    return Identities.string;\n  }\n  if (typeof value === \"number\") {\n    return Identities.number;\n  }\n  if (typeof value === \"boolean\") {\n    return Identities.boolean;\n  }\n  if (Array.isArray(value)) {\n    return Identities.array;\n  }\n  if (typeof value === \"function\") {\n    return Identities.function;\n  }\n  if (typeof value === \"object\") {\n    return Identities.object;\n  }\n  return Identities.unknown;\n};\n\n\n\n---\nFile: /src/lib/helpers/index.ts\n---\n\nexport * from \"./evaluators\";\nexport * from \"./goto\";\nexport * from \"./identify\";\nexport * from \"./logging\";\nexport * from \"./marshal\";\nexport * from \"./normalize\";\nexport * from \"./objects\";\nexport * from \"./query\";\nexport * from \"./regexp\";\nexport * from \"./runtime\";\nexport * from \"./tracing.svelte\";\nexport * from \"./urls\";\n\n\n\n---\nFile: /src/lib/helpers/logging.ts\n---\n\nimport { runtime } from \"./runtime\";\n\n/**\n * Logging facility.\n *\n * @category Helpers\n */\nexport namespace logging {\n  /**\n   * Acceptable log levels (applies to all logging methods).\n   */\n  export enum LogLevel {\n    FATAL = -1,\n    ERROR = 1,\n    INFO = 2,\n    DEBUG = 3,\n    TRACE = 4,\n    DISABLED = 5\n  }\n\n  /**\n   * A grouping of log messages.\n   */\n  export type Group = {\n    name: string;\n    messages: any | any[];\n  };\n\n  /**\n   * Acceptable log types typed out so that it's clearer what can be\n   * passed to the logging functions like groups of logs to combine them\n   * in the outputs.\n   */\n  export type Log = Group | Group[] | any | any[];\n\n  /**\n   * Convenience method for logging an info message.\n   */\n  export const info = (...msg: Log[]): void => {\n    log(LogLevel.INFO, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a debug message.\n   */\n  export const debug = (...msg: Log[]): void => {\n    log(LogLevel.DEBUG, ...msg);\n  };\n\n  /**\n   * Convenience method for logging an error message.\n   */\n  export const error = (...msg: any[]): void => {\n    log(LogLevel.ERROR, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a trace message.\n   */\n  export const trace = (...msg: any[]): void => {\n    log(LogLevel.TRACE, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a fatal error and finally throwing an error.\n   *\n   * @remarks\n   * This is used to stop the application from running if an error is encountered\n   * that is not recoverable.\n   */\n  export const fatal = (...msg: any[]): void => {\n    log(LogLevel.FATAL, ...msg);\n    throw new Error(\"Fatal error\");\n  };\n\n  /**\n   * Raw log method.\n   */\n  export const log = (level: LogLevel, ...msg: Log[]): void => {\n    if (level <= runtime.current.logging.level && level !== LogLevel.DISABLED) {\n      if (runtime.current.logging.console) {\n        if (msg.some((m) => m?.toConsole)) {\n          msg.forEach((m) => m?.toConsole?.(runtime.current.logging.level));\n        } else if (runtime.current.logging.console) {\n          console.log(...msg);\n        }\n      }\n      if (runtime.current.logging.sink) {\n        runtime.current.logging.sink(msg);\n      }\n    }\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/marshal.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\n\ndescribe(\"marshal\", () => {\n  test(\"should marshal an array from a string\", () => {\n    expect(marshal(\"a[0]=1\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with multiple values\", () => {\n    expect(marshal(\"a[0]=first&nonarray=true,a[999]=true&a[1]=second&a[2]=3&a[3]=fourth\")).toEqual({\n      identity: Identities.object,\n      value: {\n        nonarray: true,\n        a: [\"first\", \"second\", 3, \"fourth\", true]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with an empty value\", () => {\n    expect(marshal(\"a[0]=1&a[1]=b&a[2]=false&a[3]=true&a[4]=\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1, \"b\", false, true, \"\"]\n      }\n    });\n  });\n});\n\n\n\n---\nFile: /src/lib/helpers/marshal.ts\n---\n\nimport { Identities, type Identity } from \"./identify\";\n\nexport type MarshallableType = string | number | boolean | RegExp | Function | Promise<unknown>;\n\nexport type Marshalled<T> = {\n  identity: Identity;\n  value: T;\n};\n\n/**\n * Marshal a value to a specific type.\n *\n * @param value - The value to marshal\n *\n * @returns The marshaled value\n */\nexport const marshal = <T>(value: unknown): Marshalled<T> => {\n  // Most values will be strings, so we check for that first:\n  if (typeof value === \"string\") {\n    // Check for floats:\n    if (value.match(/^[\\d.-]+$/)) {\n      if (!Number.isNaN(Number.parseFloat(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseFloat(value) as T\n        };\n      }\n      // If the value is capable of being parsed as a number, we do that:\n      if (!Number.isNaN(Number.parseInt(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseInt(value) as T\n        };\n      } else {\n        return {\n          identity: Identities.string,\n          value: value as T\n        };\n      }\n    } else if (!value.includes(\",\") && (value.includes(\"&\") || value.includes(\"=\"))) {\n      // Handle both array notation (x[0]=1) and simple key-value pairs (a=1&b=2)\n      const pairs = value.split(/[&,]/);\n      const result: Record<string, unknown> = {};\n\n      for (const pair of pairs) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        // Remove array notation if present, otherwise use the key as is\n        const cleanKey = key.replace(/\\[\\d*\\]$/, \"\");\n        const marshalled = marshal(val);\n\n        if (key.includes(\"[\")) {\n          // Handle array case\n          if (!Array.isArray(result[cleanKey])) {\n            result[cleanKey] = [];\n          }\n          const index = key.match(/\\[(\\d+)\\]/)?.[1];\n          if (index) {\n            // Store index and value as tuple to sort later\n            if (!Array.isArray(result[cleanKey])) {\n              result[cleanKey] = [];\n            }\n            (result[cleanKey] as [number, unknown][]).push([parseInt(index), marshalled.value]);\n          } else {\n            (result[cleanKey] as unknown[]).push(marshalled.value);\n          }\n        } else {\n          // Handle simple key-value case\n          result[cleanKey] = marshalled.value;\n        }\n      }\n\n      // Transform arrays while preserving non-array values\n      for (const [key, value] of Object.entries(result)) {\n        if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {\n          // Sort by index and extract values only for array entries\n          result[key] = (value as [number, unknown][]).sort((a, b) => a[0] - b[0]).map(([, val]) => val);\n        }\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.includes(\"&\") && value.includes(\"=\")) {\n      const result: Record<string, unknown> = {};\n\n      for (const pair of value.split(\"&\")) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        result[key] = val;\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.match(/^[0-9a-z]+\\[\\d+\\]=.+$/)) {\n      // Handle single array element case (e.g., \"x[0]=1\")\n      const [, index, val] = value.match(/^[0-9a-z]+\\[(\\d+)\\]=(.+)$/) || [];\n      if (index !== undefined && val !== undefined) {\n        const result = [];\n        const marshalled = marshal(val);\n        result[parseInt(index, 10)] = marshalled.value;\n        return {\n          identity: Identities.array,\n          value: result as T\n        };\n      }\n    } else if (value.match(/^[0-9a-z]+\\[\\]$/)) {\n      return {\n        identity: Identities.array,\n        value: value as T\n      };\n    }\n\n    // If the value is a string that is not a number, we check if it's a boolean:\n    if (value.match(/^true$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: true as T\n      };\n    }\n    if (value.match(/^false$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: false as T\n      };\n    }\n\n    return {\n      identity: Identities.string,\n      value: value as T\n    };\n  } else if (typeof value === \"number\") {\n    return {\n      identity: Identities.number,\n      value: value as T\n    };\n  } else if (value instanceof RegExp) {\n    return {\n      identity: Identities.regexp,\n      value: value as T\n    };\n  } else if (typeof value === \"boolean\") {\n    return {\n      identity: Identities.boolean,\n      value: value as T\n    };\n  } else if (value === null) {\n    return {\n      identity: Identities.null,\n      value: null\n    };\n  } else if (value === undefined) {\n    return {\n      identity: Identities.undefined,\n      value: undefined\n    };\n  } else if (Array.isArray(value)) {\n    return {\n      identity: Identities.array,\n      value: value as T\n    };\n  } else if (typeof value === \"object\") {\n    const marshalled = Object.entries(value).reduce(\n      (acc, [key, val]) => {\n        acc[key] = marshal(val)?.value;\n        return acc;\n      },\n      {} as Record<string, unknown>\n    );\n    return {\n      identity: Identities.object,\n      value: marshalled as T\n    };\n  } else if (typeof value === \"function\") {\n    return {\n      identity: Identities.function,\n      value: value as T\n    };\n  } else if (value instanceof Promise) {\n    return {\n      identity: Identities.promise,\n      value: value as T\n    };\n  }\n\n  throw new Error(\n    `unable to marshal value: ${value} (it is neither a string, number, boolean, nor a regular expression)`\n  );\n};\n\n\n\n---\nFile: /src/lib/helpers/normalize.ts\n---\n\n/**\n * Normalize a path to ensure it starts with a slash.\n *\n * @param {string} path The path to normalize.\n *\n * @returns {string} The normalized path.\n *\n * @category Helpers\n */\nexport const normalize = (path: string): string => {\n  if (path && !path.startsWith(\"/\")) {\n    path = \"/\" + path;\n  }\n  return path;\n};\n\n\n\n---\nFile: /src/lib/helpers/objects.ts\n---\n\n/**\n * Convert a value to a primitive value.\n *\n * @param obj - The value to convert.\n *\n * @returns The primitive value.\n *\n * @category Helpers\n *\n */\nexport const toPrimitive = (obj: any): string | number | boolean | undefined | null | Array<any> => {\n  if (obj instanceof RegExp) {\n    return obj.toString();\n  }\n  if (obj instanceof Array) {\n    return obj.map((item) => toPrimitive(item));\n  }\n  if (typeof obj === \"object\") {\n    return Object.entries(obj).map(([key, value]) => {\n      return {\n        key,\n        value: toPrimitive(value)\n      };\n    });\n  }\n\n  // Return the value as is if it's not an object, array, or RegExp.\n  // TODO: Maybe we should throw an error here? (future mateo problem..)\n  return obj;\n};\n\n\n\n---\nFile: /src/lib/helpers/query.ts\n---\n\n/**\n * Get a query parameter from the current URL.\n *\n * @param key - The key of the query parameter to get\n *\n * @returns The value of the query parameter, or null if it doesn't exist\n *\n * @category Helpers\n */\nexport const query = (key: string): string | null => {\n  return new URLSearchParams(window.location.search).get(key);\n};\n\n\n\n---\nFile: /src/lib/helpers/regexp.ts\n---\n\n/**\n * Regular expression utilities.\n *\n * @module regexp\n * @category Helpers\n */\nexport namespace regexp {\n  /**\n   * Safely handle a capable value as a RegExp.\n   *\n   * If the value is a string, it will be converted to a RegExp.\n   * If the value is already a RegExp, it will be returned as is.\n   * Otherwise, an error will be thrown.\n   *\n   * @throws {Error} If the value is not a string or RegExp.\n   */\n  export const from = (v: string | RegExp): RegExp => {\n    if (typeof v === \"string\") {\n      return new RegExp(v);\n    } else if (v instanceof RegExp) {\n      return new RegExp(v.source);\n    }\n    throw new Error(\"invalid regexp expression\");\n  };\n\n  /**\n   * Check if a string contains regex syntax.\n   *\n   * @param v The string to check.\n   * @returns True if the string contains regex syntax, false otherwise.\n   */\n  export const can = (v: string): boolean => {\n    // Check for:\n    // - Special characters: [] {} () * + ? . \\ ^ $ |\n    // - Character classes: \\w \\d \\s and their negations\n    // - Anchors: ^ $\n    // - Quantifiers: + * ? {}\n    // - Groups: (? (?: (?= (?! (?<= (?<!\n    return /[[\\]{}()*+?.,\\\\^$|#\\s]|\\\\[wWdDsS]|\\(\\?[:!=<]?/.test(v);\n  };\n}\n\n\n\n---\nFile: /src/lib/helpers/runtime.ts\n---\n\nimport { logging } from \"./logging\";\n\n/**\n * Runtime level configuration functionality.\n *\n * @category Helpers\n */\nexport namespace runtime {\n  /**\n   * Runtime configuration.\n   */\n  export type Config = {\n    tracing: {\n      enabled: boolean;\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n    logging: {\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n  };\n\n  /**\n   * Retrieve the runtime configuration.\n   *\n   * This can be sourced from environment variables or passed in as an argument.\n   */\n  export const config = (config?: Config): Config => {\n    return {\n      tracing: config?.tracing || import.meta?.env?.SPA_ROUTER?.tracing || false,\n      logging: {\n        level: config?.logging?.level || import.meta?.env?.SPA_ROUTER?.logging?.level || 4,\n        console: config?.logging?.console || import.meta?.env?.SPA_ROUTER?.logging?.console,\n        sink: config?.logging?.sink || import.meta?.env?.SPA_ROUTER?.logging?.sink\n      }\n    };\n  };\n\n  /**\n   * The current runtime configuration.\n   *\n   * When first called, it will retrieve the runtime configuration from the environment variables.\n   * After that, it can be mutated and will not be retrieved from the environment variables again.\n   */\n  export let current: Config = config();\n}\n\n\n\n---\nFile: /src/lib/helpers/tracing.svelte.ts\n---\n\nimport { ReactiveMap } from \"../utilities.svelte\";\n\nimport { logging } from \"./logging\";\nimport { runtime } from \"./runtime\";\n\n/**\n * A span is a single trace in a trace collection.\n *\n * @category Helpers\n */\nexport class Span {\n  prefix?: string;\n  id?: string;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  traces?: ReactiveMap<string, Trace> = $state(new ReactiveMap());\n\n  constructor(span: Span, prefix?: string) {\n    this.prefix = prefix;\n    this.name = span.name;\n    this.id = span.id || Math.random().toString(36).substring(2, 25);\n    this.description = span.description;\n    this.metadata = span.metadata;\n    this.date = span.date || new Date();\n  }\n\n  trace?(trace: Trace, prefix?: string): Trace {\n    const id = trace.id || Math.random().toString(36).substring(2, 25);\n    trace = new Trace(trace, this.traces.size + 1, this, prefix);\n    this.traces.set(id, trace);\n\n    logging.trace(prefix, trace);\n\n    return trace;\n  }\n\n  get?(): MapIterator<Trace> {\n    return this.traces.values();\n  }\n}\n\n/**\n * A trace is a collection of spans.\n *\n * @category Helpers\n */\nexport class Trace {\n  prefix?: string;\n  id?: string;\n  index?: number;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  span?: Span;\n\n  constructor(trace: Trace, index?: number, span?: Span, prefix?: string) {\n    this.id = trace.id || Math.random().toString(36).substring(2, 25);\n    this.index = index;\n    this.date = trace.date || new Date();\n    this.name = trace.name;\n    this.description = trace.description;\n    this.metadata = trace.metadata;\n    this.span = span;\n    this.prefix = trace.prefix;\n  }\n\n  /**\n   * Wrapper method for logging a trace to the browser console.\n   *\n   * @category Helpers\n   */\n  toConsole?(level?: logging.LogLevel): void {\n    const out = [\n      \"%c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s\",\n      \"color: #505050\",\n      this.date?.toISOString(),\n      \"color: #7A7A7A\",\n      \"color: #915CF2; font-weight: bold\",\n      this.span?.name || this.id,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #C3F53B; font-weight: bold\",\n      this.index,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #3BAEF5; font-weight: bold\",\n      `${this.metadata?.router ? `[${this.metadata.router.id}] ` : \"\"}${this.name}`,\n      \"color: #06E96C\",\n      this.description\n    ];\n\n    if (this.prefix) {\n      out[0] = `${this.prefix} %c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s`;\n    }\n\n    if (runtime.current.tracing.level >= logging.LogLevel.TRACE) {\n      out[0] += \"\\n%c%s\";\n      out.push(\n        \"color: #6B757F\",\n        `attached trace metadata:\\n\\n${JSON.stringify(\n          {\n            span: this.span.metadata,\n            trace: this.metadata\n          },\n          null,\n          2\n        )}`\n      );\n    } else if (runtime.current.tracing.level >= logging.LogLevel.DEBUG) {\n      if (this.span) {\n        // @ts-ignore\n        out.push(this.span.metadata);\n      }\n      if (this.metadata) {\n        // @ts-ignore\n        out.push(this.metadata);\n      }\n    }\n\n    console.log(...out);\n  }\n}\n\n/**\n * A reactive map of spans.\n *\n * @category Helpers\n */\nexport const spans = new ReactiveMap<string, Span>();\n\n/**\n * Helper method for creating a new span.\n *\n * @category Helpers\n */\nexport const createSpan = (name: string, metadata?: Record<string, any>) => {\n  if (runtime.current.tracing) {\n    const span = new Span({ name, metadata });\n    spans.set(name, span);\n    return span;\n  }\n};\n\n\n\n---\nFile: /src/lib/helpers/urls.test.ts\n---\n\nimport { expect, test } from \"vitest\";\n\nimport { urls } from \"./urls\";\n\ntest.only(\"parses with no query parameters\", () => {\n  expect(urls.parse(\"http://localhost:5173/#/foo/bar\")).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/foo/bar\",\n    hash: \"/foo/bar\",\n    query: {\n      original: \"\",\n      params: {}\n    }\n  });\n});\n\ntest.only(\"parses key-value query parameters\", () => {\n  expect(urls.parse(\"http://localhost:5173/#/foo/bar?negative=-123&a=1&str=string&b=true\")).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/foo/bar\",\n    query: {\n      params: {\n        a: \"1\",\n        str: \"string\",\n        b: \"true\",\n        negative: \"-123\"\n      }\n    },\n    hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\"\n  });\n});\n\n// test.only(\"parses array parameter parsing\", () => {\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//     protocol: \"http\",\n//     host: \"localhost\",\n//     port: \"5173\",\n//     path: \"/foo/bar\",\n//     query: {\n//       params: {\n//         a: [\"first\", 3, false, 1.9, 9.99]\n//       }\n//     },\n//     hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//   });\n// });\n\n// test.only(\"query is undefined\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar\").query.toString()).toEqual(\"\");\n// });\n\n// test.only(\"query.toString() matches location.search\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n// });\n\n// test.only(\"query.toString() matches multiples (pagination=2,23&company=123)\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n// });\n\n\n\n---\nFile: /src/lib/helpers/urls.ts\n---\n\nimport { hash, type Hash } from \"../hash\";\nimport { Query } from \"../query.svelte\";\n\nimport { normalize } from \"./normalize\";\n\n/**\n * The returned param value types for paths.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type Param = string | number | boolean;\n\n/**\n * The returned param value types.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type ReturnParam =\n  | RegExp\n  | boolean\n  | boolean[]\n  | number\n  | number[]\n  | string\n  | string[]\n  | Record<string, string | number | boolean | string[] | number[] | boolean[]>;\n\nexport type URL = {\n  protocol: string;\n  host: string;\n  port: string;\n  path: string;\n  query: Query;\n  hash: Hash;\n};\n\nexport namespace urls {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): URL => {\n    if (url === undefined || url.length === 0) {\n      throw new Error(`invalid URL: ${url}`);\n    }\n    const isAbsoluteUrl = url.includes(\"://\");\n    if (isAbsoluteUrl) {\n      const [protocol, remaining] = url.split(\"://\");\n      const hostPortMatch = remaining.match(/^([^/:]+)(?::(\\d+))?(.*)$/);\n      const [host, port, path] = hostPortMatch?.slice(1) ?? [];\n\n      const [before, queryString = \"\"] = (path || \"\").split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol,\n        host,\n        port,\n        path: normalize(before) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    } else {\n      // Handle relative URLs\n      const [pathPart, queryString = \"\"] = url.split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol: window.location.protocol.replace(\":\", \"\"),\n        host: window.location.hostname,\n        port: window.location.port,\n        path: normalize(pathPart) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    }\n  };\n\n  export const path = (path: string): string => {\n    return path.split(\"?\")[0];\n  };\n}\n\n\n\n---\nFile: /src/lib/hash.test.ts\n---\n\n// import { describe, expect, test } from \"vitest\";\n\n// import { hash } from \"./hash\";\n\n// describe(\"simple params\", () => {\n//   test(\"simple params\", () => {\n//     expect(hash.parse(\"/foo/bar?negative=-123&a=1&str=string&b=true\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: 1,\n//           str: \"string\",\n//           b: true,\n//           negative: -123\n//         }\n//       },\n//       hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\"\n//     });\n//   });\n// });\n\n// describe(\"crazy params\", () => {\n//   test(\"crazy params\", () => {\n//     expect(hash.parse(\"/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: [\"first\", 3, false, 1.9, 9.99]\n//         }\n//       },\n//       hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//     });\n//   });\n// });\n\n\n\n---\nFile: /src/lib/hash.ts\n---\n\nimport { Query } from \"./query.svelte\";\n\nexport type Hash = {\n  path: string;\n  query: Query;\n  hash: string;\n};\n\nexport namespace hash {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): Hash => {\n    if (url) {\n      const [_, afterHash = \"\"] = url.split(\"#\");\n      const [path, queryString = \"\"] = afterHash.split(\"?\");\n      return {\n        path,\n        query: new Query(queryString),\n        hash: afterHash\n      };\n    }\n  };\n}\n\n\n\n---\nFile: /src/lib/hooks.ts\n---\n\nimport type { RouteResult } from \"./route.svelte\";\n\n/**\n * Hooks are functions that can be used to modify the behavior of routing\n * when a route is navigated to (before and/or after).\n *\n * The return value of the hook is a boolean that determines if the route should\n * be navigated to. If the hook returns `false` (or nothing), navigation will be cancelled.\n *\n * @category hooks\n */\nexport type HookReturn = void | boolean | Promise<boolean> | Promise<void>;\nexport type Hook = (route: RouteResult) => HookReturn;\n\n\n\n---\nFile: /src/lib/index.ts\n---\n\nexport * from \"./actions\";\nexport { active } from \"./actions/active.svelte\";\nexport { RouteOptions } from \"./actions/options\";\nexport { route } from \"./actions/route.svelte\";\nexport * from \"./helpers\";\nexport type { Condition, Evaluation, EvaluationResult } from \"./helpers/evaluators\";\nexport { goto } from \"./helpers/goto\";\nexport { identify, type Identities, type Identity } from \"./helpers/identify\";\nexport { logging } from \"./helpers/logging\";\nexport { marshal, type Marshalled } from \"./helpers/marshal\";\nexport { normalize } from \"./helpers/normalize\";\nexport { query } from \"./helpers/query\";\nexport { runtime } from \"./helpers/runtime\";\nexport { Span, Trace } from \"./helpers/tracing.svelte\";\nexport type { ReturnParam as Param } from \"./helpers/urls\";\nexport type { Hook } from \"./hooks\";\nexport { Query } from \"./query.svelte\";\nexport type { QueryEvaluationResult, QueryType } from \"./query.svelte\";\nexport { registry, type Registry } from \"./registry.svelte\";\nexport type { ApplyFn, Route, RouteConfig, RouteResult } from \"./route.svelte\";\nexport { RouterInstanceConfig } from \"./router-instance-config\";\nexport { RouterInstance } from \"./router-instance.svelte\";\nexport { default as Router } from \"./router.svelte\";\nexport { getStatusByValue, StatusCode, type Statuses } from \"./statuses\";\n\n\n\n---\nFile: /src/lib/path.ts\n---\n\n/**\n * @remarks\n * Future home of more path related functionality.\n */\nimport { Query } from \"./query.svelte\";\n\n/**\n * The types of values that can be used as a path.\n *\n * @category Router\n */\nexport type PathType = string | number | RegExp | Function | Promise<unknown>;\n\nexport class Path {\n  protocol: string;\n  host: string;\n  path: string;\n  query: Query;\n\n  constructor() {\n    this.protocol = location.protocol;\n    this.host = location.host;\n    this.path = location.pathname;\n    if (location.search.length > 0) {\n      this.query = new Query(Object.fromEntries(new URLSearchParams(location.search)));\n    }\n  }\n\n  toURL(): string {\n    return `${this.protocol}://${this.host}${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n\n  toURI(): string {\n    return `${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n}\n\nexport namespace paths {\n  export const base = (base: string, path: string): boolean => {\n    return path.match(new RegExp(`^${base}(/|$)`)) !== null;\n  };\n}\n\n\n\n---\nFile: /src/lib/query.svelte.ts\n---\n\nimport { evaluators, type Condition } from \"./helpers/evaluators\";\nimport { goto } from \"./helpers/goto\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport type { ReturnParam } from \"./helpers/urls\";\n\n/**\n * The types of values that can be used as a query.\n *\n * @category Router\n */\nexport type QueryType<T = unknown> = Record<string, string | number | RegExp | Function | Promise<T>>;\n\n/**\n * The types of values that the {Query} test method can return.\n *\n * @category Router\n */\nexport type QueryEvaluationResult = {\n  condition: Condition;\n  matches?: Record<string, ReturnParam>;\n};\n\n/**\n * Query string operations.\n *\n * @category Helpers\n */\nexport class Query {\n  params: Record<string, ReturnParam> = {};\n  original?: string;\n\n  constructor(query?: Record<string, string> | string | Query | Record<string, ReturnParam>) {\n    if (typeof query === \"string\") {\n      this.original = query;\n    }\n\n    if (query) {\n      const marshalled = marshal(query);\n      if (marshalled.value) {\n        this.params = marshalled.value as Record<string, ReturnParam>;\n      }\n    }\n  }\n\n  /**\n   * Get a value from the query string parameters and optionally provide\n   * a default value if the key is not found.\n   *\n   * @param key - The key to get the value from.\n   * @param defaultValue - The default value to return if the key is not found.\n   */\n  get<T>(key: string, defaultValue?: T): T {\n    return (this.params[key] as T) || defaultValue;\n  }\n\n  /**\n   * Set a value in the query string parameters.\n   */\n  set(key: string, value: string) {}\n\n  /**\n   * Delete a value from the query string parameters.\n   */\n  delete(key: string) {\n    delete this.params[key];\n  }\n\n  /**\n   * Clear the query string parameters.\n   */\n  clear() {\n    this.params = {};\n  }\n\n  goto(path: string) {\n    goto(path, this.params);\n  }\n\n  test(inbound: Query): QueryEvaluationResult {\n    if (typeof inbound === \"object\") {\n      const matches: Record<string, ReturnParam> = {};\n      for (const [key, test] of Object.entries(inbound.params)) {\n        if (this.params[key]) {\n          const marshalled = marshal(this.params[key]);\n          if (test instanceof RegExp) {\n            const res = evaluators.any[Identities.regexp](test, this.params[key]);\n            if (res) {\n              matches[key] = res;\n            } else {\n              return {\n                condition: \"no-match\"\n              };\n            }\n          } else if (marshalled.identity === Identities.number) {\n            if (marshalled.value === this.params[key]) {\n              matches[key] = marshalled.value as number;\n            }\n          } else if (marshalled.identity === Identities.string) {\n            matches[key] = marshalled.value === this.params[key];\n          } else if (marshalled.identity === Identities.boolean) {\n            matches[key] = marshalled.value === Boolean(this.params[key]);\n          } else if (marshalled.identity === Identities.array) {\n            matches[key] = (marshalled.value as Array<unknown>).includes(this.params[key]);\n          }\n        } else {\n          return {\n            condition: \"no-match\"\n          };\n        }\n      }\n\n      if (Object.keys(matches).length === Object.keys(inbound).length && evaluators.valid[Identities.object](matches)) {\n        return {\n          condition: \"exact-match\",\n          matches: marshal(matches).value as Record<string, ReturnParam>\n        };\n      }\n\n      return {\n        condition:\n          Object.keys(matches).length > 1 && Object.keys(inbound).length !== Object.keys(matches).length\n            ? \"exact-match\"\n            : \"no-match\",\n        matches: matches as Record<string, ReturnParam>\n      };\n    }\n  }\n\n  /**\n   * Convert the query string parameters to a string.\n   */\n  toString() {\n    const stringifyValue = (value: any): string => {\n      if (Array.isArray(value)) {\n        return value.map((v) => stringifyValue(v)).join(\",\");\n      }\n      if (typeof value === \"object\" && value !== null) {\n        return Object.entries(value)\n          .map(([k, v]) => `${k}:${stringifyValue(v)}`)\n          .join(\",\");\n      }\n      // console.log(\"stringifyValue\", value, typeof value);\n      return encodeURIComponent(value);\n    };\n\n    return Object.entries(this.params)\n      .map(([key, value]) => `${encodeURIComponent(key)}=${stringifyValue(value)}`)\n      .join(\"&\");\n    // return preserveOriginal ? this._original : \"\";\n  }\n\n  /**\n   * Convert the query string parameters to a JSON object given\n   * we may have parameter values that are not json serializable\n   * out of the box.\n   */\n  toJSON(preserveOriginal?: boolean) {\n    return Object.fromEntries(Object.entries(this.params).map(([key, value]) => [key, value.toString()]));\n  }\n}\n\n\n\n---\nFile: /src/lib/query.test.ts\n---\n\nimport { describe, expect, test } from \"vitest\";\n\nimport { Query } from \"./query.svelte\";\n\ndescribe(\"query\", () => {\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"b=2&a=false&c=str\").params).toEqual({ a: false, b: 2, c: \"str\" });\n  });\n\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"nonarray=1&a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\").params).toEqual({\n      nonarray: 1,\n      a: [\"first\", 3, false, 1.9, 9.99]\n    });\n  });\n});\n\n\n\n---\nFile: /src/lib/registry.svelte.ts\n---\n\nimport type { ApplyFn } from \"./route.svelte\";\nimport { RouterInstanceConfig } from \"./router-instance-config\";\nimport { RouterInstance } from \"./router-instance.svelte\";\nimport { ReactiveMap } from \"./utilities.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * Handles the dynamic registration and deregistration of router instances.\n *\n * @remarks\n * This is a singleton and should not be instantiated directly.\n *\n * @category Registry\n */\nexport class Registry {\n  /**\n   * Container for router instances.\n   */\n  instances = new ReactiveMap<string, RouterInstance>();\n\n  constructor() {\n    // Prevent multiple instantiation during HMR by storing instance in window\n    if ((window as any).__SVELTE_SPA_ROUTER_REGISTERED__) {\n      return (window as any).__SVELTE_SPA_ROUTER_REGISTERED__;\n    }\n    (window as any).__SVELTE_SPA_ROUTER_REGISTERED__ = this;\n\n    const { pushState, replaceState } = window.history;\n\n    window.history.pushState = function (...args) {\n      pushState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"pushState\"));\n    };\n\n    window.history.replaceState = function (...args) {\n      replaceState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"replaceState\"));\n    };\n  }\n\n  /**\n   * Register a new router instance.\n   *\n   * @param {Instance} config The instance to register.\n   * @param {ApplyFn} applyFn The function to call for applying route changes.\n   *\n   * @see {@link deregister}: The opposite of this method.\n   */\n  register(config: RouterInstanceConfig, applyFn: ApplyFn, span?: Span): RouterInstance {\n    if (this.instances.has(config.id)) {\n      throw new Error(`router instance with id ${config.id} already registered`);\n    }\n\n    const instance = new RouterInstance(config, applyFn);\n\n    if (span) {\n      span.trace({\n        prefix: \"🔍\",\n        name: \"registry.register\",\n        description: \"registering a new router instance\",\n        metadata: {\n          router: {\n            id: config.id,\n            basePath: config.basePath\n          },\n          location: \"/src/lib/registry.svelte:register()\",\n          config\n        }\n      });\n    }\n\n    this.instances.set(config.id, instance);\n\n    return instance;\n  }\n\n  /**\n   * Deregister a router instance.\n   *\n   * @param {string} id The id of the instance to deregister.\n   */\n  deregister(id: string, span?: Span): void {\n    const instance = this.instances.get(id);\n    if (span) {\n      span.trace({\n        prefix: instance ? \"✅\" : \"❌\",\n        name: \"registry.deregister\",\n        description: \"deregistering a router instance\",\n        metadata: {\n          router: {\n            id,\n            basePath: instance.config.basePath\n          },\n          location: \"/src/lib/registry.svelte:deregister()\",\n          config: instance.config\n        }\n      });\n    }\n\n    if (instance) {\n      this.instances.delete(id);\n    } else {\n      throw new Error(`router instance with id ${id} not found`);\n    }\n  }\n\n  get(id: string): RouterInstance {\n    const instance = this.instances.get(id);\n\n    return instance;\n  }\n}\n\n/**\n * Expose a reference to the registry of router instances.\n *\n * This is used to register & unregister router instances and to get\n * the route for a given path.\n *\n * This is a singleton and should not be instantiated directly and should\n * never be accessed outside of the scope of this package in most cases.\n *\n * @category Registry\n */\nexport const registry = (window as any).__SVELTE_SPA_ROUTER_REGISTRY__ || new Registry();\n\n\n\n---\nFile: /src/lib/route.svelte.ts\n---\n\n/**\n * This is the doc comment for file1.ts\n *\n * @packageDocumentation\n */\nimport type { Component, Snippet } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { paths, type PathType } from \"./path\";\nimport { Query } from \"./query.svelte\";\n\nimport { evaluators, type Condition, type Evaluation } from \"./helpers/evaluators\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport { normalize } from \"./helpers/normalize\";\nimport { regexp } from \"./helpers/regexp\";\nimport type { Span, Trace } from \"./helpers/tracing.svelte\";\nimport { urls, type ReturnParam } from \"./helpers/urls\";\n\n/**\n * A route result that includes the evaluation results of the route.\n *\n * This type is necessary for the internal workings of the router to ensure that\n * the evaluation results are included in the route result and to avoid requiring\n * it to be merged in the original route instance.\n *\n * @since 2.0.0\n * @category Router\n * @example\n * ```ts\n * const routeResult = new RouteResult({\n *   router: myRouter,\n *   route: myRoute,\n *   result: {\n *     path: {\n *       condition: \"exact-match\",\n *       original: \"/users/123\",\n *       params: { id: \"123\" }\n *     },\n *     querystring: {\n *       condition: \"exact-match\",\n *       original: { filter: \"active\" },\n *       params: { filter: \"active\" }\n *     },\n *     component: UserComponent,\n *     status: 200\n *   }\n * });\n * ```\n */\nexport class RouteResult {\n  /**\n   * The route that was evaluated to render this result.\n   *\n   * @since 2.0.0\n   * @readonly\n   * @remarks This may be undefined if the route result was created without an associated route.\n   * @see {@link Route}\n   */\n  route?: Route;\n\n  /**\n   * The comprehensive result of routing evaluation that rendered this route.\n   *\n   * This object contains all the information gathered during the route matching process,\n   * including path evaluation, querystring parsing, component resolution, and status determination.\n   *\n   * @since 2.0.0\n   * @readonly\n   * @remarks The result object is immutable once created and represents a snapshot of the routing state.\n   *\n   * @example\n   * ```ts\n   * // Accessing route evaluation results\n   * console.log(routeResult.result.path.original); // \"/users/123\"\n   * console.log(routeResult.result.path.params?.id); // \"123\"\n   * console.log(routeResult.result.status); // 200\n   * ```\n   */\n  result: {\n    /**\n     * The path evaluation results containing the matched path information.\n     *\n     * This object provides detailed information about how the current URL path\n     * was matched against the route's path pattern, including any extracted parameters.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For a route with path \"/users/:id\" and URL \"/users/123\"\n     * const pathResult = {\n     *   condition: \"exact-match\",\n     *   original: \"/users/123\",\n     *   params: { id: \"123\" }\n     * };\n     * ```\n     */\n    path: {\n      /**\n       * The evaluation condition indicating how the path was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"base-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original path string that was evaluated during routing.\n       *\n       * This represents the actual path portion of the URL that was processed,\n       * without query parameters or hash fragments.\n       *\n       * @since 2.0.0\n       * @example \"/users/123/profile\"\n       * @remarks This path is normalized and may differ from the raw URL path\n       */\n      original: string;\n\n      /**\n       * The parameters extracted from the path during evaluation.\n       *\n       * Contains named parameters extracted from the path pattern matching,\n       * such as route parameters defined with `:paramName` syntax or regex groups.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @example\n       * ```ts\n       * // For route \"/users/:id\" matching \"/users/123\"\n       * params: { id: \"123\" }\n       *\n       * // For regex route \"^/posts/(?<slug>.+)$\" matching \"/posts/my-article\"\n       * params: { slug: \"my-article\" }\n       * ```\n       * @see {@link ReturnParam} For parameter value types\n       * @remarks This is undefined when no parameters are extracted from the path\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The querystring evaluation results containing parsed query parameters.\n     *\n     * This object provides information about how the URL's query string\n     * was processed and any parameters that were extracted or matched.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For URL \"?filter=active&page=2\"\n     * const querystringResult = {\n     *   condition: \"exact-match\",\n     *   original: { filter: \"active\", page: \"2\" },\n     *   params: { filter: \"active\", page: \"2\" }\n     * };\n     * ```\n     */\n    querystring: {\n      /**\n       * The evaluation condition indicating how the querystring was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"partial-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original querystring data that was evaluated during routing.\n       *\n       * This can be either a parsed object representation of query parameters\n       * or the raw querystring, depending on how the route was configured.\n       *\n       * @since 2.0.0\n       * @see {@link ReturnParam} For supported parameter types\n       * @example\n       * ```ts\n       * // Parsed object format\n       * original: { filter: \"active\", sort: \"name\" }\n       *\n       * // Raw string format\n       * original: \"filter=active&sort=name\"\n       * ```\n       */\n      original: ReturnParam;\n\n      /**\n       * The parameters extracted from the querystring during evaluation.\n       *\n       * Contains the processed query parameters after applying any route-specific\n       * querystring matching rules and transformations.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @see {@link ReturnParam} For parameter value types\n       * @example\n       * ```ts\n       * // Standard query parameters\n       * params: { search: \"term\", page: \"1\" }\n       *\n       * // With type conversion\n       * params: { page: 1, active: true }\n       * ```\n       * @remarks This may be undefined if no querystring processing was required\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The component that was resolved and rendered when the route became active.\n     *\n     * This represents the actual component instance, snippet, or component factory\n     * that was determined during the routing process. It can be a direct component\n     * reference, a lazy-loaded component function, or a Svelte snippet.\n     *\n     * @since 2.0.0\n     * @default undefined\n     * @see {@link Component} Svelte component type\n     * @see {@link Snippet} Svelte snippet type\n     * @example\n     * ```ts\n     * // Direct component reference\n     * component: UserProfile\n     *\n     * // Lazy-loaded component\n     * component: () => import('./UserProfile.svelte')\n     *\n     * // Svelte snippet\n     * component: mySnippet\n     * ```\n     * @remarks This is undefined when the route doesn't render a component directly,\n     * such as when using hooks for custom rendering logic\n     */\n    component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n    /**\n     * The HTTP-style status code representing the result of the routing operation.\n     *\n     * This numeric code indicates the outcome of the route evaluation process,\n     * following HTTP status code conventions for consistency and familiarity.\n     * The status helps determine how the route result should be handled by\n     * status handlers and middleware.\n     *\n     * @since 2.0.0\n     * @see {@link StatusCode} For predefined status code constants\n     * @see {@link Statuses} For status-specific handlers\n     * @example\n     * ```ts\n     * status: 200  // Successful route match\n     * status: 404  // Route not found\n     * status: 401  // Unauthorized access\n     * status: 500  // Internal routing error\n     * ```\n     * @remarks Common values include 200 (OK), 404 (Not Found), 401 (Unauthorized),\n     * 403 (Forbidden), and 500 (Internal Server Error)\n     */\n    status: number;\n  };\n\n  /**\n   * The constructor for the `RouteResult` class.\n   *\n   * @param result The result of the route evaluation.\n   */\n  constructor(result: RouteResult) {\n    this.route = result.route;\n    this.result = result.result;\n  }\n\n  /**\n   * The string representation of the route including the querystring.\n   */\n  toString?(): string {\n    let querystring = \"\";\n    if (this.result.querystring.original && typeof this.result.querystring.original === \"object\") {\n      const params = new URLSearchParams();\n      for (const [key, value] of Object.entries(this.result.querystring.original)) {\n        if (value !== undefined && value !== null) {\n          params.append(key, String(value));\n        }\n      }\n      querystring = params.toString();\n    } else if (this.result.querystring.original) {\n      querystring = String(this.result.querystring.original);\n    }\n\n    return `${this.result.path.original}${querystring ? `?${querystring}` : \"\"}`;\n  }\n}\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn = (result: RouteResult, span?: Span) => void;\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn2 = (result: RouteResult, span?: Span) => void;\n\n/**\n * A generic type that can be used to test the type of a value.\n * @category Router\n * @example\n * ```ts\n * const a: Testing<string> = \"asdf\";\n * const b: Testing<number> = 123;\n * ```\n */\nexport type Testing<T> = T;\n\nexport class RouteConfig {\n  name?: string | number;\n  basePath?: string;\n  path?: PathType;\n  querystring?: Record<string, ReturnParam>;\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n  props?: Record<string, any>;\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n  children?: RouteConfig[];\n  status?: number;\n\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = config.path;\n    this.querystring = config.querystring;\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n  }\n\n  toJSON?(): any {\n    return {\n      name: this.name,\n      basePath: this.basePath,\n      path: this.path,\n      props: this.props,\n      component: this.component,\n      querystring: this.querystring,\n      hooks: this.hooks,\n      children: this.children,\n      status: this.status\n    };\n  }\n}\n\n/**\n * A route that can be navigated to.\n * @example\n * ```ts\n * const routes: Route[] = [\n *   {\n *     component: Home\n *   },\n *   {\n *      path: \"(?<child>.*)\",\n *      component: ParseRouteParams\n *   }\n * ]\n * ```\n *\n * @category Router\n */\nexport class Route {\n  /**\n   * The unique identifier of this route.\n   * This is useful if you need to track routes outside of the router's scope.\n   *\n   * @optional If no value is provided, the route will not have a name.\n   */\n  name?: string | number;\n\n  /**\n   * The base path of the route.\n   *\n   * This is useful if you want to be declarative about the base path of the route\n   * and not depend on the router to determine the base path.\n   */\n  basePath?: string;\n\n  /**\n   * The path of the route to match against the current path.\n   *\n   * @optional If not provided, the route will match any path\n   * as it will be the default route.\n   */\n  path?: PathType;\n\n  /**\n   * The query params of the route.\n   *\n   * @optional If no value is provided, there are no query params.\n   */\n  querystring?: Query;\n\n  /**\n   * The component to render when the route is active.\n   *\n   * @optional If no value is provided, the route will not render a component.\n   * This is useful if you want to use pre or post hooks to render a component\n   * or snippet conditionally.\n   */\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n  /**\n   * The props to pass to the component.\n   *\n   * @optional If a value is provided, the component will receive this value in $props().\n   */\n  props?: Record<string, any>;\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The children routes of the route.\n   *\n   * This is useful if you want to be declarative about the routes that are direct\n   * children of this route and not depend on the router to determine the children\n   * when there are multiple <Router/> instances.\n   *\n   *\n   * @optional If no value is provided, there are no direct child routes. Routes may\n   * be mapped to children routes by the router when there are multiple <Router/> instances\n   * with overlapping `basePath` values.\n   *\n   * @example\n   * ```ts\n   * const routes: Route[] = [\n   *   ...\n   *   {\n   *     path: \"/users\",\n   *     children: [\n   *       {\n   *         path: \"/:id\",\n   *         component: User\n   *       }\n   *     ]\n   *   }\n   *   ...\n   * ]\n   * ```\n   */\n  children?: Route[];\n\n  /**\n   * The status of the route once it has been matched or otherwise processed.\n   */\n  status?: number;\n\n  /**\n   * Traces are a list of objects that describe the route's path and query params\n   * as it is processed by the router.\n   */\n  traces?: Trace[] = $state([]);\n\n  /**\n   * The constructor for the `Route` class.\n   *\n   * @param {Route} config An instance of the `Route` class.\n   */\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = typeof config.path === \"string\" ? normalize(config.path) : config.path;\n\n    if (config.querystring) {\n      this.querystring = new Query(config.querystring);\n    }\n\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n    this.children = config.children?.map((child) => new Route(child));\n  }\n\n  /**\n   * Parse the route against the given path.\n   * @param path The path to parse against the route.\n   */\n  test?(path: PathType): Evaluation {\n    const matcher = urls.path(path.toString());\n    // Handle string paths being passed in at the route.path level:\n    if (typeof this.path === \"string\") {\n      // Detect if this path contains regex syntax:\n      if (regexp.can(this.path)) {\n        // Path is a regex, so we need to test it against the path passed in:\n        const match = regexp.from(this.path).exec(matcher);\n        if (match) {\n          return {\n            condition: \"exact-match\",\n            params: match.groups\n          };\n        }\n      } else {\n        // Path is not a regex, so we then check if the path passed in is a direct match:\n        if (this.path === matcher) {\n          return {\n            condition: \"exact-match\",\n            params: this.path\n          };\n        } else if (paths.base(this.path, matcher)) {\n          return {\n            condition: \"base-match\",\n            params: {}\n          };\n        }\n      }\n    }\n    // Handle RegExp instances being passed in at the route.path level:\n    else if (this.path instanceof RegExp) {\n      const res = evaluators.any[Identities.regexp](this.path, matcher);\n      if (res) {\n        return {\n          condition: \"exact-match\",\n          params: res\n        };\n      }\n    }\n    // Handle numeric paths being passed in at the route.path level:\n    else if (typeof this.path === \"number\" && this.path === marshal(matcher).value) {\n      throw new Error(\"numbered route match not supported at the route.path level\");\n    }\n\n    return {\n      condition: \"no-match\",\n      params: {}\n    };\n  }\n\n  /**\n   * The absolute path of the route by combining the router's base path and\n   * the route's path.\n   */\n  absolute?(): string {\n    /**\n     * If the router has a base path, we need to combine it with the route's path\n     * otherwise it will have \"undefined\" as the base path and the path will be\n     * incorrect:\n     */\n    if (this.basePath) {\n      return `${this.basePath}${this.path}`;\n    }\n    return this.path.toString();\n  }\n}\n\n\n\n---\nFile: /src/lib/router-instance-config.ts\n---\n\nimport { type Component } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { RouteConfig } from \"./route.svelte\";\nimport type { Statuses } from \"./statuses\";\n\n/**\n * The configuration for a new router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstanceConfig {\n  /**\n   * The id for the router instance.\n   *\n   * @optional If no value is provided, the id will be a random string of characters.\n   */\n  id?: string;\n\n  /**\n   * The base path for the router instance.\n   *\n   * @optional If no value is provided, the base path will be \"/\".\n   */\n  basePath?: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes: RouteConfig[];\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The initial path for the router instance.\n   *\n   * @optional If no value is provided, the initial path will be the current path of the browser.\n   */\n  initialPath?: string;\n\n  /**\n   * The not found component for the router instance.\n   *\n   * @optional If no value is provided and no route could be found,\n   * the router will will not render anything.\n   */\n  notFoundComponent?: Component<any>;\n\n  /**\n   * The default components rendered when a route is not found and\n   * the status code is in one of the following:\n   * 400, 401, 403, 404, 500\n   * @optional If no value is provided, the default components will not be rendered.\n   */\n  statuses?: Statuses;\n\n  /**\n   * Whether to allow the same route to be rendered if the conditions are the\n   * same (taking in to account the path, query, and status code).\n   */\n  renavigation?: boolean;\n\n  /**\n   * The constructor for this router instance.\n   *\n   * @param {RouterInstanceConfig} config The config for this router instance.\n   */\n  constructor(config: RouterInstanceConfig) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.basePath = config.basePath;\n    this.hooks = config.hooks;\n    this.initialPath = config.initialPath;\n    this.notFoundComponent = config.notFoundComponent;\n    this.statuses = config.statuses;\n    this.routes = config.routes.map(\n      (route) =>\n        new RouteConfig({\n          ...route,\n          ...config\n        })\n    );\n  }\n\n  toJSON(): any {\n    return {\n      id: this.id,\n      basePath: this.basePath,\n      routes: this.routes,\n      hooks: this.hooks\n    };\n  }\n}\n\n\n\n---\nFile: /src/lib/router-instance.svelte.ts\n---\n\nimport { Query, registry, RouterInstanceConfig, Span, type ApplyFn, type Hook } from \".\";\nimport { Route, RouteResult } from \"./route.svelte\";\nimport { StatusCode } from \"./statuses\";\nimport { execute } from \"./utilities.svelte\";\n\nimport { SuccessfulConditions } from \"./helpers/evaluators\";\nimport { normalize } from \"./helpers/normalize\";\nimport { createSpan } from \"./helpers/tracing.svelte\";\nimport { urls } from \"./helpers/urls\";\n\n/**\n * The default routes that are used when no routes match.\n *\n * @category Router\n */\nexport const defaultRoutes = [\"\", \"/\", \"/*\", \"/^.*$/\", \"/.*/\"];\n\n/**\n * The handlers type that is used when registering a router instance.\n *\n * This is used to restore the original history methods when the last instance is destroyed\n * and to register & unregister the event listeners for the router instances to prevent memory leaks.\n *\n * @category Router\n */\nexport type RouterHandlers = {\n  /**\n   * The handler for the pushState event.\n   */\n  pushStateHandler: () => void;\n\n  /**\n   * The handler for the replaceState event.\n   */\n  replaceStateHandler: () => void;\n\n  /**\n   * The handler for the popState event.\n   */\n  popStateHandler: () => void;\n\n  /**\n   * The handler for the hashchange event.\n   */\n  hashChangeHandler: () => void;\n};\n\n/**\n * A class that represents a router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstance {\n  /**\n   * The id of the router instance.\n   */\n  id: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes = new Set<Route>();\n\n  /**\n   * The handlers for the router instance.\n   */\n  handlers: RouterHandlers;\n\n  /**\n   * The config for the router instance.\n   */\n  config: RouterInstanceConfig;\n\n  /**\n   * The apply function for the router instance.\n   */\n  applyFn: ApplyFn;\n\n  /**\n   * Whether the router instance is navigating.\n   */\n  navigating = $state(false);\n\n  /**\n   * The current route for the router instance.\n   */\n  current = $state<RouteResult>();\n\n  /**\n   * The constructor for the RouterInstance class.\n   *\n   * @param {RouterInstanceConfig} config The config for the router instance.\n   * @param {ApplyFn} applyFn The apply function for the router instance.\n   */\n  constructor(config: RouterInstanceConfig, applyFn: ApplyFn) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.config = config;\n    this.applyFn = applyFn;\n\n    this.handlers = {\n      pushStateHandler: () => this.handleStateChange(location.toString()),\n      replaceStateHandler: () => this.handleStateChange(location.toString()),\n      popStateHandler: () => this.handleStateChange(location.toString()),\n      hashChangeHandler: () => this.handleStateChange(location.toString())\n    };\n\n    window.addEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.addEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.addEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.addEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    for (let route of config.routes) {\n      this.routes.add(\n        new Route({\n          ...route,\n          /**\n           * If the route has no base path (because it's optional), use\n           * the router instance's base path.\n           */\n          basePath: route.basePath || this.config.basePath\n        })\n      );\n    }\n  }\n\n  /**\n   * Process a state change event from the browser history API.\n   *\n   * This method is called when the browser history API is used to change the\n   * current route via the `pushState`, `replaceState`, or `popState` methods.\n   *\n   * The method will evaluate the route for the given path and query, and apply\n   * the route to the router instance to ultimately call the `applyFn` function\n   * on the downstream router component to render the new route.\n   *\n   * @param {string} path The path to handle the state change for.\n   * @param {Query} query The query to handle the state change for.\n   * @param {Span} span @optional The span to attach traces to. If not provided,\n   * a new span will be created.\n   */\n  async handleStateChange(url: string, span?: Span): Promise<void> {\n    const { path, query } = urls.parse(url);\n    this.navigating = true;\n\n    if (!span) {\n      span = createSpan(\"detected history change event\");\n    }\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.handleStateChange\",\n      description: `attempting to handle a new state change for path \"${path}\"`,\n      metadata: {\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n        basePath: this.config.basePath,\n        path,\n        query,\n        url\n      }\n    });\n\n    const result = await this.get(path, query, span);\n\n    if (result && SuccessfulConditions.includes(result.result.path.condition)) {\n      span?.trace({\n        prefix: \"✅\",\n        name: \"router-instance.handleStateChange\",\n        description: `route found for path \"${path}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query: query?.params || false,\n          route: result,\n          url\n        }\n      });\n\n      // Run the global pre hooks:\n      if (this.config.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, this.config.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Run the route specific pre hooks:\n      if (result.route?.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Contact the downstream router component to apply the route:\n      this.applyFn(result, span);\n\n      // Run the route specific post hooks:\n      if (result && result.route?.hooks?.post) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.post))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Finally, run the global post hooks:\n      if (this.config.hooks?.post) {\n        await this.evaluateHooks(result, this.config.hooks.post);\n      }\n\n      this.current = result;\n    }\n\n    this.navigating = false;\n  }\n\n  async evaluateHooks(route: RouteResult, hooks: Hook | Hook[]): Promise<boolean> {\n    if (Array.isArray(hooks)) {\n      for (const hook of hooks) {\n        if (!(await execute(() => hook(route)))) {\n          return false;\n        }\n\n        /**\n         * Add small delay between hooks to prevent rapid History API\n         * calls causing the browser to halt (firefox specifically).\n         */\n        await new Promise((resolve) => setTimeout(resolve, 50));\n      }\n    } else {\n      if (!(await execute(() => hooks(route)))) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Retrieve a route for a given path.\n   *\n   * @param {string} path The path to get the route for.\n   *\n   * @returns {RegistryMatch} The matched route for the given path.\n   */\n  async get(path: string, query?: Query, span?: Span): Promise<RouteResult> {\n    path = path.replace(\"/#\", \"\");\n    const normalized = normalize(path.replace(this.config.basePath || \"/\", \"\"));\n    const renderDefaultRoute = (reason: string): RouteResult => {\n      let defaultRoute: Route;\n\n      for (const route of this.routes) {\n        if (!route.path || defaultRoutes.includes(route.path.toString())) {\n          defaultRoute = route;\n          break;\n        }\n      }\n\n      span?.trace({\n        prefix: defaultRoute ? \"✅\" : \"❌\",\n        name: \"router-instance.getDefaultRoute\",\n        description: `get default route because \"${reason}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:get()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query,\n          normalized,\n          route: defaultRoute\n        }\n      });\n\n      if (defaultRoute) {\n        return new RouteResult({\n          route: defaultRoute,\n          result: {\n            path: {\n              condition: \"default-match\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            component: defaultRoute.component,\n            status: StatusCode.OK\n          }\n        });\n      }\n    };\n\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.get\",\n      description: `${this.config.id} with base path \"${this.config.basePath || \"/\"}\" is attempting to get a route for path \"${path}\"`,\n      metadata: {\n        location: \"/src/lib/router-instance.svelte:get()\",\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        path,\n        query,\n        normalized\n      }\n    });\n\n    if (this.config.basePath === path) {\n      return renderDefaultRoute(\"base path is the same as the path\");\n    }\n\n    let candidate: RouteResult;\n\n    /**\n     * Now we check for router nesting:\n     */\n    for (const route of this.routes) {\n      const pathEvaluation = route.test(normalized);\n      if (pathEvaluation && SuccessfulConditions.includes(pathEvaluation.condition)) {\n        span?.trace({\n          prefix: \"✅\",\n          name: \"router-instance.get:routesloop\",\n          description: `${pathEvaluation.condition} for inbound path \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n          metadata: {\n            location: \"/src/lib/router-instance.svelte:get():forloop\",\n            router: {\n              id: this.config.id,\n              basePath: this.config.basePath\n            },\n            path,\n            query,\n            normalized,\n            route,\n            evaluation: {\n              path: pathEvaluation\n            }\n          }\n        });\n\n        if (route.querystring && query) {\n          const queryEvaluation = query.test(route.querystring);\n          if (SuccessfulConditions.includes(queryEvaluation?.condition)) {\n            span?.trace({\n              prefix: \"✅\",\n              name: \"router-instance.get.evaluateQuery\",\n              description: `${queryEvaluation?.condition} evaluating querystring \"${query?.toString()}\" for the route \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n              metadata: {\n                location: \"/src/lib/router-instance.svelte:get()\",\n                router: {\n                  id: this.config.id,\n                  basePath: this.config.basePath\n                },\n                path,\n                query,\n                normalized,\n                evaluation: {\n                  path: pathEvaluation,\n                  querystring: queryEvaluation\n                }\n              }\n            });\n            candidate = new RouteResult({\n              route,\n              result: {\n                path: {\n                  ...pathEvaluation,\n                  original: normalized\n                },\n                querystring: {\n                  ...queryEvaluation,\n                  original: query.toJSON()\n                },\n                component: route.component,\n                status: StatusCode.OK\n              }\n            });\n          }\n        } else {\n          /**\n           * No querystring is configured for this route, so we will\n           * use the querystring from the inbound path.\n           */\n          candidate = new RouteResult({\n            route,\n            result: {\n              path: {\n                ...pathEvaluation,\n                original: normalized\n              },\n              querystring: {\n                condition: \"permitted-no-conditions\",\n                original: query?.toJSON(),\n                params: query?.toJSON()\n              },\n              component: route.component,\n              status: StatusCode.OK\n            }\n          });\n        }\n      }\n    }\n\n    /**\n     * If we've made it this far, we should default to trying to find\n     * a route that has no path configured. This will be treated as\n     * the \"default\" route:\n     */\n    if (path === \"/\") {\n      return renderDefaultRoute(\"no routes match, last resort is to find a default route\");\n    }\n\n    /**\n     * We've exhaausted all options, so we will attempt to locate\n     * a 404 route from the statuses configuration applied to this\n     * router instance.\n     */\n    if (!candidate && this.config.statuses?.[404]) {\n      const status = this.config.statuses[404];\n      if (typeof status === \"function\") {\n        return {\n          result: {\n            ...status(\n              {\n                result: {\n                  path: {\n                    condition: \"permitted-no-conditions\",\n                    original: path\n                  },\n                  querystring: {\n                    condition: \"permitted-no-conditions\",\n                    original: query?.toJSON(),\n                    params: query?.toJSON()\n                  },\n                  status: StatusCode.NotFound\n                }\n              },\n              span\n            ),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      } else {\n        return {\n          result: {\n            ...(status as object),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      }\n    }\n\n    return candidate;\n  }\n\n  /**\n   * Deregister a router instance by removing it from the registry and\n   * restoring the original history methods.\n   *\n   * This is called when a router instance is removed from the DOM\n   * triggered by the `onDestroy` lifecycle method of the router instance.\n   */\n  deregister(span?: Span): void {\n    window.removeEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.removeEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.removeEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.removeEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    registry.deregister(this.config.id, span);\n  }\n\n  /**\n   * Get routes as an array for serialization purposes.\n   *\n   * @returns {Route[]} The routes as an array.\n   */\n  get routesArray(): Route[] {\n    return Array.from(this.routes);\n  }\n\n  /**\n   * Custom JSON serialization to handle Set objects properly.\n   *\n   * @returns {object} The serializable representation of the router instance.\n   */\n  toJSON(): any {\n    return {\n      id: this.id,\n      config: this.config\n    };\n  }\n}\n\n\n\n---\nFile: /src/lib/router.svelte\n---\n\n<script lang=\"ts\">\n  import { onDestroy, unmount, type Component } from \"svelte\";\n  import { createSpan, Span } from \"./helpers/tracing.svelte\";\n  import { registry } from \"./registry.svelte\";\n  import { type RouteResult } from \"./route.svelte\";\n  import { RouterInstanceConfig } from \"./router-instance-config\";\n  import type { RouterInstance } from \"./router-instance.svelte\";\n\n  let { instance = $bindable(), ...rest } = $props<\n    { instance?: RouterInstance } & Partial<RouterInstanceConfig> & Record<string, any>\n  >();\n\n  const span = createSpan(rest.id ? `[${rest.id}]` : \"router\");\n\n  let RenderableComponent = $state<Component | null>(null);\n  let router: RouterInstance;\n  let route: RouteResult = $state();\n  let additionalProps = $state<Record<string, any>>({});\n\n  const apply = async (r: RouteResult, span?: Span) => {\n    route = r;\n    span?.trace({\n      prefix: \"✅\",\n      name: \"apply\",\n      description: `<Router${router.config.id ? ` id=\"${router.config.id}\"` : \"\"}/> applying route ${r.result.path.original} (${r.result.path.condition})`,\n      metadata: {\n        location: \"/src/lib/router.svelte:apply()\",\n        router: {\n          id: router.config.id,\n          basePath: router.config.basePath\n        },\n        result: r\n      }\n    });\n    if (RenderableComponent) {\n      unmount(RenderableComponent, {});\n      RenderableComponent = null;\n    }\n\n    if (typeof r.result.component === \"function\" && r.result.component.constructor.name === \"AsyncFunction\") {\n      // Handle async component by first awaiting the import:\n      const module = await r.result.component();\n      RenderableComponent = module.default || module;\n    } else {\n      // Handle regular component by directly assigning the component:\n      RenderableComponent = r.result.component;\n    }\n    additionalProps = route.route?.props;\n  };\n\n  router = registry.register(new RouterInstanceConfig(rest), apply, span);\n\n  span?.trace({\n    prefix: \"✅\",\n    name: \"<Router/> Component\",\n    description: \"new component mounted\",\n    metadata: {\n      router: {\n        id: router.config.id,\n        basePath: router.config.basePath\n      },\n      location: \"/src/lib/router.svelte:mount()\"\n    }\n  });\n\n  instance = router;\n\n  if (span) {\n    span.metadata = {\n      router: router.config.id\n    };\n  }\n\n  router.handleStateChange(location.toString(), span);\n\n  onDestroy(() => {\n    router.deregister(span);\n  });\n\n  const { routes, basePath, ...restWithoutRoutes } = rest;\n</script>\n\n<RenderableComponent\n  {route}\n  {...additionalProps}\n  {...restWithoutRoutes} />\n\n\n\n---\nFile: /src/lib/statuses.ts\n---\n\nimport type { Component } from \"svelte\";\n\nimport type { Route, RouteResult } from \"./route.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * The available status codes that a route can have called out from the statuses\n * handler mapping.\n *\n * @see {@link Statuses}\n * @category Router\n */\nexport enum StatusCode {\n  OK = 200,\n  PermanentRedirect = 301,\n  TemporaryRedirect = 302,\n  BadRequest = 400,\n  Unauthorized = 401,\n  Forbidden = 403,\n  NotFound = 404,\n  InternalServerError = 500\n}\n\n/**\n * Route status handler mapping.\n *\n * Status handlers are called with a path and should return a new route\n * or a promise that resolves to a new route.\n *\n * @example\n * ```ts\n * const statuses: Statuses = {\n *   [StatusCode.NotFound]: {\n *     component: NotFound,\n *     props: {\n *       importantInfo: \"lets go!\"\n *     }\n *   },\n *   [StatusCode.BadRequest]: (path) => {\n *     notifySomething(path);\n *     return {\n *       component: BadRequest,\n *       props: {\n *         importantInfo: \"something went wrong...\"\n *       }\n *     };\n *   }\n * }\n * ```\n *\n * @see {@link Route}\n * @see {@link StatusCode}\n * @see {@link getStatusByValue}\n * @category Router\n */\nexport type Statuses = Partial<{\n  [K in StatusCode]: (\n    result: RouteResult,\n    span?: Span\n  ) => Route | Promise<Route> | Component<any> | { component: Component<any>; props?: Record<string, any> };\n}>;\n\n/**\n * Get the status by value.\n *\n * @param {number} value The value to get the status for.\n * @returns {StatusCode} The status.\n *\n * @see {@link StatusCode}\n * @category Router\n */\nexport const getStatusByValue = (value: number) => {\n  return Object.keys(StatusCode)[Object.values(StatusCode).indexOf(value)];\n};\n\n\n\n---\nFile: /src/lib/utilities.svelte.ts\n---\n\n/**\n * Wait for a predicate to become true with timeout handling.\n *\n * @param predicate Function that returns boolean to check for\n * @param timeout Time in milliseconds to wait before timing out\n * @throws Error if timeout is reached before predicate becomes true\n *\n * @category utilities\n */\nexport async function wait(predicate: () => boolean, timeout = 5000): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const timer = setTimeout(() => {\n      reject(new Error(`Timeout after ${timeout}ms waiting for predicate: ${predicate}`));\n    }, timeout);\n\n    const check = async () => {\n      if (predicate()) {\n        clearTimeout(timer);\n        resolve();\n      } else {\n        setTimeout(check, 50);\n      }\n    };\n\n    check();\n  });\n}\n\n/**\n * Check if a value is a promise.\n *\n * @param value - The value to check.\n *\n * @returns True if the value is a promise, false otherwise.\n *\n * @category utilities\n */\nexport function isPromise(value: any): boolean {\n  return !!value && (typeof value === \"object\" || typeof value === \"function\") && typeof value.then === \"function\";\n}\n\n/**\n * Execute a function and return a promise if the function is a promise.\n *\n * @param fn - The function to execute.\n *\n * @returns A promise if the function is a promise, otherwise the function result.\n *\n * @category utilities\n */\nexport const execute = async <T>(fn: () => T | Promise<T>): Promise<T> => {\n  if (isPromise(fn)) {\n    return await fn();\n  } else {\n    return fn();\n  }\n};\n\n/**\n * A reactive map that can be observed for changes using `$state()`.\n *\n * @category utilities\n */\nexport class ReactiveMap<K, V> extends Map<K, V> {\n  #state = $state(false);\n\n  get size() {\n    this.#state;\n    return super.size;\n  }\n\n  #trig() {\n    this.#state = !this.#state;\n  }\n\n  add(key: K, value: V) {\n    if (this.has(key)) {\n      throw new Error(`key ${key} already exists`);\n    }\n    return this.set(key, value);\n  }\n\n  get(key: K) {\n    this.#state;\n    return super.get(key);\n  }\n\n  set(key: K, value: V) {\n    const result = super.set(key, value);\n    this.#trig();\n    return result;\n  }\n\n  delete(key: K) {\n    const result = super.delete(key);\n    if (result) this.#trig();\n    return result;\n  }\n\n  clear() {\n    const result = super.clear();\n    this.#trig();\n    return result;\n  }\n\n  keys() {\n    this.#state;\n    return super.keys();\n  }\n  values() {\n    this.#state;\n    return super.values();\n  }\n  entries() {\n    this.#state;\n    return super.entries();\n  }\n  forEach(fn: (value: V, key: K, map: Map<K, V>) => void) {\n    this.#state;\n    return super.forEach(fn);\n  }\n\n  [Symbol.iterator]() {\n    this.#state;\n    return super[Symbol.iterator]();\n  }\n}\n\n\n\n---\nFile: /src/vite-env.d.ts\n---\n\n/// <reference types=\"vite/client\" />\n\nimport type { TracingConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  readonly SPA_ROUTER: {\n    logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n    tracing: TracingConfig;\n  };\n}\n\n\n\n---\nFile: /svelte.config.ts\n---\n\nimport { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n\tpreprocess: [vitePreprocess({})]\n};\n\n\n\n---\nFile: /vite.config.ts\n---\n\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\n\nimport { defineConfig } from \"vite\";\n\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nimport { sveltePreprocess } from \"svelte-preprocess\";\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte({\n      preprocess: [sveltePreprocess({ typescript: true })]\n    })\n  ]\n});\n\n\n\n---\nFile: /vitest.config.ts\n---\n\nimport { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      reporter: [\"json-summary\"],\n      reportsDirectory: \"tmp/coverage\"\n    }\n  }\n});\n\n\n\n---\nFile: /vitest.setup.ts\n---\n\nimport '@testing-library/jest-dom/vitest';\n\n"
  },
  {
    "path": "makefile",
    "content": ".PHONY: demo docs/llms.txt\n\ninstall:\n\tnpm install\n\tcd demo && npm install\n\ndemo:\n\tif [ ! -d \"demo/node_modules\" ]; then \\\n\tcd demo && npm install; \\\n\tfi\n\tcd demo && npm run dev\n\nclean:\n\trm -rf node_modules demo/node_modules\n\ndemo/build:\n\tcd demo && npm run build\n\n\ndocs/watch: ## Build the docs and watch for changes.\n\ttrap 'kill 0' SIGINT; \\\n\tnpm run docs:watch & \\\n\t(sleep 4 && npm run docs:serve) & \\\n\twait\n\ndocs/llms.txt:\n\tcat docs/*.md docs/llms.txt > llms.txt\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@mateothegreat/svelte5-router\",\n  \"version\": \"2.16.19\",\n  \"type\": \"module\",\n  \"moduleResolution\": \"bundler\",\n  \"sideEffects\": false,\n  \"license\": \"MIT\",\n  \"types\": \"./index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./index.d.ts\",\n      \"import\": \"./index.js\",\n      \"svelte\": \"./index.js\"\n    }\n  },\n  \"files\": [\n    \"./**/*\"\n  ],\n  \"readme\": \"README.md\",\n  \"description\": \"🫦 An SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.\",\n  \"homepage\": \"https://github.com/mateothegreat/svelte5-router\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mateothegreat/svelte5-router.git\"\n  },\n  \"author\": {\n    \"name\": \"Matthew Davis\",\n    \"email\": \"matthew@matthewdavis.io\",\n    \"url\": \"https://matthewdavis.io\"\n  },\n  \"keywords\": [\n    \"router\",\n    \"spa router\",\n    \"svelte\",\n    \"svelte router\",\n    \"svelte spa router\",\n    \"svelte 5\",\n    \"svelte 5 router\",\n    \"svelte 5 spa router\"\n  ],\n  \"scripts\": {\n    \"check\": \"svelte-check\",\n    \"build\": \"npm run check && rm -rf dist && npx svelte-package && cp docs/assets/logo.png dist && cp docs/readme.md dist/README.md\",\n    \"build:esm+cjs\": \"npm run check && rm -rf dist && npx vite build && cp package.json docs/assets/logo.png dist && cp docs/readme.md dist/README.md\",\n    \"docs:build\": \"typedoc --footerLastModified --customTitle \\\"svelte 5 router\\\" \",\n    \"docs:watch\": \"typedoc --watch --footerLastModified --customTitle \\\"svelte 5 router\\\" \",\n    \"docs:serve\": \"cd tmp/build && npx httpserver -p 20000\",\n    \"test:ci\": \"npx vitest run --coverage\",\n    \"test:watch\": \"npx vitest\"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"^2.29.6\",\n    \"@sveltejs/package\": \"^2.5.0\",\n    \"@sveltejs/vite-plugin-svelte\": \"^6.1.3\",\n    \"@svitejs/changesets-changelog-github-compact\": \"^1.2.0\",\n    \"@tsconfig/svelte\": \"^5.0.5\",\n    \"@types/node\": \"^24.3.0\",\n    \"@vitest/coverage-v8\": \"^3.2.4\",\n    \"prettier\": \"^3.6.2\",\n    \"prettier-plugin-svelte\": \"^3.4.0\",\n    \"svelte\": \"^5.38.6\",\n    \"svelte-check\": \"^4.3.1\",\n    \"svelte-preprocess\": \"^6.0.3\",\n    \"typescript\": \"^5.9.2\",\n    \"vite-plugin-dts\": \"^4.5.4\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"vitest\": \"^3.2.4\"\n  },\n  \"peerDependencies\": {\n    \"svelte\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/lib/actions/active.svelte.ts",
    "content": "import { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Add the `active` class to the node if the current route matches the node's href.\n *\n * > Similar to {@link route}.\n *\n * Add `use:active` to an anchor element to manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n *\n * @category Actions\n * @source\n */\nexport const active = (node: HTMLAnchorElement, options: Pick<RouteOptions, \"active\"> = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  window.addEventListener(\"pushState\", apply);\n\n  return {\n    destroy() {\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n"
  },
  {
    "path": "src/lib/actions/apply-classes.ts",
    "content": "import { urls, type URL } from \"../helpers/urls\";\n\nimport { RouteOptions } from \"./options\";\n\n/**\n * Applies the active class to the node if the href is the same as the current location.\n *\n * @param href - The href to check if it is the same as the current location.\n * @param options - The options to apply to the node.\n * @param node - The node to apply the active class to.\n *\n * @category Actions\n */\nexport const applyActiveClass = (href: URL, options: RouteOptions, node: HTMLAnchorElement) => {\n  const url = urls.parse(location.toString());\n  if (\n    (href.path === url.path ||\n      href.path === url.hash.path ||\n      href.hash.path === url.path ||\n      (!options.active?.absolute && url.path.startsWith(href.path))) &&\n    (options.active?.querystring || options.active?.querystring === undefined) &&\n    (href.query.original == \"\" ||\n      href.query.original === location.search.replace(\"?\", \"\") ||\n      href.query.original === url.hash.query.original)\n  ) {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.add(...options.active?.class);\n    } else {\n      node.classList.add(options.active?.class);\n    }\n    if (options.default?.class) {\n      node.classList.remove(...options.default?.class);\n    }\n  } else {\n    if (Array.isArray(options.active?.class)) {\n      node.classList.remove(...options.active?.class);\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    } else {\n      if (options.active?.class) {\n        node.classList.remove(options.active?.class);\n      }\n      if (options.default?.class) {\n        node.classList.add(...options.default?.class);\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "src/lib/actions/index.ts",
    "content": "export * from \"./apply-classes\";\nexport * from \"./options\";\nexport * from \"./route.svelte\";\n"
  },
  {
    "path": "src/lib/actions/options.ts",
    "content": "/**\n * Options that are applied to the html element when the route is active, inactive,\n * loading, or disabled.\n *\n * @category Router\n */\nexport type RouteOptionState = {\n  /**\n   * When true, the effects will only be applied if the path is an exact match.\n   *\n   * This is useful for when you want to apply the effects to a specific route, but\n   * not when it's part of a parent route.\n   */\n  absolute?: boolean;\n\n  /**\n   * When true, the effects will only be applied if the querystring is an exact match.\n   */\n  querystring?: boolean;\n\n  /**\n   * The css class(es) to add when this state is currently active.\n   */\n  class?: string | string[];\n};\n\n/**\n * Options for the route action.\n *\n * @category Router\n */\nexport class RouteOptions {\n  /**\n   * When the route is inactive, these options are applied.\n   */\n  default?: RouteOptionState;\n\n  /**\n   * When the route is active, these options are applied.\n   */\n  active?: RouteOptionState;\n\n  /**\n   * The css class(es) to add when route is loading.\n   */\n  loading?: RouteOptionState;\n\n  /**\n   * When the route is disabled, these options are applied.\n   */\n  disabled?: RouteOptionState;\n\n  constructor(options?: Partial<RouteOptions>) {\n    if (options) {\n      Object.assign(this, options);\n    }\n  }\n}\n"
  },
  {
    "path": "src/lib/actions/route.svelte.ts",
    "content": "import { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions } from \"./options\";\n\n/**\n * Svelte action to handle routing with optional active state.\n *\n * Similar to {@link active}\n *\n * Add `use:route` to an anchor element to handle routing and optionally manage active state.\n *\n * @param {HTMLAnchorElement} node The anchor element to handle.\n * @param {RouteOptions} options Options for the route action (optional).\n * @category Actions\n * @includeExample ../demo/src/app.svelte\n * @source\n */\nexport const route = (node: HTMLAnchorElement, options: RouteOptions = {}) => {\n  let url = urls.parse(node.href);\n\n  const apply = () => {\n    applyActiveClass(url, options, node);\n  };\n\n  /**\n   * Handle click events on the anchor element.\n   * @param event - The click event.\n   */\n  const handleClick = (event: Event) => {\n    event.preventDefault();\n    window.history.pushState({}, \"\", node.href);\n    applyActiveClass(url, options, node);\n  };\n\n  apply();\n\n  node.addEventListener(\"click\", handleClick);\n  window.addEventListener(\"pushState\", apply);\n  return {\n    destroy() {\n      node.removeEventListener(\"click\", handleClick);\n      window.removeEventListener(\"pushState\", apply);\n    }\n  };\n};\n"
  },
  {
    "path": "src/lib/hash.test.ts",
    "content": "// import { describe, expect, test } from \"vitest\";\n\n// import { hash } from \"./hash\";\n\n// describe(\"simple params\", () => {\n//   test(\"simple params\", () => {\n//     expect(hash.parse(\"/foo/bar?negative=-123&a=1&str=string&b=true\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: 1,\n//           str: \"string\",\n//           b: true,\n//           negative: -123\n//         }\n//       },\n//       hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\"\n//     });\n//   });\n// });\n\n// describe(\"crazy params\", () => {\n//   test(\"crazy params\", () => {\n//     expect(hash.parse(\"/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//       path: \"/foo/bar\",\n//       query: {\n//         params: {\n//           a: [\"first\", 3, false, 1.9, 9.99]\n//         }\n//       },\n//       hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//     });\n//   });\n// });\n"
  },
  {
    "path": "src/lib/hash.ts",
    "content": "import { Query } from \"./query.svelte\";\n\nexport type Hash = {\n  path: string;\n  query: Query;\n  hash: string;\n};\n\nexport namespace hash {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): Hash => {\n    if (url) {\n      const [_, afterHash = \"\"] = url.split(\"#\");\n      const [path, queryString = \"\"] = afterHash.split(\"?\");\n      return {\n        path,\n        query: new Query(queryString),\n        hash: afterHash\n      };\n    }\n  };\n}\n"
  },
  {
    "path": "src/lib/helpers/evaluators.test.ts",
    "content": "import { describe, expect, test } from \"vitest\";\n\nimport { evaluators } from \"./evaluators\";\nimport { Identities } from \"./identify\";\nimport { regexp } from \"./regexp\";\n\ndescribe(\"regexp\", () => {\n  test(\"should convert ^home$ to a RegExp\", () => {\n    expect(regexp.from(\"^home$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /^home$/ to a RegExp\", () => {\n    expect(regexp.from(\"/^home$/\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert a RegExp to a RegExp\", () => {\n    expect(regexp.from(/^home$/)).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert ^/($|home)$ to a RegExp\", () => {\n    expect(regexp.from(\"^/($|home)$\")).toBeInstanceOf(RegExp);\n  });\n\n  test(\"should convert /(^home$/ to a RegExp\", () => {\n    expect(() => regexp.from(\"/(^home$/\")).toThrowError();\n  });\n});\n\ndescribe(\"evaluators\", () => {\n  test(\"should return true for a non-empty object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 1 })).toBe(true);\n  });\n\n  test(\"should return false for an empty-ish object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: undefined, b: true })).toBe(false);\n  });\n\n  test(\"should return false for an empty-ish nested nested object\", () => {\n    expect(evaluators.valid[Identities.object]({ a: 0, b: { c: null } })).toBe(false);\n  });\n});\n"
  },
  {
    "path": "src/lib/helpers/evaluators.ts",
    "content": "import { identify, Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\nimport type { ReturnParam } from \"./urls\";\n\n/**\n * Path or querystring evaluation result.\n *\n * @category Router\n */\nexport type Condition =\n  | \"exact-match\"\n  | \"base-match\"\n  | \"default-match\"\n  | \"no-match\"\n  | \"permitted-no-conditions\"\n  | \"one-or-more-missing\";\n\n/**\n * The conditions that are considered successful.\n *\n * @category Router\n */\nexport const SuccessfulConditions: Condition[] = [\n  \"exact-match\",\n  \"base-match\",\n  \"default-match\",\n  \"permitted-no-conditions\"\n];\n\n/**\n * The conditions that are considered failed.\n *\n * @category Router\n */\nexport const FailedConditions: Condition[] = [\"no-match\", \"one-or-more-missing\"];\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type Evaluation = {\n  condition: Condition;\n  params?: ReturnParam;\n};\n\n/**\n * The evaluation results of the route.\n *\n * @category Router\n */\nexport type EvaluationResult = {\n  path: Evaluation;\n  querystring: Evaluation;\n  original: ReturnParam;\n};\n\nexport namespace evaluators {\n  /**\n   * Composite evaluator function that can handle different types of values.\n   *\n   * @param a - The first value to evaluate.\n   * @param b - The second value to evaluate {a} against.\n   * @returns A boolean, string[], or object.\n   */\n  export const any: Record<\n    string,\n    (\n      a: any,\n      b: any\n    ) =>\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | string\n      | string[]\n      | { [key: string]: boolean | boolean[] | number | number[] | string | string[] }\n  > = {\n    [Identities.string]: (a, b) => a === b,\n    [Identities.number]: (a, b) => a === b,\n    [Identities.boolean]: (a, b) => a === b,\n    [Identities.promise]: (a, b) => a === b,\n    [Identities.function]: (a, b) => a === b,\n    [Identities.null]: (a, b) => a === b,\n    [Identities.undefined]: (a, b) => a === b,\n    [Identities.unknown]: (a, b) => a === b,\n    [Identities.array]: (a, b) =>\n      Array.isArray(a) &&\n      Array.isArray(b) &&\n      a.length === b.length &&\n      a.every((value, index) => any[identify(value)](value, b[index])),\n    [Identities.object]: (a, b) => {\n      if (typeof a !== \"object\" || typeof b !== \"object\") {\n        return false;\n      }\n      const aKeys = Object.keys(a);\n      const bKeys = Object.keys(b);\n      if (aKeys.length !== bKeys.length) {\n        return false;\n      }\n      return aKeys.every((key) => any[identify(a[key])](a[key], b[key]));\n    },\n    [Identities.regexp]: (a, b) => {\n      const result = (a as RegExp).exec(b);\n      if (result) {\n        if (result.groups) {\n          return marshal(result.groups).value as { [key: string]: string };\n        } else {\n          if (result.length === 1 && result[0] === result.input) {\n            return true;\n          }\n          return marshal(result.slice(1)[0]).value as string[];\n        }\n      }\n      return false;\n    }\n  };\n\n  /**\n   * Evaluator function that checks if a value is empty recursively.\n   *\n   * @category Router\n   */\n  export const valid: Record<string, (a: any) => boolean> = {\n    [Identities.string]: (a) => a.length > 0,\n    [Identities.boolean]: (a) => a === false,\n    [Identities.number]: (a) => !isNaN(a),\n    [Identities.array]: (a) => Array.isArray(a) && a.length > 0,\n    [Identities.object]: (a) => {\n      if (typeof a !== \"object\" || a === null) {\n        return true;\n      }\n      const keys = Object.keys(a);\n      if (keys.length === 0) {\n        return true;\n      }\n      const result = keys.every((key) => {\n        const value = a[key];\n        const valueType = identify(value);\n        return valid[valueType](value);\n      });\n      return result;\n    },\n    [Identities.regexp]: (a) => a instanceof RegExp,\n    [Identities.function]: (a) => typeof a === \"function\",\n    [Identities.null]: () => false,\n    [Identities.undefined]: () => false\n  };\n}\n"
  },
  {
    "path": "src/lib/helpers/goto.ts",
    "content": "/**\n * Navigate to a new path by using the browser's history API (pushState specifically).\n *\n * @param path - The path to navigate to (excluding the base URL).\n * @param queryParams - The query parameters to add to the URL.\n *\n * @category Helpers\n */\nexport const goto = (path: string, queryParams?: Record<string, unknown>): void => {\n  const url = new URL(path, window.location.origin);\n  if (queryParams) {\n    Object.entries(queryParams).forEach(([key, value]) => {\n      url.searchParams.set(key, value as string);\n    });\n  }\n  window.history.pushState({}, \"\", url.toString());\n};\n"
  },
  {
    "path": "src/lib/helpers/identify.ts",
    "content": "export type Identity =\n  | string\n  | number\n  | boolean\n  | null\n  | undefined\n  | RegExp\n  | Function\n  | Object\n  | Array<unknown>\n  | Promise<unknown>;\n\nexport const Identities = {\n  string: \"string\",\n  number: \"number\",\n  boolean: \"boolean\",\n  null: \"null\",\n  undefined: \"undefined\",\n  regexp: \"regexp\",\n  function: \"function\",\n  object: \"object\",\n  array: \"array\",\n  promise: \"promise\",\n  unknown: \"unknown\"\n};\n\nexport const identify = (value: unknown): (typeof Identities)[keyof typeof Identities] => {\n  if (value === null) {\n    return Identities.null;\n  }\n  if (value === undefined) {\n    return Identities.undefined;\n  }\n  if (value instanceof RegExp) {\n    return Identities.regexp;\n  }\n  if (typeof value === \"string\") {\n    return Identities.string;\n  }\n  if (typeof value === \"number\") {\n    return Identities.number;\n  }\n  if (typeof value === \"boolean\") {\n    return Identities.boolean;\n  }\n  if (Array.isArray(value)) {\n    return Identities.array;\n  }\n  if (typeof value === \"function\") {\n    return Identities.function;\n  }\n  if (typeof value === \"object\") {\n    return Identities.object;\n  }\n  return Identities.unknown;\n};\n"
  },
  {
    "path": "src/lib/helpers/index.ts",
    "content": "export * from \"./evaluators\";\nexport * from \"./goto\";\nexport * from \"./identify\";\nexport * from \"./logging\";\nexport * from \"./marshal\";\nexport * from \"./normalize\";\nexport * from \"./objects\";\nexport * from \"./pop\";\nexport * from \"./query\";\nexport * from \"./regexp\";\nexport * from \"./replace\";\nexport * from \"./runtime\";\nexport * from \"./tracing.svelte\";\nexport * from \"./urls\";\n"
  },
  {
    "path": "src/lib/helpers/logging.ts",
    "content": "import { runtime } from \"./runtime\";\n\n/**\n * Logging facility.\n *\n * @category Helpers\n */\nexport namespace logging {\n  /**\n   * Acceptable log levels (applies to all logging methods).\n   */\n  export enum LogLevel {\n    FATAL = -1,\n    ERROR = 1,\n    INFO = 2,\n    DEBUG = 3,\n    TRACE = 4,\n    DISABLED = 5\n  }\n\n  /**\n   * A grouping of log messages.\n   */\n  export type Group = {\n    name: string;\n    messages: any | any[];\n  };\n\n  /**\n   * Acceptable log types typed out so that it's clearer what can be\n   * passed to the logging functions like groups of logs to combine them\n   * in the outputs.\n   */\n  export type Log = Group | Group[] | any | any[];\n\n  /**\n   * Convenience method for logging an info message.\n   */\n  export const info = (...msg: Log[]): void => {\n    log(LogLevel.INFO, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a debug message.\n   */\n  export const debug = (...msg: Log[]): void => {\n    log(LogLevel.DEBUG, ...msg);\n  };\n\n  /**\n   * Convenience method for logging an error message.\n   */\n  export const error = (...msg: any[]): void => {\n    log(LogLevel.ERROR, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a trace message.\n   */\n  export const trace = (...msg: any[]): void => {\n    log(LogLevel.TRACE, ...msg);\n  };\n\n  /**\n   * Convenience method for logging a fatal error and finally throwing an error.\n   *\n   * @remarks\n   * This is used to stop the application from running if an error is encountered\n   * that is not recoverable.\n   */\n  export const fatal = (...msg: any[]): void => {\n    log(LogLevel.FATAL, ...msg);\n    throw new Error(\"Fatal error\");\n  };\n\n  /**\n   * Raw log method.\n   */\n  export const log = (level: LogLevel, ...msg: Log[]): void => {\n    if (level <= runtime.current.logging.level && level !== LogLevel.DISABLED) {\n      if (runtime.current.logging.console) {\n        if (msg.some((m) => m?.toConsole)) {\n          msg.forEach((m) => m?.toConsole?.(runtime.current.logging.level));\n        } else if (runtime.current.logging.console) {\n          console.log(...msg);\n        }\n      }\n      if (runtime.current.logging.sink) {\n        runtime.current.logging.sink(msg);\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "src/lib/helpers/marshal.test.ts",
    "content": "import { describe, expect, test } from \"vitest\";\n\nimport { Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\n\ndescribe(\"marshal\", () => {\n  test(\"should marshal an array from a string\", () => {\n    expect(marshal(\"a[0]=1\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with multiple values\", () => {\n    expect(marshal(\"a[0]=first&nonarray=true,a[999]=true&a[1]=second&a[2]=3&a[3]=fourth\")).toEqual({\n      identity: Identities.object,\n      value: {\n        nonarray: true,\n        a: [\"first\", \"second\", 3, \"fourth\", true]\n      }\n    });\n  });\n\n  test(\"should marshal an array from a string with an empty value\", () => {\n    expect(marshal(\"a[0]=1&a[1]=b&a[2]=false&a[3]=true&a[4]=\")).toEqual({\n      identity: Identities.object,\n      value: {\n        a: [1, \"b\", false, true, \"\"]\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "src/lib/helpers/marshal.ts",
    "content": "import { Identities, type Identity } from \"./identify\";\n\nexport type MarshallableType = string | number | boolean | RegExp | Function | Promise<unknown>;\n\nexport type Marshalled<T> = {\n  identity: Identity;\n  value: T;\n};\n\n/**\n * Marshal a value to a specific type.\n *\n * @param value - The value to marshal\n *\n * @returns The marshaled value\n */\nexport const marshal = <T>(value: unknown): Marshalled<T> => {\n  // Most values will be strings, so we check for that first:\n  if (typeof value === \"string\") {\n    // Check for floats:\n    if (value.match(/^[\\d.-]+$/)) {\n      if (!Number.isNaN(Number.parseFloat(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseFloat(value) as T\n        };\n      }\n      // If the value is capable of being parsed as a number, we do that:\n      if (!Number.isNaN(Number.parseInt(value))) {\n        return {\n          identity: Identities.number,\n          value: Number.parseInt(value) as T\n        };\n      } else {\n        return {\n          identity: Identities.string,\n          value: value as T\n        };\n      }\n    } else if (!value.includes(\",\") && (value.includes(\"&\") || value.includes(\"=\"))) {\n      // Handle both array notation (x[0]=1) and simple key-value pairs (a=1&b=2)\n      const pairs = value.split(/[&,]/);\n      const result: Record<string, unknown> = {};\n\n      for (const pair of pairs) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        // Remove array notation if present, otherwise use the key as is\n        const cleanKey = key.replace(/\\[\\d*\\]$/, \"\");\n        const marshalled = marshal(val);\n\n        if (key.includes(\"[\")) {\n          // Handle array case\n          if (!Array.isArray(result[cleanKey])) {\n            result[cleanKey] = [];\n          }\n          const index = key.match(/\\[(\\d+)\\]/)?.[1];\n          if (index) {\n            // Store index and value as tuple to sort later\n            if (!Array.isArray(result[cleanKey])) {\n              result[cleanKey] = [];\n            }\n            (result[cleanKey] as [number, unknown][]).push([parseInt(index), marshalled.value]);\n          } else {\n            (result[cleanKey] as unknown[]).push(marshalled.value);\n          }\n        } else {\n          // Handle simple key-value case\n          result[cleanKey] = marshalled.value;\n        }\n      }\n\n      // Transform arrays while preserving non-array values\n      for (const [key, value] of Object.entries(result)) {\n        if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {\n          // Sort by index and extract values only for array entries\n          result[key] = (value as [number, unknown][]).sort((a, b) => a[0] - b[0]).map(([, val]) => val);\n        }\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.includes(\"&\") && value.includes(\"=\")) {\n      const result: Record<string, unknown> = {};\n\n      for (const pair of value.split(\"&\")) {\n        if (!pair.includes(\"=\")) continue;\n        const [key, val] = pair.split(\"=\");\n        result[key] = val;\n      }\n\n      return {\n        identity: Identities.object,\n        value: result as T\n      };\n    } else if (value.match(/^[0-9a-z]+\\[\\d+\\]=.+$/)) {\n      // Handle single array element case (e.g., \"x[0]=1\")\n      const [, index, val] = value.match(/^[0-9a-z]+\\[(\\d+)\\]=(.+)$/) || [];\n      if (index !== undefined && val !== undefined) {\n        const result = [];\n        const marshalled = marshal(val);\n        result[parseInt(index, 10)] = marshalled.value;\n        return {\n          identity: Identities.array,\n          value: result as T\n        };\n      }\n    } else if (value.match(/^[0-9a-z]+\\[\\]$/)) {\n      return {\n        identity: Identities.array,\n        value: value as T\n      };\n    }\n\n    // If the value is a string that is not a number, we check if it's a boolean:\n    if (value.match(/^true$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: true as T\n      };\n    }\n    if (value.match(/^false$/i)) {\n      return {\n        identity: Identities.boolean,\n        value: false as T\n      };\n    }\n\n    return {\n      identity: Identities.string,\n      value: value as T\n    };\n  } else if (typeof value === \"number\") {\n    return {\n      identity: Identities.number,\n      value: value as T\n    };\n  } else if (value instanceof RegExp) {\n    return {\n      identity: Identities.regexp,\n      value: value as T\n    };\n  } else if (typeof value === \"boolean\") {\n    return {\n      identity: Identities.boolean,\n      value: value as T\n    };\n  } else if (value === null) {\n    return {\n      identity: Identities.null,\n      value: null\n    };\n  } else if (value === undefined) {\n    return {\n      identity: Identities.undefined,\n      value: undefined\n    };\n  } else if (Array.isArray(value)) {\n    return {\n      identity: Identities.array,\n      value: value as T\n    };\n  } else if (typeof value === \"object\") {\n    const marshalled = Object.entries(value).reduce(\n      (acc, [key, val]) => {\n        acc[key] = marshal(val)?.value;\n        return acc;\n      },\n      {} as Record<string, unknown>\n    );\n    return {\n      identity: Identities.object,\n      value: marshalled as T\n    };\n  } else if (typeof value === \"function\") {\n    return {\n      identity: Identities.function,\n      value: value as T\n    };\n  } else if (value instanceof Promise) {\n    return {\n      identity: Identities.promise,\n      value: value as T\n    };\n  }\n\n  throw new Error(\n    `unable to marshal value: ${value} (it is neither a string, number, boolean, nor a regular expression)`\n  );\n};\n"
  },
  {
    "path": "src/lib/helpers/normalize.ts",
    "content": "/**\n * Normalize a path to ensure it starts with a slash.\n *\n * @param {string} path The path to normalize.\n *\n * @returns {string} The normalized path.\n *\n * @category Helpers\n */\nexport const normalize = (path: string): string => {\n  if (path && !path.startsWith(\"/\")) {\n    path = \"/\" + path;\n  }\n  return path;\n};\n"
  },
  {
    "path": "src/lib/helpers/objects.ts",
    "content": "/**\n * Convert a value to a primitive value.\n *\n * @param obj - The value to convert.\n *\n * @returns The primitive value.\n *\n * @category Helpers\n *\n */\nexport const toPrimitive = (obj: any): string | number | boolean | undefined | null | Array<any> => {\n  if (obj instanceof RegExp) {\n    return obj.toString();\n  }\n  if (obj instanceof Array) {\n    return obj.map((item) => toPrimitive(item));\n  }\n  if (typeof obj === \"object\") {\n    return Object.entries(obj).map(([key, value]) => {\n      return {\n        key,\n        value: toPrimitive(value)\n      };\n    });\n  }\n\n  // Return the value as is if it's not an object, array, or RegExp.\n  // TODO: Maybe we should throw an error here? (future mateo problem..)\n  return obj;\n};\n"
  },
  {
    "path": "src/lib/helpers/pop.ts",
    "content": "/**\n * Navigate backwards in the browser history N pages.\n *\n * @param delta - The number of pages to navigate backwards (default: 1)\n *\n * @example\n *\n * ```ts\n * import { pop } from \"@mateothegreat/svelte5-router\";\n * pop(); // Navigate back 1 page\n * pop(2); // Navigate back 2 pages\n * ```\n *\n * @category Helpers\n */\nexport const pop = (delta?: number) => {\n  window.history.go(delta ? delta * -1 : -1);\n};\n"
  },
  {
    "path": "src/lib/helpers/query.ts",
    "content": "/**\n * Get a query parameter from the current URL.\n *\n * @param key - The key of the query parameter to get\n *\n * @returns The value of the query parameter, or null if it doesn't exist\n *\n * @category Helpers\n */\nexport const query = (key: string): string | null => {\n  return new URLSearchParams(window.location.search).get(key);\n};\n"
  },
  {
    "path": "src/lib/helpers/regexp.ts",
    "content": "/**\n * Regular expression utilities.\n *\n * @module regexp\n * @category Helpers\n */\nexport namespace regexp {\n  /**\n   * Safely handle a capable value as a RegExp.\n   *\n   * If the value is a string, it will be converted to a RegExp.\n   * If the value is already a RegExp, it will be returned as is.\n   * Otherwise, an error will be thrown.\n   *\n   * @throws {Error} If the value is not a string or RegExp.\n   */\n  export const from = (v: string | RegExp): RegExp => {\n    if (typeof v === \"string\") {\n      return new RegExp(v);\n    } else if (v instanceof RegExp) {\n      return new RegExp(v.source);\n    }\n    throw new Error(\"invalid regexp expression\");\n  };\n\n  /**\n   * Check if a string contains regex syntax.\n   *\n   * @param v The string to check.\n   * @returns True if the string contains regex syntax, false otherwise.\n   */\n  export const can = (v: string): boolean => {\n    // Check for:\n    // - Special characters: [] {} () * + ? . \\ ^ $ |\n    // - Character classes: \\w \\d \\s and their negations\n    // - Anchors: ^ $\n    // - Quantifiers: + * ? {}\n    // - Groups: (? (?: (?= (?! (?<= (?<!\n    return /[[\\]{}()*+?.,\\\\^$|#\\s]|\\\\[wWdDsS]|\\(\\?[:!=<]?/.test(v);\n  };\n}\n"
  },
  {
    "path": "src/lib/helpers/replace.ts",
    "content": "/**\n * Navigate to a new path by replacing the current browser history entry\n * instead of adding a new one (history.replaceState).\n *\n * @param path - The path to navigate to (excluding the base URL).\n * @param queryParams - Optional query parameters to append.\n *\n * @category Helpers\n */\nexport const replace = (path: string, queryParams?: Record<string, unknown>): void => {\n  const url = new URL(path, window.location.origin);\n  if (queryParams) {\n    Object.entries(queryParams).forEach(([key, value]) => {\n      url.searchParams.set(key, value as string);\n    });\n  }\n  window.history.replaceState({}, \"\", url.toString());\n};\n"
  },
  {
    "path": "src/lib/helpers/runtime.ts",
    "content": "import { logging } from \"./logging\";\n\n/**\n * Runtime level configuration functionality.\n *\n * @category Helpers\n */\nexport namespace runtime {\n  // Helper to safely read from \"import\\.meta\\.env\" or \"process\\.env\"\n  const getEnvVar = <T = unknown>(path: string[], fallback?: T): T | undefined => {\n    try {\n      // Try Vite-style env first\n      let env: any = (import.meta as any)?.env;\n      for (const key of path) {\n        if (env && key in env) env = env[key];\n        else {\n          env = undefined;\n          break;\n        }\n      }\n      if (env !== undefined) return env as T;\n    } catch {\n      /* ignore */\n    }\n\n    try {\n      // Try process.env for Electron main/preload\n      let env: any = process?.env;\n      for (const key of path) {\n        if (env && key in env) env = env[key];\n        else {\n          env = undefined;\n          break;\n        }\n      }\n      if (env !== undefined) return env as T;\n    } catch {\n      /* ignore */\n    }\n\n    return fallback;\n  };\n\n  /**\n   * Runtime configuration.\n   */\n  export type Config = {\n    tracing: {\n      enabled: boolean;\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n    logging: {\n      level?: logging.LogLevel;\n      console?: boolean;\n      sink?: (...msg: logging.Log[]) => void | Promise<void>;\n    };\n  };\n\n  /**\n   * Retrieve the runtime configuration.\n   *\n   * This can be sourced from environment variables or passed in as an argument.\n   */\n  export const config = (config?: Config): Config => {\n    return {\n      tracing: config?.tracing ?? getEnvVar([\"SPA_ROUTER\", \"tracing\"], { enabled: false }),\n      logging: {\n        level: config?.logging?.level ?? getEnvVar([\"SPA_ROUTER\", \"logging\", \"level\"], 4),\n        console: config?.logging?.console ?? getEnvVar([\"SPA_ROUTER\", \"logging\", \"console\"]),\n        sink: config?.logging?.sink ?? getEnvVar([\"SPA_ROUTER\", \"logging\", \"sink\"])\n      }\n    };\n  };\n\n  /**\n   * The current runtime configuration.\n   *\n   * When first called, it will retrieve the runtime configuration from the environment variables.\n   * After that, it can be mutated and will not be retrieved from the environment variables again.\n   */\n  export let current: Config = config();\n}\n"
  },
  {
    "path": "src/lib/helpers/tracing.svelte.ts",
    "content": "import { ReactiveMap } from \"../utilities.svelte\";\n\nimport { logging } from \"./logging\";\nimport { runtime } from \"./runtime\";\n\n/**\n * A span is a single trace in a trace collection.\n *\n * @category Helpers\n */\nexport class Span {\n  prefix?: string;\n  id?: string;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  traces?: ReactiveMap<string, Trace> = $state(new ReactiveMap());\n\n  constructor(span: Span, prefix?: string) {\n    this.prefix = prefix;\n    this.name = span.name;\n    this.id = span.id || Math.random().toString(36).substring(2, 25);\n    this.description = span.description;\n    this.metadata = span.metadata;\n    this.date = span.date || new Date();\n  }\n\n  trace?(trace: Trace, prefix?: string): Trace {\n    const id = trace.id || Math.random().toString(36).substring(2, 25);\n    trace = new Trace(trace, this.traces.size + 1, this, prefix);\n    this.traces.set(id, trace);\n\n    logging.trace(prefix, trace);\n\n    return trace;\n  }\n\n  get?(): MapIterator<Trace> {\n    return this.traces.values();\n  }\n}\n\n/**\n * A trace is a collection of spans.\n *\n * @category Helpers\n */\nexport class Trace {\n  prefix?: string;\n  id?: string;\n  index?: number;\n  date?: Date;\n  name?: string;\n  description?: string;\n  metadata?: Record<string, any>;\n  span?: Span;\n\n  constructor(trace: Trace, index?: number, span?: Span, prefix?: string) {\n    this.id = trace.id || Math.random().toString(36).substring(2, 25);\n    this.index = index;\n    this.date = trace.date || new Date();\n    this.name = trace.name;\n    this.description = trace.description;\n    this.metadata = trace.metadata;\n    this.span = span;\n    this.prefix = trace.prefix;\n  }\n\n  /**\n   * Wrapper method for logging a trace to the browser console.\n   *\n   * @category Helpers\n   */\n  toConsole?(level?: logging.LogLevel): void {\n    const out = [\n      \"%c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s\",\n      \"color: #505050\",\n      this.date?.toISOString(),\n      \"color: #7A7A7A\",\n      \"color: #915CF2; font-weight: bold\",\n      this.span?.name || this.id,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #C3F53B; font-weight: bold\",\n      this.index,\n      \"color: #7A7A7A; font-weight: bold\",\n      \"color: #3BAEF5; font-weight: bold\",\n      `${this.metadata?.router ? `[${this.metadata.router.id}] ` : \"\"}${this.name}`,\n      \"color: #06E96C\",\n      this.description\n    ];\n\n    if (this.prefix) {\n      out[0] = `${this.prefix} %c%s %cspan:%c%s:%ctrace:%c%s%c:%c%s %c%s`;\n    }\n\n    if (runtime.current.tracing.level >= logging.LogLevel.TRACE) {\n      out[0] += \"\\n%c%s\";\n      out.push(\n        \"color: #6B757F\",\n        `attached trace metadata:\\n\\n${JSON.stringify(\n          {\n            span: this.span.metadata,\n            trace: this.metadata\n          },\n          null,\n          2\n        )}`\n      );\n    } else if (runtime.current.tracing.level >= logging.LogLevel.DEBUG) {\n      if (this.span) {\n        // @ts-ignore\n        out.push(this.span.metadata);\n      }\n      if (this.metadata) {\n        // @ts-ignore\n        out.push(this.metadata);\n      }\n    }\n\n    console.log(...out);\n  }\n}\n\n/**\n * A reactive map of spans.\n *\n * @category Helpers\n */\nexport const spans = new ReactiveMap<string, Span>();\n\n/**\n * Helper method for creating a new span.\n *\n * @category Helpers\n */\nexport const createSpan = (name: string, metadata?: Record<string, any>) => {\n  if (runtime.current.tracing) {\n    const span = new Span({ name, metadata });\n    spans.set(name, span);\n    return span;\n  }\n};\n"
  },
  {
    "path": "src/lib/helpers/urls.test.ts",
    "content": "import { expect, test } from \"vitest\";\n\nimport { urls } from \"./urls\";\n\ntest.only(\"parses with no query parameters\", () => {\n  expect(urls.parse(\"http://localhost:5173/#/foo/bar\")).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/#/foo/bar\",\n    hash: {\n      hash: \"/foo/bar\",\n      path: \"/foo/bar\",\n      query: {\n        original: \"\",\n        params: {}\n      },\n    },\n    query: {\n      original: \"\",\n      params: {}\n    }\n  });\n});\n\ntest.only(\"parses key-value query parameters\", () => {\n  let result = urls.parse(\"http://localhost:5173/#/foo/bar?negative=-123&a=1&str=string&b=true\");\n  expect(result).toEqual({\n    protocol: \"http\",\n    host: \"localhost\",\n    port: \"5173\",\n    path: \"/#/foo/bar\",\n    query: {\n      params: {\n        negative: -123,\n        a: 1,\n        str: \"string\",\n        b: true,\n      },\n      original: \"negative=-123&a=1&str=string&b=true\",\n    },\n    hash: {\n      path: \"/foo/bar\",\n      query: {\n        params: {\n          negative: -123,\n          a: 1,\n          str: \"string\",\n          b: true,\n        },\n        original: \"negative=-123&a=1&str=string&b=true\",\n      },\n      hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\",\n    },\n  });\n});\n\ntest.only(\"file url without query parameters\", () => {\n  let result = urls.parse(\"file:///C:/Users/user1/projects/app1/index.html#/foo/bar\");\n  expect(result).toEqual({\n    protocol: \"file\",\n    host: \"/C:/Users/user1/projects/app1/index.html\",\n    port: \"\",\n    path: \"/#/foo/bar\",\n    query: {\n      params: {\n      },\n      original: undefined,\n    },\n    hash: {\n      path: \"/foo/bar\",\n      query: {\n        params: {\n        },\n        original: \"\",\n      },\n      hash: \"/foo/bar\",\n    },\n  });\n});\n\ntest.only(\"file url with key-value query parameters\", () => {\n  let result = urls.parse(\"file:///C:/Users/user1/projects/app1/index.html#/foo/bar?negative=-123&a=1&str=string&b=true\");\n  expect(result).toEqual({\n    protocol: \"file\",\n    host: \"/C:/Users/user1/projects/app1/index.html\",\n    port: \"\",\n    path: \"/#/foo/bar?negative=-123&a=1&str=string&b=true\",\n    query: {\n      params: {\n        negative: -123,\n        a: 1,\n        str: \"string\",\n        b: true,\n      },\n      original: \"negative=-123&a=1&str=string&b=true\",\n    },\n    hash: {\n      path: \"/foo/bar\",\n      query: {\n        params: {\n          negative: -123,\n          a: 1,\n          str: \"string\",\n          b: true,\n        },\n        original: \"negative=-123&a=1&str=string&b=true\",\n      },\n      hash: \"/foo/bar?negative=-123&a=1&str=string&b=true\",\n    },\n  });\n});\n\n// test.only(\"parses array parameter parsing\", () => {\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\")).toEqual({\n//     protocol: \"http\",\n//     host: \"localhost\",\n//     port: \"5173\",\n//     path: \"/foo/bar\",\n//     query: {\n//       params: {\n//         a: [\"first\", 3, false, 1.9, 9.99]\n//       }\n//     },\n//     hash: \"/foo/bar?a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\"\n//   });\n// });\n\n// test.only(\"query is undefined\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar\").query.toString()).toEqual(\"\");\n// });\n\n// test.only(\"query.toString() matches location.search\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?a=1&b=2\").query.toString()).toEqual(\"a=1&b=2\");\n// });\n\n// test.only(\"query.toString() matches multiples (pagination=2,23&company=123)\", () => {\n//   expect(urls.parse(\"http://localhost:5173/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n//   expect(urls.parse(\"http://localhost:5173/#/foo/bar?pagination=2,23&company=123\").query.toString()).toEqual(\n//     \"pagination=2,23&company=123\"\n//   );\n// });\n"
  },
  {
    "path": "src/lib/helpers/urls.ts",
    "content": "import { hash, type Hash } from \"../hash\";\nimport { Query } from \"../query.svelte\";\n\nimport { normalize } from \"./normalize\";\n\n/**\n * The returned param value types for paths.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type Param = string | number | boolean;\n\n/**\n * The returned param value types.\n *\n * @remarks\n * Multiple types are supported to allow for flexibility in the\n * types of params such as when an evaluation uses regex with match grouping.\n *\n * Every param value is a string, array of string, or a record\n * of string keys and values.\n *\n * Params are extracted and converted to the appropriate type\n * later in the route lifecycle\n *\n * @category Router\n */\nexport type ReturnParam =\n  | RegExp\n  | boolean\n  | boolean[]\n  | number\n  | number[]\n  | string\n  | string[]\n  | Record<string, string | number | boolean | string[] | number[] | boolean[]>;\n\nexport type URL = {\n  protocol: string;\n  host: string;\n  port: string;\n  path: string;\n  query: Query;\n  hash: Hash;\n};\n\nexport namespace urls {\n  /**\n   * Parse a URL string into its components\n   * @param url The URL to parse\n   * @returns Object containing path, query params, and hash components\n   */\n  export const parse = (url: string): URL => {\n    if (url === undefined || url.length === 0) {\n      throw new Error(`invalid URL: ${url}`);\n    }\n    const isFileUrl = url.startsWith(\"file:///\");\n    const isAbsoluteUrl = url.includes(\"://\") && !isFileUrl;\n    if (isAbsoluteUrl) {\n      const [protocol, remaining] = url.split(\"://\");\n      const hostPortMatch = remaining.match(/^([^/:]+)(?::(\\d+))?(.*)$/);\n      const [host, port, path] = hostPortMatch?.slice(1) ?? [];\n\n      const [before, queryString = \"\"] = (path || \"\").split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol,\n        host,\n        port,\n        path: normalize(before) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    } else if (isFileUrl) {\n      const [protocol, remaining] = url.split(\"://\");\n      const posHash = remaining.indexOf(\"#\");\n      const posFirstQuestionmark = remaining.indexOf(\"?\");\n      let host = \"\";\n      let path = \"/\";\n      let query : Query;\n      if (posHash > posFirstQuestionmark && posFirstQuestionmark != -1) {\n        host = remaining.slice(0, posFirstQuestionmark);\n        path = normalize(remaining.slice(posFirstQuestionmark));\n        query = new Query(path);\n      } else {\n        host = remaining.slice(0, posHash);\n        path = normalize(remaining.slice(posHash));\n        const [_, queryString] =  (path || \"\").split(\"?\");\n        query = new Query(queryString);\n      }\n      const hashed = hash.parse(remaining);\n\n      return {\n        protocol,\n        host,\n        port: \"\",\n        path,\n        query,\n        hash: hashed\n      };\n    } else {\n      // Handle relative URLs\n      const [pathPart, queryString = \"\"] = url.split(\"?\");\n      const hashed = hash.parse(url);\n\n      return {\n        protocol: window.location.protocol.replace(\":\", \"\"),\n        host: window.location.hostname,\n        port: window.location.port,\n        path: normalize(pathPart) || \"/\",\n        query: new Query(queryString),\n        hash: hashed\n      };\n    }\n  };\n\n  export const path = (path: string): string => {\n    return path.split(\"?\")[0];\n  };\n}\n"
  },
  {
    "path": "src/lib/hooks.ts",
    "content": "import type { RouteResult } from \"./route.svelte\";\n\n/**\n * Hooks are functions that can be used to modify the behavior of routing\n * when a route is navigated to (before and/or after).\n *\n * The return value of the hook is a boolean that determines if the route should\n * be navigated to. If the hook returns `false` (or nothing), navigation will be cancelled.\n *\n * @category hooks\n */\nexport type HookReturn = void | boolean | Promise<boolean> | Promise<void>;\nexport type Hook = (route: RouteResult) => HookReturn;\n"
  },
  {
    "path": "src/lib/index.ts",
    "content": "export * from \"./actions\";\nexport { active } from \"./actions/active.svelte\";\nexport { RouteOptions } from \"./actions/options\";\nexport { route } from \"./actions/route.svelte\";\nexport * from \"./helpers\";\nexport type { Condition, Evaluation, EvaluationResult } from \"./helpers/evaluators\";\nexport { goto } from \"./helpers/goto\";\nexport { identify, type Identities, type Identity } from \"./helpers/identify\";\nexport { logging } from \"./helpers/logging\";\nexport { marshal, type Marshalled } from \"./helpers/marshal\";\nexport { normalize } from \"./helpers/normalize\";\nexport { query } from \"./helpers/query\";\nexport { runtime } from \"./helpers/runtime\";\nexport { Span, Trace } from \"./helpers/tracing.svelte\";\nexport type { ReturnParam as Param } from \"./helpers/urls\";\nexport type { Hook } from \"./hooks\";\nexport { Query } from \"./query.svelte\";\nexport type { QueryEvaluationResult, QueryType } from \"./query.svelte\";\nexport { registry, type Registry } from \"./registry.svelte\";\nexport type { ApplyFn, Route, RouteConfig, RouteResult } from \"./route.svelte\";\nexport { RouterInstanceConfig } from \"./router-instance-config\";\nexport { RouterInstance } from \"./router-instance.svelte\";\nexport { default as Router } from \"./router.svelte\";\nexport { StatusCode, getStatusByValue, type Statuses } from \"./statuses\";\n"
  },
  {
    "path": "src/lib/path.ts",
    "content": "/**\n * @remarks\n * Future home of more path related functionality.\n */\nimport { Query } from \"./query.svelte\";\n\n/**\n * The types of values that can be used as a path.\n *\n * @category Router\n */\nexport type PathType = string | number | RegExp | Function | Promise<unknown>;\n\nexport class Path {\n  protocol: string;\n  host: string;\n  path: string;\n  query: Query;\n\n  constructor() {\n    this.protocol = location.protocol;\n    this.host = location.host;\n    this.path = location.pathname;\n    if (location.search.length > 0) {\n      this.query = new Query(Object.fromEntries(new URLSearchParams(location.search)));\n    }\n  }\n\n  toURL(): string {\n    return `${this.protocol}://${this.host}${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n\n  toURI(): string {\n    return `${this.path}${this.query ? this.query.toString() : \"\"}`;\n  }\n}\n\nexport namespace paths {\n  export const base = (base: string, path: string): boolean => {\n    return path.match(new RegExp(`^${base}(/|$)`)) !== null;\n  };\n}\n"
  },
  {
    "path": "src/lib/query.svelte.ts",
    "content": "import { evaluators, type Condition } from \"./helpers/evaluators\";\nimport { goto } from \"./helpers/goto\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport type { ReturnParam } from \"./helpers/urls\";\n\n/**\n * The types of values that can be used as a query.\n *\n * @category Router\n */\nexport type QueryType<T = unknown> = Record<string, string | number | RegExp | Function | Promise<T>>;\n\n/**\n * The types of values that the {Query} test method can return.\n *\n * @category Router\n */\nexport type QueryEvaluationResult = {\n  condition: Condition;\n  matches?: Record<string, ReturnParam>;\n};\n\n/**\n * Query string operations.\n *\n * @category Helpers\n */\nexport class Query {\n  params: Record<string, ReturnParam> = {};\n  original?: string;\n\n  constructor(query?: Record<string, string> | string | Query | Record<string, ReturnParam>) {\n    if (typeof query === \"string\") {\n      this.original = query;\n    }\n\n    if (query) {\n      const marshalled = marshal(query);\n      if (marshalled.value) {\n        this.params = marshalled.value as Record<string, ReturnParam>;\n      }\n    }\n  }\n\n  /**\n   * Get a value from the query string parameters and optionally provide\n   * a default value if the key is not found.\n   *\n   * @param key - The key to get the value from.\n   * @param defaultValue - The default value to return if the key is not found.\n   */\n  get<T>(key: string, defaultValue?: T): T {\n    return (this.params[key] as T) || defaultValue;\n  }\n\n  /**\n   * Set a value in the query string parameters.\n   */\n  set(key: string, value: string) {}\n\n  /**\n   * Delete a value from the query string parameters.\n   */\n  delete(key: string) {\n    delete this.params[key];\n  }\n\n  /**\n   * Clear the query string parameters.\n   */\n  clear() {\n    this.params = {};\n  }\n\n  goto(path: string) {\n    goto(path, this.params);\n  }\n\n  test(inbound: Query): QueryEvaluationResult {\n    if (typeof inbound === \"object\") {\n      const matches: Record<string, ReturnParam> = {};\n      for (const [key, test] of Object.entries(inbound.params)) {\n        if (this.params[key]) {\n          const marshalled = marshal(this.params[key]);\n          if (test instanceof RegExp) {\n            const res = evaluators.any[Identities.regexp](test, this.params[key]);\n            if (res) {\n              matches[key] = res;\n            } else {\n              return {\n                condition: \"no-match\"\n              };\n            }\n          } else if (marshalled.identity === Identities.number) {\n            if (marshalled.value === this.params[key]) {\n              matches[key] = marshalled.value as number;\n            }\n          } else if (marshalled.identity === Identities.string) {\n            matches[key] = marshalled.value === this.params[key];\n          } else if (marshalled.identity === Identities.boolean) {\n            matches[key] = marshalled.value === Boolean(this.params[key]);\n          } else if (marshalled.identity === Identities.array) {\n            matches[key] = (marshalled.value as Array<unknown>).includes(this.params[key]);\n          }\n        } else {\n          return {\n            condition: \"no-match\"\n          };\n        }\n      }\n\n      if (Object.keys(matches).length === Object.keys(inbound).length && evaluators.valid[Identities.object](matches)) {\n        return {\n          condition: \"exact-match\",\n          matches: marshal(matches).value as Record<string, ReturnParam>\n        };\n      }\n\n      return {\n        condition:\n          Object.keys(matches).length > 1 && Object.keys(inbound).length !== Object.keys(matches).length\n            ? \"exact-match\"\n            : \"no-match\",\n        matches: matches as Record<string, ReturnParam>\n      };\n    }\n  }\n\n  /**\n   * Convert the query string parameters to a string.\n   */\n  toString() {\n    const stringifyValue = (value: any): string => {\n      if (Array.isArray(value)) {\n        return value.map((v) => stringifyValue(v)).join(\",\");\n      }\n      if (typeof value === \"object\" && value !== null) {\n        return Object.entries(value)\n          .map(([k, v]) => `${k}:${stringifyValue(v)}`)\n          .join(\",\");\n      }\n      // console.log(\"stringifyValue\", value, typeof value);\n      return encodeURIComponent(value);\n    };\n\n    return Object.entries(this.params)\n      .map(([key, value]) => `${encodeURIComponent(key)}=${stringifyValue(value)}`)\n      .join(\"&\");\n    // return preserveOriginal ? this._original : \"\";\n  }\n\n  /**\n   * Convert the query string parameters to a JSON object given\n   * we may have parameter values that are not json serializable\n   * out of the box.\n   */\n  toJSON(preserveOriginal?: boolean) {\n    return Object.fromEntries(Object.entries(this.params).map(([key, value]) => [key, value.toString()]));\n  }\n}\n"
  },
  {
    "path": "src/lib/query.test.ts",
    "content": "import { describe, expect, test } from \"vitest\";\n\nimport { Query } from \"./query.svelte\";\n\ndescribe(\"query\", () => {\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"b=2&a=false&c=str\").params).toEqual({ a: false, b: 2, c: \"str\" });\n  });\n\n  test(\"should create a query object from a string\", () => {\n    expect(new Query(\"nonarray=1&a[3]=3&a[19]=1.9&a[0]=first&a[99]=9.99&a[5]=false\").params).toEqual({\n      nonarray: 1,\n      a: [\"first\", 3, false, 1.9, 9.99]\n    });\n  });\n});\n"
  },
  {
    "path": "src/lib/registry.svelte.ts",
    "content": "import type { ApplyFn } from \"./route.svelte\";\nimport { RouterInstanceConfig } from \"./router-instance-config\";\nimport { RouterInstance } from \"./router-instance.svelte\";\nimport { ReactiveMap } from \"./utilities.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * Handles the dynamic registration and deregistration of router instances.\n *\n * @remarks\n * This is a singleton and should not be instantiated directly.\n *\n * @category Registry\n */\nexport class Registry {\n  /**\n   * Container for router instances.\n   */\n  instances = new ReactiveMap<string, RouterInstance>();\n\n  constructor() {\n    // Prevent multiple instantiation during HMR by storing instance in window\n    if ((window as any).__SVELTE_SPA_ROUTER_REGISTERED__) {\n      return (window as any).__SVELTE_SPA_ROUTER_REGISTERED__;\n    }\n    (window as any).__SVELTE_SPA_ROUTER_REGISTERED__ = this;\n\n    const { pushState, replaceState } = window.history;\n\n    window.history.pushState = function (...args) {\n      pushState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"pushState\"));\n    };\n\n    window.history.replaceState = function (...args) {\n      replaceState.apply(window.history, args);\n      window.dispatchEvent(new Event(\"replaceState\"));\n    };\n  }\n\n  /**\n   * Register a new router instance.\n   *\n   * @param {Instance} config The instance to register.\n   * @param {ApplyFn} applyFn The function to call for applying route changes.\n   *\n   * @see {@link deregister}: The opposite of this method.\n   */\n  register(config: RouterInstanceConfig, applyFn: ApplyFn, span?: Span): RouterInstance {\n    if (this.instances.has(config.id)) {\n      throw new Error(`router instance with id ${config.id} already registered`);\n    }\n\n    const instance = new RouterInstance(config, applyFn);\n\n    if (span) {\n      span.trace({\n        prefix: \"🔍\",\n        name: \"registry.register\",\n        description: \"registering a new router instance\",\n        metadata: {\n          router: {\n            id: config.id,\n            basePath: config.basePath\n          },\n          location: \"/src/lib/registry.svelte:register()\",\n          config\n        }\n      });\n    }\n\n    this.instances.set(config.id, instance);\n\n    return instance;\n  }\n\n  /**\n   * Deregister a router instance.\n   *\n   * @param {string} id The id of the instance to deregister.\n   */\n  deregister(id: string, span?: Span): void {\n    const instance = this.instances.get(id);\n    if (span) {\n      span.trace({\n        prefix: instance ? \"✅\" : \"❌\",\n        name: \"registry.deregister\",\n        description: \"deregistering a router instance\",\n        metadata: {\n          router: {\n            id,\n            basePath: instance.config.basePath\n          },\n          location: \"/src/lib/registry.svelte:deregister()\",\n          config: instance.config\n        }\n      });\n    }\n\n    if (instance) {\n      this.instances.delete(id);\n    } else {\n      throw new Error(`router instance with id ${id} not found`);\n    }\n  }\n\n  get(id: string): RouterInstance {\n    const instance = this.instances.get(id);\n\n    return instance;\n  }\n}\n\n/**\n * Expose a reference to the registry of router instances.\n *\n * This is used to register & unregister router instances and to get\n * the route for a given path.\n *\n * This is a singleton and should not be instantiated directly and should\n * never be accessed outside of the scope of this package in most cases.\n *\n * @category Registry\n */\nexport const registry = (window as any).__SVELTE_SPA_ROUTER_REGISTRY__ || new Registry();\n"
  },
  {
    "path": "src/lib/route.svelte.ts",
    "content": "/**\n * This is the doc comment for file1.ts\n *\n * @packageDocumentation\n */\nimport type { Component, Snippet } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { paths, type PathType } from \"./path\";\nimport { Query } from \"./query.svelte\";\n\nimport { evaluators, type Condition, type Evaluation } from \"./helpers/evaluators\";\nimport { Identities } from \"./helpers/identify\";\nimport { marshal } from \"./helpers/marshal\";\nimport { normalize } from \"./helpers/normalize\";\nimport { regexp } from \"./helpers/regexp\";\nimport type { Span, Trace } from \"./helpers/tracing.svelte\";\nimport { urls, type ReturnParam } from \"./helpers/urls\";\n\n/**\n * A route result that includes the evaluation results of the route.\n *\n * This type is necessary for the internal workings of the router to ensure that\n * the evaluation results are included in the route result and to avoid requiring\n * it to be merged in the original route instance.\n *\n * @since 2.0.0\n * @category Router\n * @example\n * ```ts\n * const routeResult = new RouteResult({\n *   router: myRouter,\n *   route: myRoute,\n *   result: {\n *     path: {\n *       condition: \"exact-match\",\n *       original: \"/users/123\",\n *       params: { id: \"123\" }\n *     },\n *     querystring: {\n *       condition: \"exact-match\",\n *       original: { filter: \"active\" },\n *       params: { filter: \"active\" }\n *     },\n *     component: UserComponent,\n *     status: 200\n *   }\n * });\n * ```\n */\nexport class RouteResult {\n  /**\n   * The route that was evaluated to render this result.\n   *\n   * @since 2.0.0\n   * @remarks This may be undefined if the route result was created without an associated route.\n   * @see {@link Route}\n   */\n  route?: Route;\n\n  /**\n   * The comprehensive result of routing evaluation that rendered this route.\n   *\n   * This object contains all the information gathered during the route matching process,\n   * including path evaluation, querystring parsing, component resolution, and status determination.\n   *\n   * @since 2.0.0\n   * @remarks The result object is immutable once created and represents a snapshot of the routing state.\n   *\n   * @example\n   * ```ts\n   * // Accessing route evaluation results\n   * console.log(routeResult.result.path.original); // \"/users/123\"\n   * console.log(routeResult.result.path.params?.id); // \"123\"\n   * console.log(routeResult.result.status); // 200\n   * ```\n   */\n  result: {\n    /**\n     * The path evaluation results containing the matched path information.\n     *\n     * This object provides detailed information about how the current URL path\n     * was matched against the route's path pattern, including any extracted parameters.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For a route with path \"/users/:id\" and URL \"/users/123\"\n     * const pathResult = {\n     *   condition: \"exact-match\",\n     *   original: \"/users/123\",\n     *   params: { id: \"123\" }\n     * };\n     * ```\n     */\n    path: {\n      /**\n       * The evaluation condition indicating how the path was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"base-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original path string that was evaluated during routing.\n       *\n       * This represents the actual path portion of the URL that was processed,\n       * without query parameters or hash fragments.\n       *\n       * @since 2.0.0\n       * @example \"/users/123/profile\"\n       * @remarks This path is normalized and may differ from the raw URL path\n       */\n      original: string;\n\n      /**\n       * The parameters extracted from the path during evaluation.\n       *\n       * Contains named parameters extracted from the path pattern matching,\n       * such as route parameters defined with `:paramName` syntax or regex groups.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @example\n       * ```ts\n       * // For route \"/users/:id\" matching \"/users/123\"\n       * params: { id: \"123\" }\n       *\n       * // For regex route \"^/posts/(?<slug>.+)$\" matching \"/posts/my-article\"\n       * params: { slug: \"my-article\" }\n       * ```\n       * @see {@link ReturnParam} For parameter value types\n       * @remarks This is undefined when no parameters are extracted from the path\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The querystring evaluation results containing parsed query parameters.\n     *\n     * This object provides information about how the URL's query string\n     * was processed and any parameters that were extracted or matched.\n     *\n     * @since 2.0.0\n     * @example\n     * ```ts\n     * // For URL \"?filter=active&page=2\"\n     * const querystringResult = {\n     *   condition: \"exact-match\",\n     *   original: { filter: \"active\", page: \"2\" },\n     *   params: { filter: \"active\", page: \"2\" }\n     * };\n     * ```\n     */\n    querystring: {\n      /**\n       * The evaluation condition indicating how the querystring was matched.\n       *\n       * @since 2.0.0\n       * @see {@link Condition} For available condition types\n       * @example \"exact-match\" | \"partial-match\" | \"no-match\"\n       */\n      condition: Condition;\n\n      /**\n       * The original querystring data that was evaluated during routing.\n       *\n       * This can be either a parsed object representation of query parameters\n       * or the raw querystring, depending on how the route was configured.\n       *\n       * @since 2.0.0\n       * @see {@link ReturnParam} For supported parameter types\n       * @example\n       * ```ts\n       * // Parsed object format\n       * original: { filter: \"active\", sort: \"name\" }\n       *\n       * // Raw string format\n       * original: \"filter=active&sort=name\"\n       * ```\n       */\n      original: ReturnParam;\n\n      /**\n       * The parameters extracted from the querystring during evaluation.\n       *\n       * Contains the processed query parameters after applying any route-specific\n       * querystring matching rules and transformations.\n       *\n       * @since 2.0.0\n       * @default undefined\n       * @see {@link ReturnParam} For parameter value types\n       * @example\n       * ```ts\n       * // Standard query parameters\n       * params: { search: \"term\", page: \"1\" }\n       *\n       * // With type conversion\n       * params: { page: 1, active: true }\n       * ```\n       * @remarks This may be undefined if no querystring processing was required\n       */\n      params?: ReturnParam;\n    };\n\n    /**\n     * The component that was resolved and rendered when the route became active.\n     *\n     * This represents the actual component instance, snippet, or component factory\n     * that was determined during the routing process. It can be a direct component\n     * reference, a lazy-loaded component function, or a Svelte snippet.\n     *\n     * @since 2.0.0\n     * @default undefined\n     * @see {@link Component} Svelte component type\n     * @see {@link Snippet} Svelte snippet type\n     * @example\n     * ```ts\n     * // Direct component reference\n     * component: UserProfile\n     *\n     * // Lazy-loaded component\n     * component: () => import('./UserProfile.svelte')\n     *\n     * // Svelte snippet\n     * component: mySnippet\n     * ```\n     * @remarks This is undefined when the route doesn't render a component directly,\n     * such as when using hooks for custom rendering logic\n     */\n    component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n    /**\n     * The HTTP-style status code representing the result of the routing operation.\n     *\n     * This numeric code indicates the outcome of the route evaluation process,\n     * following HTTP status code conventions for consistency and familiarity.\n     * The status helps determine how the route result should be handled by\n     * status handlers and middleware.\n     *\n     * @since 2.0.0\n     * @see {@link StatusCode} For predefined status code constants\n     * @see {@link Statuses} For status-specific handlers\n     * @example\n     * ```ts\n     * status: 200  // Successful route match\n     * status: 404  // Route not found\n     * status: 401  // Unauthorized access\n     * status: 500  // Internal routing error\n     * ```\n     * @remarks Common values include 200 (OK), 404 (Not Found), 401 (Unauthorized),\n     * 403 (Forbidden), and 500 (Internal Server Error)\n     */\n    status: number;\n  };\n\n  /**\n   * The constructor for the `RouteResult` class.\n   *\n   * @param result The result of the route evaluation.\n   */\n  constructor(result: RouteResult) {\n    this.route = result.route;\n    this.result = result.result;\n  }\n\n  /**\n   * The string representation of the route including the querystring.\n   */\n  toString(): string {\n    let querystring = \"\";\n    if (this.result.querystring.original && typeof this.result.querystring.original === \"object\") {\n      const params = new URLSearchParams();\n      for (const [key, value] of Object.entries(this.result.querystring.original)) {\n        if (value !== undefined && value !== null) {\n          params.append(key, String(value));\n        }\n      }\n      querystring = params.toString();\n    } else if (this.result.querystring.original) {\n      querystring = String(this.result.querystring.original);\n    }\n\n    return `${this.result.path.original}${querystring ? `?${querystring}` : \"\"}`;\n  }\n}\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn = (result: RouteResult, span?: Span) => void;\n\n/**\n * The function that is used to apply a route to the DOM.\n *\n * @category Router\n */\nexport type ApplyFn2 = (result: RouteResult, span?: Span) => void;\n\n/**\n * A generic type that can be used to test the type of a value.\n * @category Router\n * @example\n * ```ts\n * const a: Testing<string> = \"asdf\";\n * const b: Testing<number> = 123;\n * ```\n */\nexport type Testing<T> = T;\n\nexport class RouteConfig {\n  name?: string | number;\n  basePath?: string;\n  path?: PathType;\n  querystring?: Record<string, ReturnParam>;\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n  props?: Record<string, any>;\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n  children?: RouteConfig[];\n  status?: number;\n\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = config.path;\n    this.querystring = config.querystring;\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n  }\n\n  toJSON?(): any {\n    return {\n      name: this.name,\n      basePath: this.basePath,\n      path: this.path,\n      props: this.props,\n      component: this.component,\n      querystring: this.querystring,\n      hooks: this.hooks,\n      children: this.children,\n      status: this.status\n    };\n  }\n}\n\n/**\n * A route that can be navigated to.\n * @example\n * ```ts\n * const routes: Route[] = [\n *   {\n *     component: Home\n *   },\n *   {\n *      path: \"(?<child>.*)\",\n *      component: ParseRouteParams\n *   }\n * ]\n * ```\n *\n * @category Router\n */\nexport class Route {\n  /**\n   * The unique identifier of this route.\n   * This is useful if you need to track routes outside of the router's scope.\n   *\n   * @optional If no value is provided, the route will not have a name.\n   */\n  name?: string | number;\n\n  /**\n   * The base path of the route.\n   *\n   * This is useful if you want to be declarative about the base path of the route\n   * and not depend on the router to determine the base path.\n   */\n  basePath?: string;\n\n  /**\n   * The path of the route to match against the current path.\n   *\n   * @optional If not provided, the route will match any path\n   * as it will be the default route.\n   */\n  path?: PathType;\n\n  /**\n   * The query params of the route.\n   *\n   * @optional If no value is provided, there are no query params.\n   */\n  querystring?: Query;\n\n  /**\n   * The component to render when the route is active.\n   *\n   * @optional If no value is provided, the route will not render a component.\n   * This is useful if you want to use pre or post hooks to render a component\n   * or snippet conditionally.\n   */\n  component?: Component<any> | Snippet | (() => Promise<Component<any> | Snippet>) | Function | any;\n\n  /**\n   * The props to pass to the component.\n   *\n   * @optional If a value is provided, the component will receive this value in $props().\n   */\n  props?: Record<string, any>;\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The children routes of the route.\n   *\n   * This is useful if you want to be declarative about the routes that are direct\n   * children of this route and not depend on the router to determine the children\n   * when there are multiple <Router/> instances.\n   *\n   *\n   * @optional If no value is provided, there are no direct child routes. Routes may\n   * be mapped to children routes by the router when there are multiple <Router/> instances\n   * with overlapping `basePath` values.\n   *\n   * @example\n   * ```ts\n   * const routes: Route[] = [\n   *   ...\n   *   {\n   *     path: \"/users\",\n   *     children: [\n   *       {\n   *         path: \"/:id\",\n   *         component: User\n   *       }\n   *     ]\n   *   }\n   *   ...\n   * ]\n   * ```\n   */\n  children?: Route[];\n\n  /**\n   * The status of the route once it has been matched or otherwise processed.\n   */\n  status?: number;\n\n  /**\n   * Traces are a list of objects that describe the route's path and query params\n   * as it is processed by the router.\n   */\n  traces?: Trace[] = $state([]);\n\n  /**\n   * The constructor for the `Route` class.\n   *\n   * @param {Route} config An instance of the `Route` class.\n   */\n  constructor(config: RouteConfig) {\n    this.name = config.name;\n    this.basePath = config.basePath;\n    this.path = typeof config.path === \"string\" ? normalize(config.path) : config.path;\n\n    if (config.querystring) {\n      this.querystring = new Query(config.querystring);\n    }\n\n    this.component = config.component;\n    this.props = config.props;\n    this.hooks = config.hooks;\n    this.status = config.status;\n    this.children = config.children?.map((child) => new Route(child));\n  }\n\n  /**\n   * Parse the route against the given path.\n   * @param path The path to parse against the route.\n   */\n  test?(path: PathType): Evaluation {\n    const matcher = urls.path(path.toString());\n    // Handle string paths being passed in at the route.path level:\n    if (typeof this.path === \"string\") {\n      // Detect if this path contains regex syntax:\n      if (regexp.can(this.path)) {\n        // Path is a regex, so we need to test it against the path passed in:\n        const match = regexp.from(this.path).exec(matcher);\n        if (match) {\n          return {\n            condition: \"exact-match\",\n            params: match.groups\n          };\n        }\n      } else {\n        // Path is not a regex, so we then check if the path passed in is a direct match:\n        if (this.path === matcher) {\n          return {\n            condition: \"exact-match\",\n            params: this.path\n          };\n        } else if (paths.base(this.path, matcher)) {\n          return {\n            condition: \"base-match\",\n            params: {}\n          };\n        }\n      }\n    }\n    // Handle RegExp instances being passed in at the route.path level:\n    else if (this.path instanceof RegExp) {\n      const res = evaluators.any[Identities.regexp](this.path, matcher);\n      if (res) {\n        return {\n          condition: \"exact-match\",\n          params: res\n        };\n      }\n    }\n    // Handle numeric paths being passed in at the route.path level:\n    else if (typeof this.path === \"number\" && this.path === marshal(matcher).value) {\n      throw new Error(\"numbered route match not supported at the route.path level\");\n    }\n\n    return {\n      condition: \"no-match\",\n      params: {}\n    };\n  }\n\n  /**\n   * The absolute path of the route by combining the router's base path and\n   * the route's path.\n   */\n  absolute?(): string {\n    /**\n     * If the router has a base path, we need to combine it with the route's path\n     * otherwise it will have \"undefined\" as the base path and the path will be\n     * incorrect:\n     */\n    if (this.basePath) {\n      return `${this.basePath}${this.path}`;\n    }\n    return this.path.toString();\n  }\n}\n"
  },
  {
    "path": "src/lib/router-instance-config.ts",
    "content": "import { type Component } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { RouteConfig } from \"./route.svelte\";\nimport type { Statuses } from \"./statuses\";\n\n/**\n * Configuration interface for creating a new router instance.\n */\nexport interface RouterInstanceConfigOptions {\n  id?: string;\n  basePath?: string;\n  routes: any[];\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n  initialPath?: string;\n  notFoundComponent?: Component<any>;\n  statuses?: Statuses;\n  renavigation?: boolean;\n}\n\n/**\n * The configuration for a new router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstanceConfig {\n  /**\n   * The id for the router instance.\n   *\n   * @optional If no value is provided, the id will be a random string of characters.\n   */\n  id?: string;\n\n  /**\n   * The base path for the router instance.\n   *\n   * @optional If no value is provided, the base path will be \"/\".\n   */\n  basePath?: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes: RouteConfig[];\n\n  /**\n   * Hooks to be run before and after the routes are rendered\n   * at the router level (independent of the route hooks if applicable).\n   *\n   * @optional If no value is provided, no hooks will be run.\n   */\n  hooks?: {\n    pre?: Hook | Hook[];\n    post?: Hook | Hook[];\n  };\n\n  /**\n   * The initial path for the router instance.\n   *\n   * @optional If no value is provided, the initial path will be the current path of the browser.\n   */\n  initialPath?: string;\n\n  /**\n   * The not found component for the router instance.\n   *\n   * @optional If no value is provided and no route could be found,\n   * the router will will not render anything.\n   */\n  notFoundComponent?: Component<any>;\n\n  /**\n   * The default components rendered when a route is not found and\n   * the status code is in one of the following:\n   * 400, 401, 403, 404, 500\n   * @optional If no value is provided, the default components will not be rendered.\n   */\n  statuses?: Statuses;\n\n  /**\n   * Whether to allow components to re-mount when navigating to routes.\n   * When true, components will re-mount for both same routes and different routes using the same component.\n   * This ensures fresh component state for each navigation.\n   * \n   * @default true - Components will re-mount when navigating to any route\n   */\n  renavigation?: boolean;\n\n  /**\n   * The constructor for this router instance.\n   *\n   * @param {RouterInstanceConfig} config The config for this router instance.\n   */\n  constructor(config: RouterInstanceConfigOptions) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.basePath = config.basePath;\n    this.hooks = config.hooks;\n    this.initialPath = config.initialPath;\n    this.notFoundComponent = config.notFoundComponent;\n    this.statuses = config.statuses;\n    this.renavigation = config.renavigation ?? true;\n    this.routes = config.routes.map(\n      (route) =>\n        new RouteConfig({\n          ...route,\n          ...config\n        })\n    );\n  }\n\n  toJSON(): any {\n    return {\n      id: this.id,\n      basePath: this.basePath,\n      routes: this.routes,\n      hooks: this.hooks\n    };\n  }\n}\n"
  },
  {
    "path": "src/lib/router-instance.svelte.ts",
    "content": "import { Query, registry, RouterInstanceConfig, Span, type ApplyFn, type Hook } from \".\";\nimport { Route, RouteResult } from \"./route.svelte\";\nimport { StatusCode } from \"./statuses\";\nimport { execute } from \"./utilities.svelte\";\n\nimport { SuccessfulConditions } from \"./helpers/evaluators\";\nimport { normalize } from \"./helpers/normalize\";\nimport { createSpan } from \"./helpers/tracing.svelte\";\nimport { urls } from \"./helpers/urls\";\n\n/**\n * The default routes that are used when no routes match.\n *\n * @category Router\n */\nexport const defaultRoutes = [\"\", \"/\", \"/*\", \"/^.*$/\", \"/.*/\"];\n\n/**\n * The handlers type that is used when registering a router instance.\n *\n * This is used to restore the original history methods when the last instance is destroyed\n * and to register & unregister the event listeners for the router instances to prevent memory leaks.\n *\n * @category Router\n */\nexport type RouterHandlers = {\n  /**\n   * The handler for the pushState event.\n   */\n  pushStateHandler: () => void;\n\n  /**\n   * The handler for the replaceState event.\n   */\n  replaceStateHandler: () => void;\n\n  /**\n   * The handler for the popState event.\n   */\n  popStateHandler: () => void;\n\n  /**\n   * The handler for the hashchange event.\n   */\n  hashChangeHandler: () => void;\n};\n\n/**\n * A class that represents a router instance.\n *\n * @remarks\n * This class should rarely be used directly. Instead, use the `Router` component\n * to create a new router instance.\n *\n * @category Router\n */\nexport class RouterInstance {\n  /**\n   * The id of the router instance.\n   */\n  id: string;\n\n  /**\n   * The routes for the router instance.\n   */\n  routes = new Set<Route>();\n\n  /**\n   * The handlers for the router instance.\n   */\n  handlers: RouterHandlers;\n\n  /**\n   * The config for the router instance.\n   */\n  config: RouterInstanceConfig;\n\n  /**\n   * The apply function for the router instance.\n   */\n  applyFn: ApplyFn;\n\n  /**\n   * Whether the router instance is navigating.\n   */\n  navigating = $state(false);\n\n  /**\n   * The current route for the router instance.\n   */\n  current = $state<RouteResult>();\n\n  /**\n   * The constructor for the RouterInstance class.\n   *\n   * @param {RouterInstanceConfig} config The config for the router instance.\n   * @param {ApplyFn} applyFn The apply function for the router instance.\n   */\n  constructor(config: RouterInstanceConfig, applyFn: ApplyFn) {\n    this.id = config.id || Math.random().toString(36).substring(2, 15);\n    this.config = config;\n    this.applyFn = applyFn;\n\n    this.handlers = {\n      pushStateHandler: () => this.handleStateChange(location.toString()),\n      replaceStateHandler: () => this.handleStateChange(location.toString()),\n      popStateHandler: () => this.handleStateChange(location.toString()),\n      hashChangeHandler: () => this.handleStateChange(location.toString())\n    };\n\n    window.addEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.addEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.addEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.addEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    for (let route of config.routes) {\n      this.routes.add(\n        new Route({\n          ...route,\n          /**\n           * If the route has no base path (because it's optional), use\n           * the router instance's base path.\n           */\n          basePath: route.basePath || this.config.basePath\n        })\n      );\n    }\n  }\n\n  /**\n   * Process a state change event from the browser history API.\n   *\n   * This method is called when the browser history API is used to change the\n   * current route via the `pushState`, `replaceState`, or `popState` methods.\n   *\n   * The method will evaluate the route for the given path and query, and apply\n   * the route to the router instance to ultimately call the `applyFn` function\n   * on the downstream router component to render the new route.\n   *\n   * @param {string} path The path to handle the state change for.\n   * @param {Query} query The query to handle the state change for.\n   * @param {Span} span @optional The span to attach traces to. If not provided,\n   * a new span will be created.\n   */\n  async handleStateChange(url: string, span?: Span): Promise<void> {\n    const { path, query } = urls.parse(url);\n    this.navigating = true;\n\n    if (!span) {\n      span = createSpan(\"detected history change event\");\n    }\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.handleStateChange\",\n      description: `attempting to handle a new state change for path \"${path}\"`,\n      metadata: {\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n        basePath: this.config.basePath,\n        path,\n        query,\n        url\n      }\n    });\n\n    const result = await this.get(path, query, span);\n\n    if (result && SuccessfulConditions.includes(result.result.path.condition)) {\n      span?.trace({\n        prefix: \"✅\",\n        name: \"router-instance.handleStateChange\",\n        description: `route found for path \"${path}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query: query?.params || false,\n          route: result,\n          url\n        }\n      });\n\n      // Run the global pre hooks:\n      if (this.config.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, this.config.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Run the route specific pre hooks:\n      if (result.route?.hooks?.pre) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.pre))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      const isSameRoute = this.current && \n        this.current.result.path.original === result.result.path.original &&\n        JSON.stringify(this.current.result.querystring.params) === JSON.stringify(result.result.querystring.params);\n      \n      // This ensures components re-mount for both same routes and different routes using same component\n      const shouldApply = this.config.renavigation !== false;\n      \n      if (shouldApply) {\n        this.current = undefined;\n        \n        span?.trace({\n          prefix: isSameRoute ? \"🔄\" : \"✅\",\n          name: \"router-instance.applyRoute\",\n          description: isSameRoute ? \n            `re-mounting same route \"${result.result.path.original}\" (renavigation enabled)` :\n            `applying new route \"${result.result.path.original}\"`,\n          metadata: {\n            location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n            router: {\n              id: this.config.id,\n              basePath: this.config.basePath\n            },\n            isSameRoute,\n            renavigation: this.config.renavigation,\n            result\n          }\n        });\n        \n        // Contact the downstream router component to apply the route:\n        this.applyFn(result, span);\n        \n        this.current = result;\n      } else {\n        span?.trace({\n          prefix: \"⏭️\",\n          name: \"router-instance.skipRenavigation\",\n          description: `skipping same route \"${result.result.path.original}\" (renavigation disabled)`,\n          metadata: {\n            location: \"/src/lib/router-instance.svelte:handleStateChange()\",\n            router: {\n              id: this.config.id,\n              basePath: this.config.basePath\n            },\n            isSameRoute,\n            renavigation: this.config.renavigation,\n            result\n          }\n        });\n        \n        this.current = result;\n      }\n\n      // Run the route specific post hooks:\n      if (result && result.route?.hooks?.post) {\n        if (!(await this.evaluateHooks(result, result.route.hooks.post))) {\n          this.navigating = false;\n          return;\n        }\n      }\n\n      // Finally, run the global post hooks:\n      if (this.config.hooks?.post) {\n        await this.evaluateHooks(result, this.config.hooks.post);\n      }\n\n      this.current = result;\n    }\n\n    this.navigating = false;\n  }\n\n  async evaluateHooks(route: RouteResult, hooks: Hook | Hook[]): Promise<boolean> {\n    if (Array.isArray(hooks)) {\n      for (const hook of hooks) {\n        if (!(await execute(() => hook(route)))) {\n          return false;\n        }\n\n        /**\n         * Add small delay between hooks to prevent rapid History API\n         * calls causing the browser to halt (firefox specifically).\n         */\n        await new Promise((resolve) => setTimeout(resolve, 50));\n      }\n    } else {\n      if (!(await execute(() => hooks(route)))) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Retrieve a route for a given path.\n   *\n   * @param {string} path The path to get the route for.\n   *\n   * @returns {RegistryMatch} The matched route for the given path.\n   */\n  async get(path: string, query?: Query, span?: Span): Promise<RouteResult> {\n    path = path.replace(\"/#\", \"\");\n    const normalized = normalize(path.replace(this.config.basePath || \"/\", \"\"));\n    const renderDefaultRoute = (reason: string): RouteResult => {\n      let defaultRoute: Route;\n\n      for (const route of this.routes) {\n        if (!route.path || defaultRoutes.includes(route.path.toString())) {\n          defaultRoute = route;\n          break;\n        }\n      }\n\n      span?.trace({\n        prefix: defaultRoute ? \"✅\" : \"❌\",\n        name: \"router-instance.getDefaultRoute\",\n        description: `get default route because \"${reason}\"`,\n        metadata: {\n          location: \"/src/lib/router-instance.svelte:get()\",\n          router: {\n            id: this.config.id,\n            basePath: this.config.basePath\n          },\n          path,\n          query,\n          normalized,\n          route: defaultRoute\n        }\n      });\n\n      if (defaultRoute) {\n        return new RouteResult({\n          route: defaultRoute,\n          result: {\n            path: {\n              condition: \"default-match\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            component: defaultRoute.component,\n            status: StatusCode.OK\n          }\n        });\n      }\n    };\n\n    span?.trace({\n      prefix: \"🔍\",\n      name: \"router-instance.get\",\n      description: `${this.config.id} with base path \"${this.config.basePath || \"/\"}\" is attempting to get a route for path \"${path}\"`,\n      metadata: {\n        location: \"/src/lib/router-instance.svelte:get()\",\n        router: {\n          id: this.config.id,\n          basePath: this.config.basePath\n        },\n        path,\n        query,\n        normalized\n      }\n    });\n\n    if (this.config.basePath === path) {\n      return renderDefaultRoute(\"base path is the same as the path\");\n    }\n\n    let candidate: RouteResult;\n\n    /**\n     * Now we check for router nesting:\n     */\n    for (const route of this.routes) {\n      const pathEvaluation = route.test(normalized);\n      if (pathEvaluation && SuccessfulConditions.includes(pathEvaluation.condition)) {\n        span?.trace({\n          prefix: \"✅\",\n          name: \"router-instance.get:routesloop\",\n          description: `${pathEvaluation.condition} for inbound path \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n          metadata: {\n            location: \"/src/lib/router-instance.svelte:get():forloop\",\n            router: {\n              id: this.config.id,\n              basePath: this.config.basePath\n            },\n            path,\n            query,\n            normalized,\n            route,\n            evaluation: {\n              path: pathEvaluation\n            }\n          }\n        });\n\n        if (route.querystring && query) {\n          const queryEvaluation = query.test(route.querystring);\n          if (SuccessfulConditions.includes(queryEvaluation?.condition)) {\n            span?.trace({\n              prefix: \"✅\",\n              name: \"router-instance.get.evaluateQuery\",\n              description: `${queryEvaluation?.condition} evaluating querystring \"${query?.toString()}\" for the route \"${path}\"${route.name ? ` (named: \"${route.name}\")` : \"\"}`,\n              metadata: {\n                location: \"/src/lib/router-instance.svelte:get()\",\n                router: {\n                  id: this.config.id,\n                  basePath: this.config.basePath\n                },\n                path,\n                query,\n                normalized,\n                evaluation: {\n                  path: pathEvaluation,\n                  querystring: queryEvaluation\n                }\n              }\n            });\n            candidate = new RouteResult({\n              route,\n              result: {\n                path: {\n                  ...pathEvaluation,\n                  original: normalized\n                },\n                querystring: {\n                  ...queryEvaluation,\n                  original: query.toJSON()\n                },\n                component: route.component,\n                status: StatusCode.OK\n              }\n            });\n          }\n        } else {\n          /**\n           * No querystring is configured for this route, so we will\n           * use the querystring from the inbound path.\n           */\n          candidate = new RouteResult({\n            route,\n            result: {\n              path: {\n                ...pathEvaluation,\n                original: normalized\n              },\n              querystring: {\n                condition: \"permitted-no-conditions\",\n                original: query?.toJSON(),\n                params: query?.toJSON()\n              },\n              component: route.component,\n              status: StatusCode.OK\n            }\n          });\n        }\n      }\n    }\n\n    /**\n     * If we've made it this far, we should default to trying to find\n     * a route that has no path configured. This will be treated as\n     * the \"default\" route:\n     */\n    if (path === \"/\") {\n      return renderDefaultRoute(\"no routes match, last resort is to find a default route\");\n    }\n\n    /**\n     * We've exhaausted all options, so we will attempt to locate\n     * a 404 route from the statuses configuration applied to this\n     * router instance.\n     */\n    if (!candidate && this.config.statuses?.[404]) {\n      const status = this.config.statuses[404];\n      if (typeof status === \"function\") {\n        return {\n          result: {\n            ...status(\n              {\n                result: {\n                  path: {\n                    condition: \"permitted-no-conditions\",\n                    original: path\n                  },\n                  querystring: {\n                    condition: \"permitted-no-conditions\",\n                    original: query?.toJSON(),\n                    params: query?.toJSON()\n                  },\n                  status: StatusCode.NotFound\n                }\n              },\n              span\n            ),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      } else {\n        return {\n          result: {\n            ...(status as object),\n            path: {\n              condition: \"permitted-no-conditions\",\n              original: path\n            },\n            querystring: {\n              condition: \"permitted-no-conditions\",\n              original: query?.toJSON(),\n              params: query?.toJSON()\n            },\n            status: StatusCode.NotFound\n          }\n        };\n      }\n    }\n\n    return candidate;\n  }\n\n  /**\n   * Deregister a router instance by removing it from the registry and\n   * restoring the original history methods.\n   *\n   * This is called when a router instance is removed from the DOM\n   * triggered by the `onDestroy` lifecycle method of the router instance.\n   */\n  deregister(span?: Span): void {\n    window.removeEventListener(\"pushState\", this.handlers.pushStateHandler);\n    window.removeEventListener(\"replaceState\", this.handlers.replaceStateHandler);\n    window.removeEventListener(\"popstate\", this.handlers.popStateHandler);\n    window.removeEventListener(\"hashchange\", this.handlers.hashChangeHandler);\n\n    registry.deregister(this.config.id, span);\n  }\n\n  /**\n   * Get routes as an array for serialization purposes.\n   *\n   * @returns {Route[]} The routes as an array.\n   */\n  get routesArray(): Route[] {\n    return Array.from(this.routes);\n  }\n\n  /**\n   * Custom JSON serialization to handle Set objects properly.\n   *\n   * @returns {object} The serializable representation of the router instance.\n   */\n  toJSON(): any {\n    return {\n      id: this.id,\n      config: this.config\n    };\n  }\n}\n"
  },
  {
    "path": "src/lib/router-integration.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from 'vitest';\n\ndescribe('Router Remount Integration', () => {\n  it('should detect same route navigation correctly', () => {\n    const route1 = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { foo: 'bar' } }\n      }\n    };\n    \n    const route2 = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { foo: 'bar' } }\n      }\n    };\n    \n    const route3 = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { foo: 'different' } }\n      }\n    };\n    \n    const isSameRoute = (current: any, result: any) => {\n      return !!(current && \n        current.result.path.original === result.result.path.original &&\n        JSON.stringify(current.result.querystring.params) === JSON.stringify(result.result.querystring.params));\n    };\n    \n    expect(isSameRoute(route1, route2)).toBe(true);\n    expect(isSameRoute(route1, route3)).toBe(false);\n    expect(isSameRoute(null, route1)).toBe(false);\n  });\n\n  it('should determine when to apply route based on renavigation config', () => {\n    const current = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: {} }\n      }\n    };\n    \n    const newRoute = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: {} }\n      }\n    };\n    \n    const shouldApply = (current: any, result: any, renavigation: boolean | undefined) => {\n      const isSameRoute = current && \n        current.result.path.original === result.result.path.original &&\n        JSON.stringify(current.result.querystring.params) === JSON.stringify(result.result.querystring.params);\n      \n      return !isSameRoute || renavigation !== false;\n    };\n    \n    expect(shouldApply(current, newRoute, undefined)).toBe(true);\n    \n    expect(shouldApply(current, newRoute, true)).toBe(true);\n    \n    expect(shouldApply(current, newRoute, false)).toBe(false);\n    \n    const differentRoute = {\n      result: {\n        path: { original: '/different' },\n        querystring: { params: {} }\n      }\n    };\n    expect(shouldApply(current, differentRoute, false)).toBe(true);\n  });\n\n  it('should handle query parameter differences correctly', () => {\n    const current = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { a: '1', b: '2' } }\n      }\n    };\n    \n    const sameQuery = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { a: '1', b: '2' } }\n      }\n    };\n    \n    const differentQuery = {\n      result: {\n        path: { original: '/test' },\n        querystring: { params: { a: '1', b: '3' } }\n      }\n    };\n    \n    const isSameRoute = (current: any, result: any) => {\n      return !!(current && \n        current.result.path.original === result.result.path.original &&\n        JSON.stringify(current.result.querystring.params) === JSON.stringify(result.result.querystring.params));\n    };\n    \n    expect(isSameRoute(current, sameQuery)).toBe(true);\n    expect(isSameRoute(current, differentQuery)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "src/lib/router-patterns-demo.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { RouterInstanceConfig } from './router-instance-config';\n\ndescribe('Patterns Demo Router Test Coverage', () => {\n  let mockDumpComponent: any;\n  let mockParameterExtractionComponent: any;\n\n  beforeEach(() => {\n    mockDumpComponent = vi.fn();\n    mockParameterExtractionComponent = vi.fn();\n  });\n\n  describe('Patterns router configuration', () => {\n    it('should create router config matching patterns demo setup', () => {\n      const routes = [\n        {\n          path: \"default-route\",\n          component: mockDumpComponent\n        },\n        {\n          path: \"single-path\", \n          component: mockDumpComponent\n        },\n        {\n          path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n          component: mockParameterExtractionComponent\n        }\n      ];\n\n      const config = new RouterInstanceConfig({\n        id: 'patterns-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: routes\n      });\n\n      expect(config.basePath).toBe('/patterns');\n      expect(config.renavigation).toBe(true);\n      expect(config.routes).toHaveLength(3);\n      \n      expect(config.routes[0].path).toBe('default-route');\n      expect(config.routes[1].path).toBe('single-path');\n      expect(config.routes[2].path).toBeInstanceOf(RegExp);\n    });\n\n    it('should handle same component for different routes', () => {\n      const routes = [\n        {\n          path: \"default-route\",\n          component: mockDumpComponent\n        },\n        {\n          path: \"single-path\",\n          component: mockDumpComponent\n        }\n      ];\n\n      const config = new RouterInstanceConfig({\n        id: 'patterns-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: routes\n      });\n\n      expect(config.routes[0].component).toBe(mockDumpComponent);\n      expect(config.routes[1].component).toBe(mockDumpComponent);\n      expect(config.routes[0].component).toBe(config.routes[1].component);\n    });\n\n    it('should support regex routes for parameter extraction', () => {\n      const parameterExtractionRoute = {\n        path: /^\\/parameter-extraction\\/(?<child>.*)$/,\n        component: mockParameterExtractionComponent\n      };\n\n      const config = new RouterInstanceConfig({\n        id: 'patterns-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [parameterExtractionRoute]\n      });\n\n      const routePath = config.routes[0].path;\n      expect(routePath).toBeInstanceOf(RegExp);\n      if (routePath instanceof RegExp) {\n        expect(routePath.test('/parameter-extraction/some-child')).toBe(true);\n        expect(routePath.test('/other-route')).toBe(false);\n      }\n    });\n  });\n\n  describe('Navigation scenarios from patterns demo', () => {\n    it('should handle navigation from default-route to single-path', () => {\n      const mockApplyFn = vi.fn();\n      \n      const currentRoute = {\n        result: {\n          path: { original: '/patterns/default-route' },\n          querystring: { params: {} }\n        }\n      };\n\n      const newRoute = {\n        result: {\n          path: { original: '/patterns/single-path' },\n          querystring: { params: {} }\n        }\n      };\n\n      const isSameRoute = currentRoute && \n        currentRoute.result.path.original === newRoute.result.path.original &&\n        JSON.stringify(currentRoute.result.querystring.params) === JSON.stringify(newRoute.result.querystring.params);\n\n      expect(isSameRoute).toBe(false); // Different paths\n\n      const renavigation = true;\n      const shouldApply = (renavigation as boolean | undefined) !== false;\n\n      expect(shouldApply).toBe(true); // Should apply because renavigation is enabled\n    });\n\n    it('should handle navigation to same route with renavigation enabled', () => {\n      const currentRoute = {\n        result: {\n          path: { original: '/patterns/default-route' },\n          querystring: { params: {} }\n        }\n      };\n\n      const sameRoute = {\n        result: {\n          path: { original: '/patterns/default-route' },\n          querystring: { params: {} }\n        }\n      };\n\n      const isSameRoute = currentRoute && \n        currentRoute.result.path.original === sameRoute.result.path.original &&\n        JSON.stringify(currentRoute.result.querystring.params) === JSON.stringify(sameRoute.result.querystring.params);\n\n      expect(isSameRoute).toBe(true); // Same paths\n\n      const renavigation = true;\n      const shouldApply = (renavigation as boolean | undefined) !== false;\n\n      expect(shouldApply).toBe(true); // Should still apply because renavigation is enabled\n    });\n\n    it('should handle myExtraRouterProp from patterns demo', () => {\n      const config = new RouterInstanceConfig({\n        id: 'patterns-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [\n          {\n            path: \"default-route\",\n            component: mockDumpComponent\n          }\n        ]\n      });\n\n      expect(config.id).toBe('patterns-router');\n      expect(config.basePath).toBe('/patterns');\n      expect(config.renavigation).toBe(true);\n    });\n  });\n\n  describe('Dump component data flow', () => {\n    it('should verify route props structure for Dump component', () => {\n      \n      const mockRoute = {\n        result: {\n          path: { \n            original: '/patterns/default-route',\n            condition: 'exact-match'\n          },\n          querystring: { \n            params: { test: 'value' },\n            original: { test: 'value' }\n          },\n          component: mockDumpComponent,\n          status: 200\n        },\n        route: {\n          path: 'default-route',\n          component: mockDumpComponent,\n          props: { additionalProp: 'test' }\n        }\n      };\n\n      expect(mockRoute.result.path).toHaveProperty('original');\n      expect(mockRoute.result.path).toHaveProperty('condition');\n      expect(mockRoute.result.querystring).toHaveProperty('params');\n      expect(mockRoute.result).toHaveProperty('component');\n      expect(mockRoute.result).toHaveProperty('status');\n      \n      expect(mockRoute.route).toHaveProperty('path');\n      expect(mockRoute.route).toHaveProperty('component');\n    });\n\n    it('should handle route data changes between navigations', () => {\n      const defaultRouteData = {\n        result: {\n          path: { original: '/patterns/default-route' },\n          querystring: { params: {} }\n        }\n      };\n\n      const singlePathRouteData = {\n        result: {\n          path: { original: '/patterns/single-path' },\n          querystring: { params: {} }\n        }\n      };\n\n      expect(defaultRouteData.result.path.original).toBe('/patterns/default-route');\n      expect(singlePathRouteData.result.path.original).toBe('/patterns/single-path');\n      expect(defaultRouteData.result.path.original).not.toBe(singlePathRouteData.result.path.original);\n    });\n\n    it('should verify JSON.stringify behavior for route data', () => {\n      const routeData = {\n        path: '/patterns/default-route',\n        params: { test: 'value' },\n        nested: { deep: { value: 123 } }\n      };\n\n      const jsonString = JSON.stringify(routeData, null, 2);\n      expect(jsonString).toContain('/patterns/default-route');\n      expect(jsonString).toContain('test');\n      expect(jsonString).toContain('value');\n      expect(jsonString).toContain('123');\n    });\n  });\n\n  describe('Edge cases and error handling', () => {\n    it('should handle null component gracefully', () => {\n      const config = new RouterInstanceConfig({\n        id: 'test-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [\n          {\n            path: 'null-component',\n            component: null as any\n          }\n        ]\n      });\n\n      expect(config.routes[0].component).toBeNull();\n      expect(config.renavigation).toBe(true);\n    });\n\n    it('should handle undefined route props', () => {\n      const config = new RouterInstanceConfig({\n        id: 'test-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [\n          {\n            path: 'no-props',\n            component: mockDumpComponent\n          }\n        ]\n      });\n\n      expect(config.routes[0].props).toBeUndefined();\n    });\n\n    it('should handle async component functions', async () => {\n      const asyncComponent = async () => ({ default: mockDumpComponent });\n      \n      const config = new RouterInstanceConfig({\n        id: 'test-router',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [\n          {\n            path: 'async-route',\n            component: asyncComponent\n          }\n        ]\n      });\n\n      expect(config.routes[0].component).toBe(asyncComponent);\n      expect(typeof config.routes[0].component).toBe('function');\n      \n      const result = await config.routes[0].component();\n      expect(result.default).toBe(mockDumpComponent);\n    });\n\n    it('should handle complex route parameters', () => {\n      const complexRoute = {\n        result: {\n          path: { original: '/patterns/complex-route' },\n          querystring: { \n            params: { \n              id: '123',\n              filter: 'active',\n              sort: 'desc',\n              nested: { deep: 'value' }\n            }\n          }\n        }\n      };\n\n      expect(complexRoute.result.querystring.params.id).toBe('123');\n      expect(complexRoute.result.querystring.params.filter).toBe('active');\n      expect(complexRoute.result.querystring.params.nested.deep).toBe('value');\n    });\n\n    it('should handle empty basePath', () => {\n      const config = new RouterInstanceConfig({\n        id: 'test-router',\n        basePath: '',\n        renavigation: true,\n        routes: [\n          {\n            path: 'root-route',\n            component: mockDumpComponent\n          }\n        ]\n      });\n\n      expect(config.basePath).toBe('');\n      expect(config.routes[0].path).toBe('root-route');\n    });\n\n    it('should handle multiple router instances', () => {\n      const config1 = new RouterInstanceConfig({\n        id: 'router-1',\n        basePath: '/patterns',\n        renavigation: true,\n        routes: [{ path: 'route1', component: mockDumpComponent }]\n      });\n\n      const config2 = new RouterInstanceConfig({\n        id: 'router-2',\n        basePath: '/other',\n        renavigation: false,\n        routes: [{ path: 'route2', component: mockParameterExtractionComponent }]\n      });\n\n      expect(config1.id).toBe('router-1');\n      expect(config2.id).toBe('router-2');\n      expect(config1.renavigation).toBe(true);\n      expect(config2.renavigation).toBe(false);\n      expect(config1.basePath).not.toBe(config2.basePath);\n    });\n  });\n});\n"
  },
  {
    "path": "src/lib/router-remount.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { RouterInstanceConfig } from './router-instance-config';\n\ndescribe('Router Remount Configuration', () => {\n  let mockComponent: any;\n\n  beforeEach(() => {\n    mockComponent = vi.fn();\n  });\n\n  it('should create RouterInstanceConfig with renavigation enabled by default', () => {\n    const config = new RouterInstanceConfig({\n      id: 'test-router',\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        }\n      ]\n    });\n\n    expect(config.renavigation).toBe(true);\n  });\n\n  it('should create RouterInstanceConfig with renavigation explicitly disabled', () => {\n    const config = new RouterInstanceConfig({\n      id: 'test-router',\n      renavigation: false,\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        }\n      ]\n    });\n\n    expect(config.renavigation).toBe(false);\n  });\n\n  it('should create RouterInstanceConfig with renavigation explicitly enabled', () => {\n    const config = new RouterInstanceConfig({\n      id: 'test-router',\n      renavigation: true,\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        }\n      ]\n    });\n\n    expect(config.renavigation).toBe(true);\n  });\n\n  it('should preserve renavigation setting in constructor', () => {\n    const configDisabled = new RouterInstanceConfig({\n      id: 'test-router',\n      renavigation: false,\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        }\n      ]\n    });\n\n    const configEnabled = new RouterInstanceConfig({\n      id: 'test-router',\n      renavigation: true,\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        }\n      ]\n    });\n\n    expect(configDisabled.renavigation).toBe(false);\n    expect(configEnabled.renavigation).toBe(true);\n  });\n\n  it('should handle multiple routes with renavigation configuration', () => {\n    const config = new RouterInstanceConfig({\n      id: 'test-router',\n      renavigation: true,\n      routes: [\n        {\n          path: '/test',\n          component: mockComponent\n        },\n        {\n          path: '/other',\n          component: mockComponent\n        }\n      ]\n    });\n\n    expect(config.renavigation).toBe(true);\n    expect(config.routes).toHaveLength(2);\n  });\n});\n"
  },
  {
    "path": "src/lib/router.svelte",
    "content": "<script lang=\"ts\">\n  import { onDestroy, unmount, type Component } from \"svelte\";\n  import { createSpan, Span } from \"./helpers/tracing.svelte\";\n  import { registry } from \"./registry.svelte\";\n  import { type RouteResult } from \"./route.svelte\";\n  import { RouterInstanceConfig } from \"./router-instance-config\";\n  import type { RouterInstance } from \"./router-instance.svelte\";\n\n  let { instance = $bindable(), ...rest } = $props<\n    { instance?: RouterInstance } & Partial<RouterInstanceConfig> & Record<string, any>\n  >();\n\n  const span = createSpan(rest.id ? `[${rest.id}]` : \"router\");\n\n  let RenderableComponent = $state<Component | null>(null);\n  let router: RouterInstance;\n  let route: RouteResult = $state();\n  let additionalProps = $state<Record<string, any>>({});\n\n  const apply = async (r: RouteResult, span?: Span) => {\n    route = r;\n    span?.trace({\n      prefix: \"✅\",\n      name: \"apply\",\n      description: `<Router${router.config.id ? ` id=\"${router.config.id}\"` : \"\"}/> applying route ${r.result.path.original} (${r.result.path.condition})`,\n      metadata: {\n        location: \"/src/lib/router.svelte:apply()\",\n        router: {\n          id: router.config.id,\n          basePath: router.config.basePath\n        },\n        result: r\n      }\n    });\n    \n    // The {#key} block handles component lifecycle automatically\n    // No manual unmounting needed - Svelte manages this for us\n\n    if (typeof r.result.component === \"function\" && r.result.component.constructor.name === \"AsyncFunction\") {\n      // Handle async component by first awaiting the import:\n      const module = await r.result.component();\n      RenderableComponent = module.default || module;\n    } else {\n      // Handle regular component by directly assigning the component:\n      RenderableComponent = r.result.component;\n    }\n    \n    // Force reactivity by updating route state after component assignment\n    route = { ...r };\n    additionalProps = route.route?.props;\n  };\n\n  router = registry.register(new RouterInstanceConfig(rest), apply, span);\n\n  span?.trace({\n    prefix: \"✅\",\n    name: \"<Router/> Component\",\n    description: \"new component mounted\",\n    metadata: {\n      router: {\n        id: router.config.id,\n        basePath: router.config.basePath\n      },\n      location: \"/src/lib/router.svelte:mount()\"\n    }\n  });\n\n  instance = router;\n\n  if (span) {\n    span.metadata = {\n      router: router.config.id\n    };\n  }\n\n  router.handleStateChange(location.toString(), span);\n\n  onDestroy(() => {\n    router.deregister(span);\n  });\n\n  const { routes, basePath, ...restWithoutRoutes } = rest;\n</script>\n\n{#key route?.result?.path?.original || Math.random()}\n  <RenderableComponent\n    {route}\n    {...additionalProps}\n    {...restWithoutRoutes} />\n{/key}\n"
  },
  {
    "path": "src/lib/statuses.ts",
    "content": "import type { Component } from \"svelte\";\n\nimport type { Route, RouteResult } from \"./route.svelte\";\n\nimport type { Span } from \"./helpers/tracing.svelte\";\n\n/**\n * The available status codes that a route can have called out from the statuses\n * handler mapping.\n *\n * @see {@link Statuses}\n * @category Router\n */\nexport enum StatusCode {\n  OK = 200,\n  PermanentRedirect = 301,\n  TemporaryRedirect = 302,\n  BadRequest = 400,\n  Unauthorized = 401,\n  Forbidden = 403,\n  NotFound = 404,\n  InternalServerError = 500\n}\n\n/**\n * Route status handler mapping.\n *\n * Status handlers are called with a path and should return a new route\n * or a promise that resolves to a new route.\n *\n * @example\n * ```ts\n * const statuses: Statuses = {\n *   [StatusCode.NotFound]: {\n *     component: NotFound,\n *     props: {\n *       importantInfo: \"lets go!\"\n *     }\n *   },\n *   [StatusCode.BadRequest]: (path) => {\n *     notifySomething(path);\n *     return {\n *       component: BadRequest,\n *       props: {\n *         importantInfo: \"something went wrong...\"\n *       }\n *     };\n *   }\n * }\n * ```\n *\n * @see {@link Route}\n * @see {@link StatusCode}\n * @see {@link getStatusByValue}\n * @category Router\n */\nexport type Statuses = Partial<{\n  [K in StatusCode]: (\n    result: RouteResult,\n    span?: Span\n  ) => Route | Promise<Route> | Component<any> | { component: Component<any>; props?: Record<string, any> };\n}>;\n\n/**\n * Get the status by value.\n *\n * @param {number} value The value to get the status for.\n * @returns {StatusCode} The status.\n *\n * @see {@link StatusCode}\n * @category Router\n */\nexport const getStatusByValue = (value: number) => {\n  return Object.keys(StatusCode)[Object.values(StatusCode).indexOf(value)];\n};\n"
  },
  {
    "path": "src/lib/utilities.svelte.ts",
    "content": "/**\n * Wait for a predicate to become true with timeout handling.\n *\n * @param predicate Function that returns boolean to check for\n * @param timeout Time in milliseconds to wait before timing out\n * @throws Error if timeout is reached before predicate becomes true\n *\n * @category utilities\n */\nexport async function wait(predicate: () => boolean, timeout = 5000): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const timer = setTimeout(() => {\n      reject(new Error(`Timeout after ${timeout}ms waiting for predicate: ${predicate}`));\n    }, timeout);\n\n    const check = async () => {\n      if (predicate()) {\n        clearTimeout(timer);\n        resolve();\n      } else {\n        setTimeout(check, 50);\n      }\n    };\n\n    check();\n  });\n}\n\n/**\n * Check if a value is a promise.\n *\n * @param value - The value to check.\n *\n * @returns True if the value is a promise, false otherwise.\n *\n * @category utilities\n */\nexport function isPromise(value: any): boolean {\n  return !!value && (typeof value === \"object\" || typeof value === \"function\") && typeof value.then === \"function\";\n}\n\n/**\n * Execute a function and return a promise if the function is a promise.\n *\n * @param fn - The function to execute.\n *\n * @returns A promise if the function is a promise, otherwise the function result.\n *\n * @category utilities\n */\nexport const execute = async <T>(fn: () => T | Promise<T>): Promise<T> => {\n  if (isPromise(fn)) {\n    return await fn();\n  } else {\n    return fn();\n  }\n};\n\n/**\n * A reactive map that can be observed for changes using `$state()`.\n *\n * @category utilities\n */\nexport class ReactiveMap<K, V> extends Map<K, V> {\n  #state = $state(false);\n\n  get size() {\n    this.#state;\n    return super.size;\n  }\n\n  #trig() {\n    this.#state = !this.#state;\n  }\n\n  add(key: K, value: V) {\n    if (this.has(key)) {\n      throw new Error(`key ${key} already exists`);\n    }\n    return this.set(key, value);\n  }\n\n  get(key: K) {\n    this.#state;\n    return super.get(key);\n  }\n\n  set(key: K, value: V) {\n    const result = super.set(key, value);\n    this.#trig();\n    return result;\n  }\n\n  delete(key: K) {\n    const result = super.delete(key);\n    if (result) this.#trig();\n    return result;\n  }\n\n  clear() {\n    const result = super.clear();\n    this.#trig();\n    return result;\n  }\n\n  keys() {\n    this.#state;\n    return super.keys();\n  }\n  values() {\n    this.#state;\n    return super.values();\n  }\n  entries() {\n    this.#state;\n    return super.entries();\n  }\n  forEach(fn: (value: V, key: K, map: Map<K, V>) => void) {\n    this.#state;\n    return super.forEach(fn);\n  }\n\n  [Symbol.iterator]() {\n    this.#state;\n    return super[Symbol.iterator]();\n  }\n}\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\nimport type { TracingConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface ImportMetaEnv {\n  readonly SPA_ROUTER: {\n    logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n    tracing: TracingConfig;\n  };\n}\n"
  },
  {
    "path": "svelte.config.js",
    "content": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n\tpreprocess: [vitePreprocess({})]\n};\n"
  },
  {
    "path": "test/app/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Matthew Davis <matthew@matthewdavis.io>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "test/app/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    <title>@mateothegreat/svelte5-router test</title>\n  </head>\n  <body class=\"dark\">\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/app/package.json",
    "content": "{\n  \"name\": \"@mateothegreat/svelte-template-app\",\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite dev\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"check\": \"svelte-check --tsconfig ./tsconfig.json\",\n    \"test\": \"vitest\",\n    \"test:coverage\": \"vitest --coverage\",\n    \"lint\": \"prettier --check .\",\n    \"format\": \"prettier --write .\"\n  },\n  \"devDependencies\": {\n    \"@sveltejs/vite-plugin-svelte\": \"^6.1.3\",\n    \"@tailwindcss/vite\": \"^4.1.12\",\n    \"@testing-library/svelte\": \"^5.2.8\",\n    \"@tsconfig/svelte\": \"^5.0.5\",\n    \"@types/node\": \"^24.3.0\",\n    \"@vitest/coverage-v8\": \"^3.2.4\",\n    \"jsdom\": \"^26.1.0\",\n    \"prettier\": \"^3.6.2\",\n    \"prettier-plugin-svelte\": \"^3.4.0\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.14\",\n    \"svelte\": \"^5.38.6\",\n    \"svelte-check\": \"^4.3.1\",\n    \"tailwindcss\": \"^4.1.12\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^7.1.3\",\n    \"vitest\": \"^3.2.4\"\n  }\n}\n"
  },
  {
    "path": "test/app/readme.md",
    "content": "# Date _and_ Time Picker\n\nA date and time picker for Svelte 5 using bits-ui.\n\n![alt text](<Google Chrome-001276@2x.png>)\n\n> Live demo: <https://svelte5-datetime-picker.vercel.app>\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-datetime-picker\n```\n"
  },
  {
    "path": "test/app/src/app.css",
    "content": "@import \"tailwindcss\";\n"
  },
  {
    "path": "test/app/src/app.svelte",
    "content": "<script lang=\"ts\">\n  import { route, Router, type RouteConfig, type RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import TestA from \"./routes/test-a/test-a.svelte\";\n  import TestB from \"./routes/test-b/test-b.svelte\";\n\n  let instance: RouterInstance;\n\n  const routes: RouteConfig[] = [\n    {\n      path: \"/test-a\",\n      component: TestA,\n      hooks: {\n        pre: (r: RouteConfig) => {\n          console.log(`pre hook for ${r.result.path.original}`, r.result.path);\n          return true;\n        },\n        post: (r: RouteConfig) => {\n          console.log(`post hook for ${r.result.path.original}`, r.result.path);\n          return true;\n        }\n      }\n    },\n    {\n      path: \"/test-b\",\n      component: TestB,\n      hooks: {\n        pre: (r: RouteConfig) => {\n          console.log(`pre hook for ${r.result.path.original}`, r.result.path);\n          return true;\n        },\n        post: (r: RouteConfig) => {\n          console.log(`post hook for ${r.result.path.original}`, r.result.path);\n          return true;\n        }\n      }\n    }\n  ];\n</script>\n\n<h1 class=\"bg-zinc-950/95 p-2 font-medium text-emerald-400\">@mateothegreat/svelte5-router test</h1>\n\n<div class=\"m-2 flex flex-col gap-2\">\n  <div class=\"flex flex-col gap-4 rounded-md border-2 border-gray-300 bg-zinc-500 p-2 text-sm font-medium text-white\">\n    <div class=\"group flex gap-4\">\n      <a use:route href=\"/test-a\" class=\"hover:text-blue-600 hover:underline\">/test-a</a>\n      <a use:route href=\"/test-b\" class=\"hover:text-blue-600 hover:underline\">/test-b</a>\n    </div>\n  </div>\n  <div class=\"m-1 flex flex-col gap-4 rounded-md bg-slate-200 p-4\">\n    <Router bind:instance {routes} />\n  </div>\n</div>\n"
  },
  {
    "path": "test/app/src/main.ts",
    "content": "import { mount } from \"svelte\";\n\nimport \"./app.css\";\nimport App from \"./app.svelte\";\n\nconst app = mount(App, {\n  target: document.getElementById(\"app\")!\n});\n"
  },
  {
    "path": "test/app/src/routes/test-a/test-a.svelte",
    "content": "<div class=\"flex flex-col gap-4 rounded-md bg-emerald-300 p-2\">\n  <h1 class=\"text-lg font-bold\">test-a.svelte</h1>\n  <div class=\"text-sm\">This is the test-a page.</div>\n</div>\n"
  },
  {
    "path": "test/app/src/routes/test-b/test-b.svelte",
    "content": "<script lang=\"ts\">\n  import { replace } from \"@mateothegreat/svelte5-router\";\n\n  setTimeout(() => {\n    replace(\"/test-a\", {\n      foo: \"bar\"\n    });\n  }, 200);\n</script>\n\n<div class=\"flex flex-col gap-4 rounded-md bg-emerald-300 p-2\">\n  <h1 class=\"text-lg font-bold\">test-b.svelte</h1>\n  <div class=\"text-sm\">This is the test-b page.</div>\n  <div class=\"bg-zinc-950/95 p-2 text-center font-bold text-emerald-400 text-pink-500\">In 2 seconds, I will pop back to the previous page...</div>\n</div>\n"
  },
  {
    "path": "test/app/svelte.config.ts",
    "content": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess()\n};\n"
  },
  {
    "path": "test/app/tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"sourceMap\": true,\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"isolatedModules\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"moduleResolution\": \"bundler\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "test/app/vite.config.ts",
    "content": "import { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-inspector\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  plugins: [\n    svelte(),\n    tailwindcss(),\n    svelteInspector({\n      toggleKeyCombo: \"alt-x\",\n      showToggleButton: \"always\",\n      toggleButtonPos: \"bottom-left\"\n    })\n  ]\n});\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2022\", \"DOM\"],\n    \"verbatimModuleSyntax\": true,\n    \"declaration\": true,\n    \"declarationMap\": false,\n    \"emitDeclarationOnly\": false,\n    \"outDir\": \"dist\",\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"strictNullChecks\": false,\n    \"sourceMap\": false\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"sourceMap\": true,\n    \"target\": \"ESNext\",\n    \"lib\": [\"ESNext\"],\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"allowJs\": false,\n    \"checkJs\": false,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowArbitraryExtensions\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "// vite.config.ts\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { sveltePreprocess } from \"svelte-preprocess\";\nimport { defineConfig } from \"vite\";\nimport dts from \"vite-plugin-dts\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\n// Helper to copy .svelte sources after build\nfunction copySvelteSources() {\n  return {\n    name: \"copy-svelte-sources\",\n    closeBundle() {\n      const srcDir = path.resolve(__dirname, \"src/lib\");\n      const outDir = path.resolve(__dirname, \"dist\");\n      fs.copySync(srcDir, outDir, {\n        filter: (src) => src.endsWith(\".svelte\") || fs.statSync(src).isDirectory()\n      });\n      console.log(\"✅ Copied .svelte files to dist\");\n    }\n  };\n}\n\nexport default defineConfig({\n  plugins: [\n    tsconfigPaths(),\n    svelte({\n      preprocess: [sveltePreprocess()]\n    }),\n    dts({\n      outDir: \"dist\",\n      tsconfigPath: \"./tsconfig.build.json\"\n    }),\n    copySvelteSources() // <-- our custom plugin\n  ],\n  build: {\n    reportCompressedSize: true,\n    lib: {\n      name: \"@mateothegreat/svelte5-router\",\n      formats: [\"es\", \"cjs\"],\n      entry: path.resolve(__dirname, \"src/lib/index.ts\"),\n      fileName: (format) => `router.${format}.js`\n    },\n    rollupOptions: {\n      external: [\"svelte\"],\n      output: {\n        globals: { svelte: \"Svelte\" }\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      reporter: [\"json-summary\"],\n      reportsDirectory: \"tmp/coverage\"\n    }\n  }\n});\n"
  },
  {
    "path": "vitest.setup.ts",
    "content": "import '@testing-library/jest-dom/vitest';\n"
  }
]