Full Code of mateothegreat/svelte5-router for AI

main 371538eb962e cached
159 files
815.7 KB
222.3k tokens
104 symbols
1 requests
Download .txt
Showing preview only (870K chars total). Download the full file or copy to clipboard to get everything.
Repository: mateothegreat/svelte5-router
Branch: main
Commit: 371538eb962e
Files: 159
Total size: 815.7 KB

Directory structure:
gitextract_rt2wkk2r/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── demo.yaml
│       ├── docs.yaml
│       ├── release.yaml
│       ├── setup.yaml
│       └── test.yaml
├── LICENSE
├── demo/
│   ├── cypress/
│   │   ├── e2e/
│   │   │   └── route-activation.cy.ts
│   │   ├── fixtures/
│   │   │   └── routes.json
│   │   └── support/
│   │       ├── commands.ts
│   │       └── e2e.ts
│   ├── cypress.config.ts
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── app.css
│   │   ├── app.svelte
│   │   ├── lib/
│   │   │   ├── components/
│   │   │   │   ├── badge.svelte
│   │   │   │   ├── code.svelte
│   │   │   │   ├── container.svelte
│   │   │   │   ├── default.svelte
│   │   │   │   ├── file-link.svelte
│   │   │   │   ├── inline-code.svelte
│   │   │   │   └── routes/
│   │   │   │       ├── route-link.svelte
│   │   │   │       ├── route-title.svelte
│   │   │   │       └── route-wrapper.svelte
│   │   │   ├── default-route-config.ts
│   │   │   ├── router-history.ts
│   │   │   └── session.svelte.ts
│   │   ├── main.ts
│   │   ├── routes/
│   │   │   ├── delayed.svelte
│   │   │   ├── extras/
│   │   │   │   ├── dump.svelte
│   │   │   │   ├── extras.svelte
│   │   │   │   └── passing-down-props.svelte
│   │   │   ├── hash/
│   │   │   │   └── hash.svelte
│   │   │   ├── home.svelte
│   │   │   ├── nested/
│   │   │   │   ├── level-1/
│   │   │   │   │   ├── level-1.svelte
│   │   │   │   │   └── level-2/
│   │   │   │   │       ├── level-2.svelte
│   │   │   │   │       └── level-3/
│   │   │   │   │           └── level-3.svelte
│   │   │   │   └── nested.svelte
│   │   │   ├── not-found.svelte
│   │   │   ├── paths-and-params/
│   │   │   │   ├── custom-not-found.svelte
│   │   │   │   ├── display-params.svelte
│   │   │   │   ├── paths-and-params.svelte
│   │   │   │   └── querystring-matching.svelte
│   │   │   ├── patterns/
│   │   │   │   ├── dump.svelte
│   │   │   │   ├── output.svelte
│   │   │   │   ├── parameter-extraction.svelte
│   │   │   │   └── patterns.svelte
│   │   │   ├── protected/
│   │   │   │   ├── account-state.svelte.ts
│   │   │   │   ├── denied.svelte
│   │   │   │   ├── login.svelte
│   │   │   │   ├── main.svelte
│   │   │   │   └── manage-account/
│   │   │   │       ├── auth-guard-fast.ts
│   │   │   │       ├── auth-guard-slow.ts
│   │   │   │       ├── balance.svelte
│   │   │   │       ├── home.svelte
│   │   │   │       ├── manage-account.svelte
│   │   │   │       └── worker-client.svelte.ts
│   │   │   └── transitions/
│   │   │       ├── fade.svelte
│   │   │       ├── slide.svelte
│   │   │       └── transitions.svelte
│   │   └── vite-env.d.ts
│   ├── svelte.config.js
│   ├── tailwind.config.ts
│   ├── tsconfig.deployed.json
│   ├── tsconfig.json
│   ├── vercel.json
│   └── vite.config.ts
├── docs/
│   ├── CNAME
│   ├── actions.md
│   ├── assets/
│   │   └── coverage.json
│   ├── changelog.md
│   ├── cliff.toml
│   ├── debugging.md
│   ├── diagrams/
│   │   ├── component-hierarchy.mmd
│   │   ├── route-evaluations.mmd
│   │   ├── router-architecture.mmd
│   │   └── routing-lifecycle.mmd
│   ├── diagrams.md
│   ├── getting-started.md
│   ├── helpers.md
│   ├── hooks.md
│   ├── llms.txt
│   ├── makefile
│   ├── package.json
│   ├── props.md
│   ├── puppeteer.config.cjs
│   ├── readme.md
│   ├── registry.md
│   ├── routing-patterns.md
│   ├── routing.md
│   ├── statuses.md
│   ├── styling.md
│   ├── tsconfig.json
│   └── typedoc.json
├── llms.txt
├── makefile
├── package.json
├── src/
│   ├── lib/
│   │   ├── actions/
│   │   │   ├── active.svelte.ts
│   │   │   ├── apply-classes.ts
│   │   │   ├── index.ts
│   │   │   ├── options.ts
│   │   │   └── route.svelte.ts
│   │   ├── hash.test.ts
│   │   ├── hash.ts
│   │   ├── helpers/
│   │   │   ├── evaluators.test.ts
│   │   │   ├── evaluators.ts
│   │   │   ├── goto.ts
│   │   │   ├── identify.ts
│   │   │   ├── index.ts
│   │   │   ├── logging.ts
│   │   │   ├── marshal.test.ts
│   │   │   ├── marshal.ts
│   │   │   ├── normalize.ts
│   │   │   ├── objects.ts
│   │   │   ├── pop.ts
│   │   │   ├── query.ts
│   │   │   ├── regexp.ts
│   │   │   ├── replace.ts
│   │   │   ├── runtime.ts
│   │   │   ├── tracing.svelte.ts
│   │   │   ├── urls.test.ts
│   │   │   └── urls.ts
│   │   ├── hooks.ts
│   │   ├── index.ts
│   │   ├── path.ts
│   │   ├── query.svelte.ts
│   │   ├── query.test.ts
│   │   ├── registry.svelte.ts
│   │   ├── route.svelte.ts
│   │   ├── router-instance-config.ts
│   │   ├── router-instance.svelte.ts
│   │   ├── router-integration.test.ts
│   │   ├── router-patterns-demo.test.ts
│   │   ├── router-remount.test.ts
│   │   ├── router.svelte
│   │   ├── statuses.ts
│   │   └── utilities.svelte.ts
│   └── vite-env.d.ts
├── svelte.config.js
├── test/
│   └── app/
│       ├── LICENSE
│       ├── index.html
│       ├── package.json
│       ├── readme.md
│       ├── src/
│       │   ├── app.css
│       │   ├── app.svelte
│       │   ├── main.ts
│       │   └── routes/
│       │       ├── test-a/
│       │       │   └── test-a.svelte
│       │       └── test-b/
│       │           └── test-b.svelte
│       ├── svelte.config.ts
│       ├── tsconfig.json
│       └── vite.config.ts
├── tsconfig.build.json
├── tsconfig.json
├── vite.config.ts
├── vitest.config.ts
└── vitest.setup.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: mateothegreat

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Logs**
If available please provide any available logs, screenshots, etc.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: mateothegreat

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
  - package-ecosystem: "npm" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: "daily"


================================================
FILE: .github/workflows/demo.yaml
================================================
name: 📱 Demo
on:
  workflow_dispatch:
  workflow_call:
    secrets:
      VERCEL_TOKEN:
        required: true
concurrency:
  group: demo
  cancel-in-progress: true
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: install vercel
        run: npm install --global vercel@latest
      - name: vercel pull
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: vercel build
        run: vercel build --local-config demo/vercel.json --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: vercel deploy
        run: vercel deploy --local-config demo/vercel.json --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}


================================================
FILE: .github/workflows/docs.yaml
================================================
name: 📚 Docs
on:
  workflow_dispatch:
  workflow_call:
permissions:
  id-token: write
  pages: write
defaults:
  run:
    shell: bash
    working-directory: ./docs
jobs:
  setup:
    name: 🔧 Request
    uses: ./.github/workflows/setup.yaml
  build:
    name: "📚 Build Docs"
    needs: setup
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      # [setup] checkout the repo.
      - uses: actions/checkout@v5.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
      # [setup] setup node.js.
      - name: Setup Node.js
        uses: actions/setup-node@v4.4.0
        with:
          node-version: 20.x
      - name: 📊 Generate diagrams
        run: make diagrams
      - name: 📚 Build documentation
        run: npm run docs:build
      - name: 📤 Upload Pages artifact
        uses: actions/upload-pages-artifact@v3.0.1
        with:
          path: tmp/build
      - id: deployment
        name: 📤 Deploy documentation to GitHub Pages
        uses: actions/deploy-pages@v4.0.5
      # [setup] authenticate with the cicd app to get proper credentials for pushing to the repo later.
      - name: cicd app auth
        id: app
        uses: actions/create-github-app-token@v2.1.1
        with:
          app-id: ${{ secrets.CICD_APP_ID }}
          private-key: ${{ secrets.CICD_APP_PRIVATE_KEY }}
      # [setup] configure git for later changelog generation, commit, and the finalpush.
      - name: 🔧 Commit and push diagrams
        run: |
          git config --global user.name "mateothegreat[bot]"
          git config --global user.email "mateothegreat[bot]@users.noreply.github.com"
          git remote set-url origin https://x-access-token:${{ steps.app.outputs.token }}@github.com/${{ github.repository }}.git
          git add .
          git commit -am "chore(docs): update diagrams"
          git push origin HEAD:main


================================================
FILE: .github/workflows/release.yaml
================================================
name: 🚀 Release
run-name: 🚀 Release (${{ github.event.inputs.label }})
on:
  workflow_dispatch:
    inputs:
      label:
        description: "The label to add to the release"
        required: false
        default: "standard release"
        type: string
      build_docs:
        description: "Release the docs?"
        required: false
        default: false
        type: boolean
      build_demo:
        description: "Release the demo?"
        required: false
        default: false
        type: boolean
concurrency:
  group: release
  cancel-in-progress: true
jobs:
  release:
    name: 🚀 Release ${{ github.event.inputs.label }}
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write
    steps:
      # [setup] authenticate with the cicd app to get proper credentials for pushing to the repo later.
      - name: cicd app auth
        id: app
        uses: actions/create-github-app-token@v2.1.1
        with:
          app-id: ${{ secrets.CICD_APP_ID }}
          private-key: ${{ secrets.CICD_APP_PRIVATE_KEY }}
      # [setup] checkout the repo.
      - uses: actions/checkout@v5.0.0
        with:
          fetch-depth: 0
          persist-credentials: false
      # [setup] setup node.js.
      - name: Setup Node.js
        uses: actions/setup-node@v4.4.0
        with:
          node-version: 20.x
      # [setup] configure git for later changelog generation, commit, and the finalpush.
      - name: configure git
        run: |
          git config --global user.name "mateothegreat[bot]"
          git config --global user.email "mateothegreat[bot]@users.noreply.github.com"
          git remote set-url origin https://x-access-token:${{ steps.app.outputs.token }}@github.com/${{ github.repository }}.git
      # [build] install dependencies first.
      - name: install
        run: npm install
      # [build] build the package which outputs to the dist/ directory.
      - name: build
        run: npm run build
      # [version] bump the version
      - name: npm version
        id: version
        run: |
          version=$(npm version patch --no-git-tag-version --json | sed 's/^v//' || {
            echo "npm version patch failed, git is dirty"
            exit 1
          })
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "version is now: $version"
      # [version] generate the new changelog.
      - name: generate changelog
        uses: orhun/git-cliff-action@98c93442bb05a455a77bee982867857ae748eeea
        id: git-cliff
        with:
          config: ./docs/cliff.toml
          args: --tag ${{ steps.version.outputs.version }}
        env:
          OUTPUT: docs/changelog.md
      # [version] commit the changelog and package.json.
      - name: commit
        run: |
          git add docs/changelog.md package.json
          git commit -am "chore(release): update of changelog and bumping package.json for ${{ steps.version.outputs.version }}"
          git push origin HEAD:main
          git tag -a ${{ steps.version.outputs.version }} -m "release: ${{ steps.version.outputs.version }}"
          git push origin ${{ steps.version.outputs.version }}
      # [publish] prepare to publish by setting up credentials.
      - name: write to .npmrc
        run: echo "//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}" > ~/.npmrc
      # [publish] release publicly only if the build was successful above.
      - name: publish
        working-directory: dist
        run: |
          cp ../package.json .
          npm publish --access public
      # [publish] create a github release.
      - name: create github release
        uses: softprops/action-gh-release@v2.3.2
        with:
          body: ${{ steps.git-cliff.outputs.content }}
          tag_name: ${{ steps.version.outputs.version }}
  docs:
    if: ${{ github.event.inputs.build_docs == true }}
    needs: release
    permissions:
      id-token: write
      pages: write
    uses: ./.github/workflows/docs.yaml
  demo:
    if: ${{ github.event.inputs.build_demo == true }}
    needs: release
    uses: ./.github/workflows/demo.yaml
    secrets: inherit
  notify:
    if: always()
    continue-on-error: true
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: Ilshidur/action-discord@ad5235de713df3ef38022185499b02ffe93b7efe
        with:
          status: ${{ needs.build.result }}
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}


================================================
FILE: .github/workflows/setup.yaml
================================================
name: 🔧 Setup
on:
  workflow_call:
jobs:
  node:
    name: "Runtime"  
    environment:
      name: dev
    runs-on: ubuntu-latest
    steps:
      - name: ⬇️ Check out repo
        uses: actions/checkout@v4
      - name: ⎔ Setup node
        uses: actions/setup-node@v4
        with:
          node-version-file: "package.json"
          cache: "npm"
      - name: 📦 Cache node_modules
        uses: actions/cache@v3
        id: cache-node-modules
        with:
          path: node_modules
          key: ${{ hashFiles('package-lock.json') }}          
      - name: 📦 Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: |
          npm ci --legacy-peer-deps          


================================================
FILE: .github/workflows/test.yaml
================================================
name: ⚡ Test Runner
on:
  workflow_dispatch:
  workflow_call:
permissions:
  contents: write
jobs:
  setup:
    name: "🔧 Setup"
    environment:
      name: dev
    runs-on: ubuntu-latest
    steps:
      - name: ⬇️ Check out repo
        uses: actions/checkout@v4
      - name: ⎔ Setup node
        uses: actions/setup-node@v4
        with:
          node-version-file: "package.json"
          cache: "npm"
          # cache-dependency-path: "package-lock.json"
      - name: 🧹 Clean install for cross-platform compatibility
        run: |
          npm ci --legacy-peer-deps
  test:
    name: 🏃 Test
    needs: setup
    environment:
      name: dev
    runs-on: ubuntu-latest
    steps:
      - name: ⬇️ Check out repo
        uses: actions/checkout@v4
      - name: ⎔ Setup node (reuse cache from setup)
        uses: actions/setup-node@v4
        with:
          node-version-file: "package.json"
          cache: "npm"
      - name: Hydrate npm module cache
        uses: actions/cache@v3
        id: hydrate
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
      - name: Install dependencies
        if: steps.hydrate.outputs.cache-hit != 'true'
        run: npm ci --legacy-peer-deps
      - name: 🧪 Run Tests
        run: npm run test:ci
      - name: ⚙️ Generating coverage badges
        run: |
          npx --yes coverage-badges-cli \
              --source tmp/coverage/coverage-summary.json \
              --style plastic \
              --type statements \
              --iconWidth 190 \
              --label "Test Coverage"  \
              --output docs/assets/coverage-badge.svg
      - name: ⬆️ Push badges branch
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"
          git add docs/assets/coverage-badge.svg
          git commit -m "chore: update coverage badge"
          git push


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Matthew Davis <matthew@matthewdavis.io>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: demo/cypress/e2e/route-activation.cy.ts
================================================
/// <reference types="cypress" />

const routes = require("../fixtures/routes.json");
describe.only("route activation", () => {
  beforeEach(() => {
    cy.viewport(1500, 1500);
    cy.visit("http://localhost:8173");
  });

  routes.forEach((route) => {
    it(`should activate ${route.id} when visiting ${route.path}`, () => {
      cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, "active", route.active);
    });
  });

  // it.only("displays two todo items by default", () => {
  //   // cy.get("a[href='/props']").should("have.length", 1).click();
  //   // cy.contains("props.svelte").should("exist");

  //   // cy.get("a[href='/props/foo']").should("have.length", 1).click();
  //   // cy.contains("display-params.svelte").should("exist");
  //   // cy.contains(`"child": "foo"`).should("exist");

  //   // cy.get("a").filter('[href^="/props/bar?"]').should("have.length", 1).click();
  //   // cy.contains("display-params.svelte").should("exist");
  //   // cy.contains(`"child": "bar"`).should("exist");

  //   // cy.get("a").filter('[href="/props/foo"]').should("not.have.class", "active");
  //   // cy.get("a").filter('[href^="/props/bar"]').should("have.class", "active");

  //   cy.clickAndValidateActiveClasses("a[href='/props']", "active");
  // });

  // it("can add new todo items", () => {
  //   // We'll store our item text in a variable so we can reuse it
  //   const newItem = "Feed the cat";

  //   // Let's get the input element and use the `type` command to
  //   // input our new list item. After typing the content of our item,
  //   // we need to type the enter key as well in order to submit the input.
  //   // This input has a data-test attribute so we'll use that to select the
  //   // element in accordance with best practices:
  //   // https://on.cypress.io/selecting-elements
  //   cy.get("[data-test=new-todo]").type(`${newItem}{enter}`);

  //   // Now that we've typed our new item, let's check that it actually was added to the list.
  //   // Since it's the newest item, it should exist as the last element in the list.
  //   // In addition, with the two default items, we should have a total of 3 elements in the list.
  //   // Since assertions yield the element that was asserted on,
  //   // we can chain both of these assertions together into a single statement.
  //   cy.get(".todo-list li")
  //     .should("have.length", 3)
  //     .last()
  //     .should("have.text", newItem);
  // });

  // it("can check off an item as completed", () => {
  //   // In addition to using the `get` command to get an element by selector,
  //   // we can also use the `contains` command to get an element by its contents.
  //   // However, this will yield the <label>, which is lowest-level element that contains the text.
  //   // In order to check the item, we'll find the <input> element for this <label>
  //   // by traversing up the dom to the parent element. From there, we can `find`
  //   // the child checkbox <input> element and use the `check` command to check it.
  //   cy.contains("Pay electric bill")
  //     .parent()
  //     .find("input[type=checkbox]")
  //     .check();

  //   // Now that we've checked the button, we can go ahead and make sure
  //   // that the list element is now marked as completed.
  //   // Again we'll use `contains` to find the <label> element and then use the `parents` command
  //   // to traverse multiple levels up the dom until we find the corresponding <li> element.
  //   // Once we get that element, we can assert that it has the completed class.
  //   cy.contains("Pay electric bill")
  //     .parents("li")
  //     .should("have.class", "completed");
  // });

  // context("with a checked task", () => {
  //   beforeEach(() => {
  //     // We'll take the command we used above to check off an element
  //     // Since we want to perform multiple tests that start with checking
  //     // one element, we put it in the beforeEach hook
  //     // so that it runs at the start of every test.
  //     cy.contains("Pay electric bill")
  //       .parent()
  //       .find("input[type=checkbox]")
  //       .check();
  //   });

  //   it("can filter for uncompleted tasks", () => {
  //     // We'll click on the "active" button in order to
  //     // display only incomplete items
  //     cy.contains("Active").click();

  //     // After filtering, we can assert that there is only the one
  //     // incomplete item in the list.
  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .first()
  //       .should("have.text", "Walk the dog");

  //     // For good measure, let's also assert that the task we checked off
  //     // does not exist on the page.
  //     cy.contains("Pay electric bill").should("not.exist");
  //   });

  //   it("can filter for completed tasks", () => {
  //     // We can perform similar steps as the test above to ensure
  //     // that only completed tasks are shown
  //     cy.contains("Completed").click();

  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .first()
  //       .should("have.text", "Pay electric bill");

  //     cy.contains("Walk the dog").should("not.exist");
  //   });

  //   it("can delete all completed tasks", () => {
  //     // First, let's click the "Clear completed" button
  //     // `contains` is actually serving two purposes here.
  //     // First, it's ensuring that the button exists within the dom.
  //     // This button only appears when at least one task is checked
  //     // so this command is implicitly verifying that it does exist.
  //     // Second, it selects the button so we can click it.
  //     cy.contains("Clear completed").click();

  //     // Then we can make sure that there is only one element
  //     // in the list and our element does not exist
  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .should("not.have.text", "Pay electric bill");

  //     // Finally, make sure that the clear button no longer exists.
  //     cy.contains("Clear completed").should("not.exist");
  //   });
  // });
});


================================================
FILE: demo/cypress/fixtures/routes.json
================================================
[
  {
    "path": "/home",
    "id": "home.svelte",
    "active": 2
  },
  {
    "path": "/props",
    "id": "props.svelte",
    "active": 1,
    "children": [
      {
        "path": "/foo",
        "id": "display-params.svelte",
        "active": 1
      },
      {
        "path": "/bar?someQueryParam=123",
        "id": "display-params.svelte",
        "active": 1
      }
    ]
  },
  {
    "path": "/nested",
    "id": "nested.svelte",
    "active": 1,
    "children": [
      {
        "path": "/foo",
        "id": "display-params.svelte",
        "active": 1
      }
    ]
  },
  {
    "path": "/async",
    "id": "async.svelte",
    "active": 1
  },
  {
    "path": "/delayed",
    "id": "delayed.svelte",
    "active": 1
  }
]


================================================
FILE: demo/cypress/support/commands.ts
================================================
/// <reference types="cypress" />


================================================
FILE: demo/cypress/support/e2e.ts
================================================
import "./commands";

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Custom command to check if navigation link is active based on href.
       *
       * @param selector - The selector to check against
       * @param attribute - The attribute to check against
       * @param value - The value to check against
       *
       * @example cy.hasClassByAttr('nav a', 'href', '/about', 'foo')
       */
      allowedClassesByAttr(selector: string, attribute: string, allowed: string | string[], className: string): Chainable<Element>;

      /**
       * Custom command to navigate to a specific path and validate the number of active elements.
       *
       * @param selector - The selector to check against
       * @param path - The path to navigate to
       * @param expected - The expected number of active elements
       *
       * @example cy.clickAndValidateActiveClasses("a[href='/props/foo']", "active", 1)
       */
      clickAndValidateActiveClasses(selector: string, path: string, expected: number): Chainable<Element>;
    }
  }
}

Cypress.Commands.add("allowedClassesByAttr", (selector: string, attribute: string, allowed: string | string[], className: string) => {
  cy.get(selector).then(($elements) => {
    $elements.each((_, $el) => {
      cy.wrap($el).then(($el) => {
        if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {
          if (Array.isArray(allowed) && !allowed.includes($el.attr(attribute) || "")) {
            throw new Error(`allowedClassesByAttr: ${attribute}="${$el.attr(attribute)}" has class "${className}" (allowed: "${allowed}")`);
          } else if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {
            throw new Error(`allowedClassesByAttr: ${attribute}="${$el.attr(attribute)}" has class "${className}" (allowed: "${allowed}")`);
          }
        }
      });
    });
  });
});

Cypress.Commands.add("clickAndValidateActiveClasses", (selector: string, className: string, expected: number) => {
  cy.get(selector).then(($el) => {
    if ($el.length !== 1) {
      throw new Error(`clickAndValidateActiveClasses: ${selector} should match only 1 element, has ${$el.length}`);
    }
    cy.get(selector).click();
    cy.allowedClassesByAttr("nav a", "href", $el.attr("href") || "", className);
  });
});


================================================
FILE: demo/cypress.config.ts
================================================
import { defineConfig } from "cypress";

export default defineConfig({
  component: {
    devServer: {
      framework: "svelte",
      bundler: "vite",
      viteConfig: {
        server: {
          port: 8173,
        },
      },
    },
  },
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },
});


================================================
FILE: demo/index.html
================================================
<!doctype html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<link rel="icon" type="image/svg+xml" href="/vite.svg" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Svelte5 Router</title>
	</head>
	<body class="dark">
		<div id="app"></div>
		<script type="module" src="/src/main.ts"></script>
	</body>
</html>


================================================
FILE: demo/package.json
================================================
{
  "name": "test-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite --port 5174 --host",
    "build": "vite build",
    "preview": "vite preview",
    "check": "svelte-check --tsconfig ./tsconfig.json",
    "test:start": "vite --port 8173",
    "test:cy:open-e2e": "cypress open --e2e --browser chrome",
    "test:cy:open-unit": "cypress open --component --browser chrome",
    "test:cy:run-e2e": "cypress run --e2e --no-runner-ui",
    "test:cy:run-unit": "cypress run --component",
    "test:cy:e2e": "start-server-and-test test:start http-get://localhost:8173 test:cy:open-e2e"
  },
  "devDependencies": {
    "@mateothegreat/svelte5-table": "^1.0.29",
    "@shikijs/colorized-brackets": "^3.12.0",
    "@sveltejs/vite-plugin-svelte": "^6.1.3",
    "@sveltejs/vite-plugin-svelte-inspector": "^5.0.1",
    "@tailwindcss/oxide": "^4.1.12",
    "@tailwindcss/postcss": "^4.1.12",
    "@tailwindcss/vite": "^4.1.12",
    "@tsconfig/svelte": "^5.0.5",
    "cypress": "^15.0.0",
    "lucide-svelte": "^0.542.0",
    "prettier-plugin-svelte": "^3.4.0",
    "prettier-plugin-tailwindcss": "^0.6.14",
    "shiki": "^3.12.0",
    "svelte": "^5.38.6",
    "svelte-check": "^4.3.1",
    "svelte-highlight": "^7.8.3",
    "tailwindcss": "^4.1.12",
    "tslib": "^2.8.1",
    "typescript": "^5.9.2",
    "vite": "^7.1.3",
    "vite-plugin-version-mark": "^0.2.2",
    "vite-tsconfig-paths": "^5.1.4"
  }
}


================================================
FILE: demo/src/app.css
================================================
@import "tailwindcss";
@config '../tailwind.config.ts';

/*
  The default border color has changed to `currentColor` in Tailwind CSS v4,
  so we've added these compatibility styles to make sure everything still
  looks the same as it did with Tailwind CSS v3.

  If we ever want to remove these styles, we need to add an explicit border
  color utility to any element that depends on these defaults.
*/
@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentColor);
  }
}

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 72.2% 50.6%;
    --destructive-foreground: 210 40% 98%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }

  .dark {
    --background: 0 0% 0%;
    --foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --ring: 212.7 26.8% 83.9%;
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 224.3 76.3% 48%;
    --sidebar-primary-foreground: 0 0% 100%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}


================================================
FILE: demo/src/app.svelte
================================================
<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { session } from "$lib/session.svelte";
  import Extras from "$routes/extras/extras.svelte";
  import Home from "$routes/home.svelte";
  import Nested from "$routes/nested/nested.svelte";
  import PathsAndParams from "$routes/paths-and-params/paths-and-params.svelte";
  import Patterns from "$routes/patterns/patterns.svelte";
  import Protected from "$routes/protected/main.svelte";
  import Transitions from "$routes/transitions/transitions.svelte";
  import type { RouteConfig, RouteResult } from "@mateothegreat/svelte5-router";
  import { goto, logging, registry, type Route, Router, type RouterInstance } from "@mateothegreat/svelte5-router";
  import { BookHeart, Github, HelpCircle, MousePointerClick } from "lucide-svelte";

  /**
   * Only needed for the demo environment development.
   *
   * It is not needed for including the router package in your project.
   */
  if (import.meta.hot) {
    import.meta.hot.accept(() => {
      import.meta.hot!.invalidate();
    });
  }

  /**
   * This is a state variable that will hold the router instance.
   *
   * It can be used to access the current route, navigate, etc:
   */
  let router: RouterInstance = $state();

  /**
   * Get notified when the current route changes:
   */
  const route = $derived(router.current);
  $effect(() => {
    if (router.current) {
      logging.info(
        `🚀 I'm an $effect in app.svelte and i'm running because the current route is now ${router.current.result.path.original}!`
      );
    }
  });

  /**
   * Let's declare our routes for the main app router:
   */
  const routes: RouteConfig[] = [
    {
      // You can name your routes anything you want for tracking or debugging:
      name: "default-route",
      hooks: {
        pre: async (route: RouteResult) => {
          console.error(`redirecting to ${session.mode === "hash" ? "/#" : ""}/home using a pre hook!`, route);
          goto(`${session.mode === "hash" ? "/#" : ""}/home`);
        },
        post: async (route: RouteResult) => {
          console.error(`post hook fired for route`, route);
        }
      }
    },
    {
      path: "/patterns",
      component: Patterns
    },
    {
      // Here we use a regex to match the home route.
      // This is useful if you want to match a route that has a dynamic path.
      // The "?:" is used to group the regex without capturing the match:
      // path: /^\/($|home)$/,
      path: "home",
      component: Home,
      // Use hooks to perform actions before and after the route is resolved:
      hooks: {
        pre: async (route: Route): Promise<boolean> => {
          // console.log("pre hook #1 fired for route");
          return true; // Return true to continue down the route evaluation path.
        },
        // Hooks can also be an array of functions (async too):
        post: [
          // This is a post hook that will be executed after the route is resolved:
          (route: Route): boolean => {
            // console.log("post hook #1 fired for route");
            return true; // Return true to continue down the route evaluation path.
          },
          // This is an async post hook that will be executed after the route is resolved:
          async (route: Route): Promise<boolean> => {
            // console.log("post hook #2 (async) fired for route");
            return true; // Return true to continue down the route evaluation path.
          }
        ]
      }
    },
    {
      path: "nested",
      component: Nested
    },
    {
      path: "paths-and-params",
      component: PathsAndParams
    },
    {
      path: "protected",
      component: Protected
    },
    {
      path: "transitions",
      component: Transitions
    },
    {
      path: "extras",
      component: Extras
    }
  ];

  // This is a global pre hook that can be applied to all routes.
  // Here you could check if the user is logged in or perform some other
  // authentication checks:
  const globalAuthGuardHook = async (route: Route): Promise<boolean> => {
    console.warn("globalAuthGuardHook", route);
    // Return true so that the route can continue down its evaluation path.
    return true;
  };
</script>

<div class="flex h-screen flex-col gap-4 bg-zinc-700/25 p-4">
  <div class="flex items-start">
    <div class="flex flex-col gap-4">
      <a
        href="https://github.com/mateothegreat/svelte5-router"
        class="text-slate-400 hover:text-green-500"
        target="_blank">
        <Github class="h-6 w-6" />
      </a>
      <a
        href="https://github.com/mateothegreat/svelte5-router"
        class="text-slate-400 hover:text-green-500"
        target="_blank">
        <BookHeart class="h-6 w-6 text-fuchsia-500" />
      </a>
    </div>
    <div class="logo h-51 w-60">
      <img
        src="https://github.com/mateothegreat/svelte5-router/raw/dev/docs/assets/logo.png"
        alt="logo" />
    </div>
    <div class="m-4 flex-1 flex justify-end text-indigo-400 gap-2 text-xs">
      <div class="text-slate-500 text-sm mb-3.5 rounded-md border-2 bg-black px-2 py-1.5">
        demo version: <a
          href="https://github.com/mateothegreat/svelte5-router/tree/{window.__SVELTE5_ROUTER_VERSION__}"
          class="text-emerald-500 hover:text-blue-400 cursor-pointer"
          target="_blank">
          {window.__SVELTE5_ROUTER_VERSION__}
        </a>
      </div>
    </div>
  </div>
  <div class="flex-1 gap-4 flex flex-col">
    <div
      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">
      <MousePointerClick class="h-4 w-4 text-slate-500" />
      change url mode:
      <button
        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"
        class:text-orange-400={session.mode === "hash"}
        class:text-green-400={session.mode === "path"}
        onclick={() => {
          session.mode = session.mode === "hash" ? "path" : "hash";
        }}>
        {session.mode === "hash" ? "path" : "hash"}
      </button>
    </div>
    <RouteWrapper
      name="main app router"
      title={{
        file: "src/app.svelte",
        content:
          "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! 🚀"
      }}
      {router}
      {route}
      links={[
        {
          href: "/",
          label: "/",
          options: {
            active: {
              absolute: true
            }
          }
        },
        {
          href: "/home",
          label: "/home"
        },
        {
          href: "/patterns",
          label: "/patterns"
        },
        {
          href: "/protected",
          label: "/protected"
        },
        {
          href: "/paths-and-params",
          label: "/paths-and-params"
        },
        {
          href: "/nested",
          label: "/nested"
        },
        {
          href: "/transitions",
          label: "/transitions"
        },
        {
          href: "/404",
          label: "/404"
        },
        {
          href: "/extras",
          label: "/extras"
        }
      ]}>
      <div class="flex-1">
        <Router
          id="my-main-router"
          bind:instance={router}
          {routes}
          {...myDefaultRouterConfig} />
      </div>
    </RouteWrapper>
  </div>
</div>
<div
  class="fixed bottom-0 right-10 overflow-hidden rounded-t-md border-2 border-b-0 bg-neutral-950 text-xs text-gray-400">
  <p class="flex items-center gap-1.5 bg-black/80 p-2.5 text-sm font-medium text-slate-400">
    <a
      href="https://github.com/mateothegreat/svelte5-router/blob/main/docs/registry.md"
      class="text-yellow-300/70 hover:text-pink-500"
      target="_blank">
      <HelpCircle class="h-5 w-5" />
    </a>
    router registry
  </p>
  <table class="divide-y divide-gray-900 overflow-hidden rounded-md border-2 text-xs text-gray-400">
    <thead>
      <tr class="text-center tracking-wider text-slate-500">
        <th class="px-3 py-2 font-medium">Router Name</th>
        <th class="px-3 py-2 font-medium">Routes</th>
        <th class="px-3 py-2 font-medium">State</th>
        <th class="px-3 py-2 font-medium">Current Path</th>
      </tr>
    </thead>
    <tbody class="divide-y divide-gray-800 font-mono">
      {#each registry.instances.entries() as [key, instance]}
        <tr>
          <td class="px-3 py-2 text-left text-indigo-400">
            {key}
          </td>
          <td class="px-3 py-2 text-pink-500">
            {instance.routes.size}
          </td>
          <td class="px-3 py-2">
            {#if instance.navigating}
              <span class="text-green-500">busy</span>
            {:else}
              <span class="text-gray-500">idle</span>
            {/if}
          </td>
          <td class="px-3 py-2 text-green-500">
            {instance.current?.path || "<default>"}
          </td>
        </tr>
      {/each}
    </tbody>
  </table>
</div>


================================================
FILE: demo/src/lib/components/badge.svelte
================================================
<script lang="ts">
  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "lucide-svelte";
  import type { Snippet } from "svelte";

  let {
    icon,
    text,
    variant = "info",
    class: className,
    children
  }: {
    icon?: any;
    text?: string;
    variant?: "info" | "success" | "warning" | "error";
    class?: string;
    children?: Snippet;
  } = $props();

  const variants = {
    info: {
      classes: "bg-blue-900 text-white",
      icon: InfoIcon
    },
    success: {
      classes: "bg-green-600/80 text-white",
      icon: CheckCircleIcon
    },
    warning: {
      classes: "bg-yellow-200/80 text-black",
      icon: AlertTriangleIcon
    },
    error: {
      classes: "bg-red-900/80 text-white",
      icon: XCircleIcon
    }
  };

  let Icon = icon ?? variants[variant].icon;
</script>

<span
  class="inline-flex items-center gap-1.5 rounded-sm p-2 text-sm font-medium {variants[variant].classes} {className}">
  {#if Icon}
    <Icon class="inline-block h-5 w-5" />
  {/if}
  {text}
  {@render children?.()}
</span>


================================================
FILE: demo/src/lib/components/code.svelte
================================================
<script lang="ts">
  import { transformerColorizedBrackets } from "@shikijs/colorized-brackets";
  import { Code2 } from "lucide-svelte";
  import { createHighlighter, type Highlighter } from "shiki";
  import { onMount, type Snippet } from "svelte";
  import FileLink from "./file-link.svelte";

  let {
    title,
    file,
    class: className,
    children,
    language = "typescript",
    theme = "github-dark-dimmed"
  }: {
    title?: string;
    file?: string;
    class?: string;
    children?: Snippet;
    language?: string;
    theme?: string;
  } = $props();

  // State for the highlighted HTML content
  let highlightedContent = $state<string>("");
  // State for the reference to the hidden temporary element
  let tempElement: HTMLElement | undefined = $state(undefined);
  // State for the Shiki highlighter instance
  let shikiHighlighter: Highlighter | null = $state(null);

  /**
   * Initializes the Shiki highlighter instance once when the component mounts.
   */
  onMount(async () => {
    shikiHighlighter = await createHighlighter({
      themes: [theme],
      langs: ["typescript", "svelte", "html", "css", "json"]
    });
  });

  /**
   * Reactive effect that updates the syntax highlighting whenever
   * the children, language, or highlighter instance changes.
   */
  $effect(() => {
    if (!shikiHighlighter || !children || !tempElement) {
      if (!children) {
        highlightedContent = "";
      }
      return;
    }

    /**
     * At this point, Svelte has rendered the output of `children()` into `tempElement`
     * due to the `{@render children()}` directive in the hidden div.
     * We can now extract the raw text content from it.
     */
    const codeContent = (tempElement.textContent || "").trim();

    if (codeContent) {
      try {
        // Convert the raw code string to HTML using Shiki.
        highlightedContent = shikiHighlighter.codeToHtml(codeContent, {
          lang: language,
          theme: theme,
          transformers: [transformerColorizedBrackets()]
        });
      } catch (error) {
        console.error("Shiki highlighting error:", error, { language, codeContent });
        // Fallback to showing raw code (escaped) if highlighting fails.
        highlightedContent = `<pre class="shiki-fallback"><code>${escapeHtml(codeContent)}</code></pre>`;
      }
    } else {
      // Clear highlighted content if there's no text content.
      highlightedContent = "";
    }
  });

  /**
   * Helper function to escape HTML special characters.
   * Used for the fallback when Shiki highlighting fails.
   */
  function escapeHtml(unsafe: string): string {
    return unsafe
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }
</script>

<div class="code-block-container flex flex-col rounded-md border-2 border-slate-700 bg-black/80 {className}">
  <div class="header flex items-center justify-between bg-zinc-900/70 text-sm p-1.5 px-3 flex-shrink-0">
    <p class="flex items-center gap-1.5 font-mono text-xs text-slate-300">
      <Code2 class="h-4 w-4 text-orange-500 shrink-0" />
      {#if title}
        <span>{title}</span>
      {:else}
        <span>Code</span>
      {/if}
    </p>
    {#if file}
      <FileLink {file} />
    {/if}
  </div>

  <!--
    Temporary hidden element.
    The `children` snippet is rendered here by Svelte using `{@render children()}`.
    This component's `$effect` then reads `tempElement.textContent` to get the
    raw string representation of the rendered children for Shiki to process.
  -->
  <div
    bind:this={tempElement}
    style="display: none;"
    aria-hidden="true">
    {#if children}
      {@render children()}
    {/if}
  </div>

  {#if highlightedContent}
    <div class="p-2.5 text-[13px] leading-[18px] overflow-auto bg-[#0a0a0a] flex-1 min-h-0">
      {@html highlightedContent}
    </div>
  {:else if children}
    <pre class="fallback-pre p-3 font-mono text-sm text-slate-200 overflow-auto flex-1 min-h-0">
      {@render children()}
    </pre>
  {:else}
    <div class="p-3 text-slate-500 text-sm font-mono">Loading syntax highlighter...</div>
  {/if}
</div>

<style>
  :global(.shiki) {
    background: #090909 !important;
  }
</style>


================================================
FILE: demo/src/lib/components/container.svelte
================================================
<script lang="ts">
  import FileLink from "./file-link.svelte";

  let { title, file, children }: { title?: string; file?: string; children: any } = $props();
</script>

<div class="flex flex-col gap-1 rounded-md border-2 border-slate-700 bg-gray-900 text-slate-400">
  <div class="flex items-center justify-between bg-slate-800 p-2 text-sm text-slate-500">
    <p class="flex items-center gap-2 font-mono">
      {#if title}
        &lt;{title} /&gt;
      {/if}
    </p>
    {#if file}
      <FileLink {file} />
    {/if}
  </div>
  {@render children()}
</div>


================================================
FILE: demo/src/lib/components/default.svelte
================================================
<script lang="ts">
  import { BadgeInfo } from "lucide-svelte";
  import type { Snippet } from "svelte";
  import Badge from "./badge.svelte";

  let { class: className, children }: { class?: string; children?: Snippet } = $props();
</script>

<div class="flex flex-col gap-4 rounded-md border-4 border-slate-900/80 p-4 text-center text-gray-400 {className}">
  {#if children}
    {@render children()}
  {:else}
    <p>
      <Badge
        variant="info"
        icon={BadgeInfo}>
        There was no path provided to the router, so the default route was used (declared as a snippet).
      </Badge>
    </p>
    Click on a link above to see the different effects!
  {/if}
</div>


================================================
FILE: demo/src/lib/components/file-link.svelte
================================================
<script lang="ts">
  import { FileCode2, GithubIcon } from "lucide-svelte";

  let { file } = $props();
</script>

<div class="flex gap-1">
  <div class="flex flex-1 items-center text-slate-500">
    <FileCode2 class="h-4 w-4" />
  </div>
  <a
    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}
    class="flex cursor-pointer items-center gap-1 text-center text-sm text-orange-400 hover:font-medium hover:text-indigo-400">
    {file}
  </a>
  <a
    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}
    target="_blank"
    class="flex cursor-pointer items-center gap-1 text-center text-sm text-slate-500 hover:font-medium hover:text-indigo-400">
    view source
    <GithubIcon class="h-4 w-4" />
  </a>
</div>


================================================
FILE: demo/src/lib/components/inline-code.svelte
================================================
<script lang="ts">
  export type InlineCodeProps = {
    text: string;
    class?: string;
  };

  let { text, class: className }: InlineCodeProps = $props();

  if (!className) {
    className = "text-green-400 bg-black/50";
  }
</script>

<span class="whitespace-nowrap rounded-md p-1 px-2 font-mono text-sm {className}">{text}</span>


================================================
FILE: demo/src/lib/components/routes/route-link.svelte
================================================
<script lang="ts">
  import { session } from "$lib/session.svelte";
  import { route, RouteOptions } from "@mateothegreat/svelte5-router";

  export type RouteLinkProps = {
    options?: RouteOptions;
    href: string;
    label: string;
  };

  let { options, href, label }: RouteLinkProps = $props();
  if (!options) {
    options = new RouteOptions();
  }

  if (!options.active) {
    options.active = {
      class: ["active", "bg-indigo-600", "text-white", "border-indigo-400"]
    };
  }
  if (!options.default) {
    options.default = {
      class: ["inactive", "text-slate-300", "border-slate-500/50"]
    };
  }
  if (!options.loading) {
    options.loading = {
      class: ["loading", "bg-orange-500"]
    };
  }
  if (!options.disabled) {
    options.disabled = {
      class: ["disabled", "bg-gray-500"]
    };
  }

  if (!options.active.class) {
    options.active.class = ["active", "bg-indigo-600", "text-white", "border-indigo-400"];
  }

  if (!options.default.class) {
    options.default.class = ["inactive", "text-slate-300", "border-slate-500/50"];
  }
  if (!options.loading.class) {
    options.loading.class = ["loading", "bg-orange-500"];
  }
  if (!options.disabled.class) {
    options.disabled.class = ["disabled", "bg-gray-500"];
  }
</script>

<a
  use:route={options}
  href={session.mode === "hash" ? `/#${href}` : href}
  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">
  {#if session.mode === "hash"}
    <span>/#</span>
    <span>/{label.startsWith("/") ? label.slice(1) : label}</span>
  {:else}
    <span>
      {label}
    </span>
  {/if}
</a>


================================================
FILE: demo/src/lib/components/routes/route-title.svelte
================================================
<script lang="ts">
  import type { RouteResult, RouterInstance } from "@mateothegreat/svelte5-router";
  import { ArrowDown, ArrowRight, ArrowRightFromLine, StopCircle } from "lucide-svelte";
  import FileLink from "../file-link.svelte";

  export type RouteTitleProps = {
    router?: RouterInstance;
    route?: RouteResult;
    file?: string;
    content?: any;
    end?: boolean;
  };

  let { router = $bindable(), route, file, content, end }: RouteTitleProps = $props();
</script>

<div class="flex flex-col gap-4">
  <div class="flex items-center gap-3 rounded-md bg-black/50 p-1.5 px-2 border-2">
    {#if router}
      <div class="flex flex-wrap items-center rounded-sm bg-gray-800 px-1.5 py-0.5 text-sm text-slate-500">
        <ArrowRightFromLine class="h-4 w-4 text-green-400 mr-1" />
        {router.config.id}
        {#if router.navigating}
          <span class="px-1 py-0.5 text-red-400">(hooks firing)</span>
        {:else}
          <span class="px-1 py-0.5 text-slate-600">(idle)</span>
        {/if}
        routed the path
        <span class="px-1 py-0.5 text-green-400">
          {route?.absolute?.()}
        </span>
        and nesting&nbsp;
        {#if end}
          <span class="flex items-center gap-1 whitespace-nowrap">
            <span class="text-red-400">stopped</span>
            <StopCircle class="h-4 w-4 text-red-400" />
          </span>
        {:else}
          <span class="flex items-center gap-1 whitespace-nowrap">
            <span class="text-green-400">continued</span>
            <ArrowDown class="h-4 w-4 text-green-400" />
          </span>
        {/if}
      </div>
    {/if}
    <ArrowRight class="h-4 w-4 text-slate-500" />
    <FileLink {file} />
  </div>
  {#if content}
    <div class="p-2">
      {#if typeof content === "string"}
        <div class="flex flex-col items-center gap-2 text-center text-slate-400">
          <div class="max-w-3xl text-sm text-slate-500">
            {content}
          </div>
        </div>
      {:else}
        <div class="flex items-center">
          {@render content()}
        </div>
      {/if}
    </div>
  {/if}
</div>


================================================
FILE: demo/src/lib/components/routes/route-wrapper.svelte
================================================
<script lang="ts">
  import type { RouteResult, RouterInstance } from "@mateothegreat/svelte5-router";
  import { Anchor } from "lucide-svelte";
  import type { RouteLinkProps } from "./route-link.svelte";
  import RouteLink from "./route-link.svelte";
  import type { RouteTitleProps } from "./route-title.svelte";
  import RouteTitle from "./route-title.svelte";

  export type RouteWrapperProps = {
    router?: RouterInstance;
    name: string;
    route?: RouteResult;
    end?: boolean;
    title: RouteTitleProps;
    links: RouteLinkProps[];
    children?: any;
  };

  let { router = $bindable(), name, route, title, links, children, end = $bindable() }: RouteWrapperProps = $props();
</script>

<div class="flex flex-col gap-3 border-2 rounded-md h-full p-2.5">
  <div class="flex flex-col gap-4">
    <RouteTitle
      {router}
      {route}
      {end}
      file={title.file}
      content={title.content} />
    <div class="flex w-fit items-center gap-2 rounded-md border-2 border-slate-900/80 bg-slate-700/80 p-1.5">
      <p class="flex items-center gap-1 text-sm text-slate-300">
        <Anchor class="h-4 w-4 text-indigo-500" />
        <span class="text-pink-400">{router?.config.id}</span>
        routes:
      </p>
      {#each links as link}
        <RouteLink {...link} />
      {/each}
    </div>
  </div>
  <div class="flex-1">
    {@render children?.()}
  </div>
</div>


================================================
FILE: demo/src/lib/default-route-config.ts
================================================
import { StatusCode, type RouteResult } from "@mateothegreat/svelte5-router";

import NotFound from "$routes/not-found.svelte";

/**
 * Surface a reusable configuration for routers to import
 * and apply to their router instances:
 *
 * @example
 * ```ts
 * <script lang="ts">
 *   import { RouteConfig } from "@mateothegreat/svelte5-router";
 *   import { myDefaultRouterConfig } from "$lib/default-route-config";
 *
 *   const routes: RouteConfig[] = [
 *     {
 *       path: "/home",
 *       component: Home
 *     }
 *   ];
 * </script>
 *
 * <Router
 *   id="my-main-router"
 *   {routes}
 *   {...myDefaultRouterConfig} />
 * ```
 */
export const myDefaultRouterConfig = {
  statuses: {
    /**
     * You can use a function to return a new route or a promise that
     * resolves to a new route:
     */
    [StatusCode.NotFound]: (result: RouteResult) => {
      console.log(result);
      return {
        component: NotFound,
        props: {
          somethingExtra: new Date().toISOString()
        }
      };
    }
    /**
     * You can also use an object to return a new route while having access
     * to the path and querystring:
     *
     * [StatusCode.NotFound]: (path: RouteResult) => ({
     *   component: NotFound,
     *   props: {
     *     somethingExtra: new Date().toISOString()
     *   }
     * }),
     *
     *
     * or simply return an object with a component and props:
     *
     * [StatusCode.NotFound]: {
     *   component: NotFound,
     *   props: {
     *     somethingExtra: new Date().toISOString()
     *   }
     * }
     */
  }
};


================================================
FILE: demo/src/lib/router-history.ts
================================================
import type { Route } from "@mateothegreat/svelte5-router";

export const history = $state<Route[]>([]);

export const appendHistory = (route: Route) => {
  history.push(route);
};


================================================
FILE: demo/src/lib/session.svelte.ts
================================================
let _state: {
  mode: "hash" | "path";
} = $state({
  mode: (localStorage.getItem("mode") as "hash" | "path") || "path"
});

export const session = {
  set mode(value: "hash" | "path") {
    localStorage.setItem("mode", value);
    _state = {
      ..._state,
      mode: value
    };
    if (value === "hash") {
      window.history.pushState({}, "", `/#/`);
    } else {
      window.history.pushState({}, "", "/");
    }
  },
  get mode() {
    return _state.mode;
  }
};


================================================
FILE: demo/src/main.ts
================================================
import { mount } from "svelte";

import './app.css';
import App from './app.svelte';

const app = mount(App, {
  target: document.getElementById('app')!,
})

export default app


================================================
FILE: demo/src/routes/delayed.svelte
================================================
<div class="bg-indigo-400 p-10">
  <h1>
    a delayed route component, the text for "Navigating" will be "Navigating: busy" while this component is loading
  </h1>
</div>


================================================
FILE: demo/src/routes/extras/dump.svelte
================================================
<script lang="ts">
  import Code from "$lib/components/code.svelte";
  let { route, ...rest } = $props();
</script>

<div class="flex flex-col gap-3">
  <Code
    title="usage:"
    file="src/routes/extras/dump.svelte"
    class="flex-1">
    {`<script lang="ts">
  let { route, ...rest } = $props();
  console.log(route, rest);
</script>`}
  </Code>

  <div class="flex gap-3">
    <Code
      title="$props().route"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(route, null, 2)}</div>
    </Code>

    <Code
      title="$props().rest"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(rest, null, 2)}</div>
    </Code>
  </div>
</div>


================================================
FILE: demo/src/routes/extras/extras.svelte
================================================
<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { RouterInstance } from "@mateothegreat/svelte5-router";
  import type { RouteConfig } from "@mateothegreat/svelte5-router/route.svelte";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import { onDestroy } from "svelte";
  import PassingDownProps from "./passing-down-props.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();

  let randoms = $state({
    float: (Math.random() * 1000).toFixed(2),
    int: (Math.random() * 1000).toFixed(0),
    string: (Math.random() * 1000).toFixed(2).toString()
  });

  const interval = setInterval(() => {
    randoms.float = (Math.random() * 1000).toFixed(2);
    randoms.int = (Math.random() * 1000).toFixed(0);
    randoms.string = (Math.random() * 1000).toFixed(2).toString();
  }, 750);

  onDestroy(() => {
    clearInterval(interval);
  });

  const routes: RouteConfig[] = [
    {
      component: overview
    },
    {
      path: "passing-down-props",
      component: PassingDownProps,
      props: {
        route: "passing-down-props"
      },
      hooks: {
        pre: () => {
          console.log("pre");
          return true;
        }
      }
    }
  ];
</script>

{#snippet overview()}
  <div class="p-2">
    <div class="flex flex-col items-center gap-2 text-center text-slate-400">
      <div class="flex max-w-3xl flex-col gap-2 text-sm text-slate-500">
        <p class="text-fuchsia-500">Extra & cool stuff can be demoed here.</p>
        <p>Click a route above to see the different effects!</p>
      </div>
    </div>
  </div>
{/snippet}

<RouteWrapper
  {router}
  name="extras"
  {route}
  title={{
    file: "src/routes/extras/extras.svelte"
  }}
  links={[
    {
      href: "/extras/passing-down-props",
      label: "passing-down-props"
    }
  ]}>
  <Router
    basePath="/extras"
    bind:instance={router}
    {routes} />
</RouteWrapper>


================================================
FILE: demo/src/routes/extras/passing-down-props.svelte
================================================
<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { RouterInstance } from "@mateothegreat/svelte5-router";
  import type { RouteConfig } from "@mateothegreat/svelte5-router/route.svelte";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import Dump from "./dump.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();

  const routes: RouteConfig[] = [
    {
      component: Dump,
      props: {
        foo: "bar",
        baz: {
          awesome: true
        }
      }
    }
  ];
</script>

<RouteWrapper
  {router}
  name="passing-down-props"
  {route}
  end={true}
  title={{
    file: "src/routes/extras/passing-down-props.svelte"
  }}
  links={[
    {
      href: "/extras/passing-down-props",
      label: "default path"
    }
  ]}>
  <Router
    basePath="/extras/passing-down-props"
    bind:instance={router}
    myAdditionalProp="I was added to the <Router/> component directly."
    {routes} />
</RouteWrapper>


================================================
FILE: demo/src/routes/hash/hash.svelte
================================================
<script lang="ts">
  import { route } from "@mateothegreat/svelte5-router";

  console.log(location.hash);
</script>

<a
  use:route
  href="/hash/#b?test=4">
  b
</a>

<a
  use:route
  href="/hash/#a?test=123">
  a
</a>


================================================
FILE: demo/src/routes/home.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Code from "$lib/components/code.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { RouterInstance, type Route, type RouteResult } from "@mateothegreat/svelte5-router";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import { Github, MessageCircleQuestion, Newspaper } from "lucide-svelte";

  let { route }: { route: RouteResult } = $props();
  let router: RouterInstance = $state();

  const routes: Route[] = [
    {
      // Starting paths with "/" is not required, but it's a good idea to
      // do so for clarity in some cases.
      //
      // The router will match this route if the path is "/welcome" or "/home/welcome"
      // because the base path is passed in as "/home" below.
      path: "/",
      component: welcome
    },
    {
      // Starting paths with "/" is not required, but it's a good idea to
      // do so for clarity in some cases.
      //
      // The router will match this route if the path is "/with-query-params" or "/home/with-query-params"
      // because the base path is passed in as "/home" below.
      path: "with-query-params",
      component: displayRouteProps
    }
  ];
</script>

{#snippet welcome()}
  <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">
    <h1 class="text-xl font-bold">Single Page Application Router (SPAR) for Svelte 5+</h1>
    <p>
      <InlineCode
        text="@mateothegreat/svelte5-router"
        class="bg-black text-blue-500" /> is an SPA router for Svelte that allows you to divide & conquer your app with nested
      routers, snippets, and more.
    </p>
    <div class="flex gap-3">
      <div
        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">
        <Newspaper class="h-5 w-5" />
        <a
          target="_blank"
          href="https://github.com/mateothegreat/svelte5-router/blob/main/docs/readme.md"
          class="">
          Documentation
        </a>
      </div>
      <div
        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">
        <Github class="h-5 w-5" />
        <a
          target="_blank"
          href="https://github.com/mateothegreat/svelte5-router"
          class="">
          GitHub Repository
        </a>
      </div>
      <div
        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">
        <MessageCircleQuestion class="h-5 w-5" />
        <a href="https://github.com/mateothegreat/svelte5-router/issues">GitHub Issues</a>
      </div>
    </div>
    <div class="flex flex-col gap-2">
      <h2 class="text-lg font-semibold text-indigo-400">Features</h2>
      <ul class="list-disc space-y-1 pl-6 text-slate-300">
        <li>Built for Svelte 5 🚀!</li>
        <li>Divide & conquer - use nested routers all over the place</li>
        <li class="text-teal-400">Use components, snippets, or both 🔥!</li>
        <li>Use regex paths (e.g. /foo/(.*?)/bar) and/or named parameters together</li>
        <li>Use async routes simply with component: async () => import("./my-component.svelte")</li>
        <li class="font-bold">Add hooks to your routes to control the navigation flow 🔧</li>
        <li>Automagic styling of your anchor tags 💄</li>
        <li>Helper methods 🛠️ to make your life easier</li>
        <li>Debugging tools included 🔍</li>
      </ul>
    </div>
    <div class="flex items-center">
      Get started now with
      <InlineCode
        text="npm install @mateothegreat/svelte5-router"
        class="mx-1 bg-black" />
      and check out the
      <a
        class="mx-1 cursor-pointer text-violet-400 hover:text-green-500 hover:underline"
        href="https://github.com/mateothegreat/svelte5-router/blob/main/docs/getting-started.md">
        getting started guide..
      </a>
    </div>
  </div>
{/snippet}

{#snippet displayRouteProps()}
  <div class="flex flex-col gap-4 border-t-2 border-slate-800 pt-4">
    <div class="flex flex-col gap-5">
      <div class="w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2">
        match path
        <InlineCode text={route.route.path.toString()} />
        to
        <InlineCode text={`${route.route.path}?someQueryParam=123`} />
      </div>
      <div class="flex flex-col gap-4 text-sm text-gray-400">
        <div class=" gap-1">
          This demo shows how to use the route's
          <InlineCode text="querystring" />
          configuration option to match against the current
          <InlineCode text="location.search" />
          value passed in by the browser.
        </div>
        <Badge
          variant="success"
          class="w-fit">
          Because we did not specify any <InlineCode text="querystring" /> parameters, the route render regardless of the
          <InlineCode text="querystring" /> and will be passed to the component as shown below.
        </Badge>
      </div>
    </div>
    <Code
      title={"{#snippet displayRouteProps()}"}
      file="src/routes/home.svelte">
      {JSON.stringify(route.result, null, 2)}
    </Code>
  </div>
{/snippet}

<RouteWrapper
  {router}
  name="home-router"
  {route}
  end={true}
  title={{
    file: "src/routes/home.svelte",
    content:
      "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."
  }}
  links={[
    {
      href: "/home",
      label: "/home",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/home/with-query-params?someQueryParam=123",
      label: "/home/with-query-params?someQueryParam=123"
    }
  ]}>
  <Router
    id="home-router"
    basePath="/home"
    bind:instance={router}
    {...myDefaultRouterConfig}
    {routes} />
</RouteWrapper>


================================================
FILE: demo/src/routes/nested/level-1/level-1.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance } from "@mateothegreat/svelte5-router";
  import Level_2 from "./level-2/level-2.svelte";

  let router: RouterInstance = $state();
  let { route, foo } = $props();

  /**
   * Demonstrate how support for additional props is working.
   */
  console.log("additionalProps.foo in ../nested.svelte is passed to this component as:", foo);

  /**
   * This is a helper state variable that can be used to determine if the
   * current route is the same as the route that is being rendered so
   * that we can show a badge to indicate this is the last router in the
   * nested routing hierarchy.
   */
  let end = $state(false);
  $effect(() => {
    end = router.current?.result.path.condition === "default-match";
  });
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/level-1/level-1.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested/level-1"
  {end}
  {route}
  title={{
    file: "src/routes/nested/level-1/level-1.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested/level-1",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1/level-2",
      label: "/nested/level-1/level-2"
    }
  ]}>
  <Router
    id="nested-level-1-router"
    basePath="/nested/level-1"
    bind:instance={router}
    routes={[
      {
        path: "level-2",
        component: Level_2,
        hooks: {
          pre: () => {
            console.log(`Route "/nested/level-1/level-2" matched (I'm a pre hook in the level-1.svelte route)`);
            return true;
          }
        }
      },
      /**
       * Default routes can be placed anywhere in the routes array
       * and will be matched if no other routes match regardless of
       * their position in the array:
       */
      {
        component: snippet
      }
    ]}
    {...myDefaultRouterConfig} />
</RouteWrapper>


================================================
FILE: demo/src/routes/nested/level-1/level-2/level-2.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance, type Route } from "@mateothegreat/svelte5-router";
  import Level_3 from "./level-3/level-3.svelte";

  const routes: Route[] = [
    {
      path: "level-3",
      component: Level_3,
      hooks: {
        pre: () => {
          console.log(`Route "/nested/level-1/level-2" matched (I'm a pre hook in the level-2.svelte route)`);
          return true;
        }
      }
    },
    {
      component: snippet
    }
  ];

  let router: RouterInstance = $state();
  let { route } = $props();
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/level-1/level-2/level-2.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested/level-2"
  end={true}
  {route}
  title={{
    file: "src/routes/nested/level-1/level-2/level-2.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested/level-1/level-2",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1/level-2/level-3",
      label: "/nested/level-1/level-2/level-3"
    }
  ]}>
  <Router
    id="nested-level-2-router"
    basePath="/nested/level-1/level-2"
    bind:instance={router}
    {routes}
    {...myDefaultRouterConfig} />
</RouteWrapper>


================================================
FILE: demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte
================================================
<script>
  import Container from "$lib/components/container.svelte";
</script>

<Container
  title={"Level_3"}
  file="src/routes/nested/level-1/level-2/level-3/level-3.svelte">
  <div class="flex flex-col gap-3 bg-green-500 p-4 text-center font-medium text-white">
    You've reached the deepest level of nested routing (level-3)!
  </div>
</Container>


================================================
FILE: demo/src/routes/nested/nested.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance, type Route } from "@mateothegreat/svelte5-router";
  import Level_1 from "./level-1/level-1.svelte";

  const routes: Route[] = [
    {
      component: snippet
    },
    {
      path: "level-1",
      component: Level_1,
      hooks: {
        pre: () => {
          console.log(`Route "/nested/level-1" matched (I'm a pre hook in the nested.svelte route)`);
          return true;
        }
      }
    }
  ];

  let router: RouterInstance = $state();
  let { route } = $props();

  /**
   * This is a helper state variable that can be used to determine if the
   * current route is the same as the route that is being rendered so
   * that we can show a badge to indicate this is the last router in the
   * nested routing hierarchy.
   */
  let end = $state(false);
  $effect(() => {
    end = router.current?.result.path.condition === "default-match";
  });

  const additionalProps = {
    foo: {
      bar: "baz"
    }
  };
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/nested.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested"
  {route}
  {end}
  title={{
    file: "src/routes/nested/nested.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1",
      label: "/nested/level-1"
    }
  ]}>
  <Router
    id="nested-router"
    basePath="/nested"
    bind:instance={router}
    {...myDefaultRouterConfig}
    {routes}
    {...additionalProps} />
</RouteWrapper>


================================================
FILE: demo/src/routes/not-found.svelte
================================================
<script lang="ts">
  let { route } = $props();
  $inspect(route);
</script>

<div class="flex flex-col items-center justify-center gap-4">
  <pre class="rounded-md bg-gray-800 p-2 text-sm text-emerald-500">included from "not-found.svelte":</pre>
  <h1 class="text-2xl font-bold">404 not found :(</h1>
  <p class="text-sm text-gray-500">The page you are looking for does not exist.</p>
  <pre class="rounded-md bg-gray-900 p-2 text-sm text-gray-400">$props():

{JSON.stringify(route, null, 2)}
</pre>
</div>


================================================
FILE: demo/src/routes/paths-and-params/custom-not-found.svelte
================================================
<script lang="ts">
  let { route } = $props();
</script>

<div class="flex flex-col items-center justify-center gap-4">
  <pre class="rounded-md bg-gray-800 p-2 text-sm text-emerald-500">included from "custom-not-found.svelte":</pre>
  <h1 class="text-2xl font-bold">404 not found :(</h1>
  <p class="text-sm text-gray-500">The page you are looking for does not exist.</p>
  <pre class="rounded-md bg-gray-900 p-2 text-sm text-gray-400">$props():

{JSON.stringify(route, null, 2)}
</pre>
</div>


================================================
FILE: demo/src/routes/paths-and-params/display-params.svelte
================================================
<script lang="ts">
  import Code from "$lib/components/code.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";

  let { route } = $props();
</script>

{#snippet content()}
  The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes
  it to the component as the `params` prop.
{/snippet}

<Code
  title="params.route value:"
  file="src/routes/props/display-params.svelte">
  <div>{JSON.stringify(route, null, 2)}</div>
</Code>


================================================
FILE: demo/src/routes/paths-and-params/paths-and-params.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { getStatusByValue, RouterInstance, StatusCode } from "@mateothegreat/svelte5-router";
  import type { RouteConfig, RouteResult } from "@mateothegreat/svelte5-router/route.svelte";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import CustomNotFound from "./custom-not-found.svelte";
  import DisplayParams from "./display-params.svelte";
  import QuerystringMatching from "./querystring-matching.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();

  const routes: RouteConfig[] = [
    /**
     * This route will be used if there is no matching routes we
     * define below:
     */
    {
      component: snippet
    },
    /**
     * For this route, use querystring to match against the current location.search value:
     *
     *   ✅ /paths-and-params/query-matcher?number=2&number-as-string=2
     *   ✅ /paths-and-params/query-matcher?number=2.1&number-as-string=2.345
     *   ❌ /paths-and-params/query-matcher?number=2&number-as-string=two
     */
    {
      name: "match-number-and-string",
      path: "query-matcher",
      component: QuerystringMatching,
      querystring: {
        /**
         * The "number" querystring parameter:
         *
         *   - ✅ must be present
         *   - ✅ must be a number or a string that can be converted to a number
         */
        float: /^([\d.]+)$/,
        /**
         * The "number-as-string" querystring parameter:
         *
         *   - ✅ must be present
         *   - ✅ must be a number or a string that can be converted to a number
         */
        string: "123"
      }
    },
    /**
     * For this route, use querystring to match against the current location.search value:
     *
     *   ✅ /paths-and-params/query-matcher?pagination=1,10
     *   ✅ /paths-and-params/query-matcher?pagination=1&company=123
     *   ✅ /paths-and-params/query-matcher?pagination=1&company=1234567
     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=123
     *   ✅ /paths-and-params/query-matcher?pagination=2,20&company=1234567
     *   ❌ /paths-and-params/query-matcher?pagination=1,&company=123
     *   ❌ /paths-and-params/query-matcher?pagination=&company=123
     *   ❌ /paths-and-params/query-matcher?pagination=2,3,4
     *   ❌ /paths-and-params/query-matcher?pagination=bad-value
     */
    {
      name: "match-pagination",
      path: "query-matcher",
      component: QuerystringMatching,
      querystring: {
        /**
         * The "pagination" querystring parameter:
         *
         *   - ✅ must be present
         *   - ✅ must be a number
         *   - ❔ and then be followed by an optional "cursor" parameter:
         *     - ✅ it must have a comma delimiter
         *     - ✅ it must be a string of alphanumeric characters only
         */
        pagination: /^(?<page>\d+)(?:,(?<cursor>\d+))?$/,
        /**
         * The "company" querystring parameter is optional, but if present:
         *
         *   - ✅ can be empty
         *   - ✅ must be a single number
         */
        company: /^(\d+)?$/
      },
      props: {
        metadata: {
          src: "paths-and-params.svelte"
        }
      }
    },
    /**
     * This route will match any path and pass the pattern groups
     * as an object to the component that is passed in $props().
     *
     * The component will access the params using $props() and the
     * property "child" will contain the value extracted from the path.
     */
    {
      path: "extensions/(?<extension>.*)/(?<page>[^/]+)(?:/(?<rest>.*))?",
      component: DisplayParams
    },
    {
      name: "fancy-regex-capture-group",
      path: "(foo|bar).*",
      component: DisplayParams,
      props: {
        randomId: Math.random().toString(36).substring(2, 15),
        someUserStuff: {
          username: "mateothegreat",
          userAgent: navigator.userAgent
        }
      }
    }
  ];
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/paths-and-params/paths-and-params.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/paths-and-params"
  {route}
  end={true}
  title={{
    file: "src/routes/paths-and-params/paths-and-params.svelte",
    content: "This demo shows how to pass values downstream to the component that is rendered."
  }}
  links={[
    {
      href: "/paths-and-params",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/paths-and-params/foo",
      label: "foo"
    },
    {
      href: "/paths-and-params/query-matcher?pagination=2,23&company=123",
      label: "query-matcher?pagination=2,23&company=123"
    }
  ]}>
  <Router
    id="props-router"
    basePath="/paths-and-params"
    bind:instance={router}
    {routes}
    hooks={{
      /**
       * You could use a global auth guard here to run before every route:
       *
       * hooks={{
       *   pre: (route: Routed) => {
       *     if (!isAuthenticated()) {
       *       console.warn("user is not authenticated, redirecting to login", route);
       *       return {
       *         component: NotGonnaMakeIt,
       *       };
       *     }
       *   }
       * }}
       *
       * You could also use a global error handler here to run after every route:
       *
       * hooks={{
       *   post: [
       *     (route: Routed) => {
       *       console.info("do some more work here", route);
       *       return true;
       *     },
       *     someLogMethod,
       *     finalMethod,
       *   ]
       * }}
       */
    }}
    statuses={{
      [StatusCode.NotFound]: (result: RouteResult) => {
        console.warn(`the path "${result.result.path.original}" could not be found :(`, {
          /**
           * You could use the status name to make something pretty:
           */
          status: getStatusByValue(StatusCode.NotFound),
          /**
           * You could also use the status code to something more dynamic:
           */
          code: StatusCode.NotFound
        });
        /**
         * Now, we're going to return a new route that will be rendered by the router:
         */
        return {
          component: CustomNotFound,
          /**
           * You can pass props to the component that is rendered if you need to
           * share some extra information:
           */
          props: {
            src: "props.svelte"
          }
        };
      }
    }} />
</RouteWrapper>


================================================
FILE: demo/src/routes/paths-and-params/querystring-matching.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Code from "$lib/components/code.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";

  let { route } = $props();
</script>

{#snippet content()}
  The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes
  it to the component as the `params` prop.
{/snippet}

<div class="flex flex-col gap-4 border-t-2 border-slate-800 pt-4">
  <div class="flex flex-col gap-5">
    <div class="w-fit px-2 flex items-center gap-1 font-bold text-indigo-300 bg-gray-800 rounded-sm p-2">
      <InlineCode text="querystring" />
      match multiple values
      <InlineCode text="RegExp" />
      <InlineCode text="number" />
      <InlineCode text="string" />
      <InlineCode text="boolean" />
      <InlineCode text="array" />
    </div>
    <div class="flex flex-col gap-4 text-sm text-gray-400">
      <Badge
        variant="warning"
        class="w-fit">
        Matching will occur if <em><strong>all</strong></em>
        querystring keys and values match what was provided in the `querystring` configuration option.
      </Badge>
      <div class=" gap-1">
        This demo shows how to use the route's
        <InlineCode text="querystring" />
        configuration option to match against the current
        <InlineCode text="location.search" />
        value passed in by the browser.
      </div>
      <Badge
        variant="success"
        class="w-fit">
        There are 2 potential matches for the path "/paths-and-params/query-matcher". Notice how the active route is
        highlighted in green for this route only + the querystring matching.
      </Badge>
    </div>
  </div>
  <div class="flex flex-col gap-6 border-2 bg-gray-900/60 rounded-lg p-4 border-slate-700">
    <Code
      title="$props().route.result value:"
      file="src/routes/paths-and-params/querystring-matching.svelte">
      <div>{JSON.stringify(route.result, null, 2)}</div>
    </Code>
    <Code
      title="$props().route.route value:"
      file="src/routes/paths-and-params/querystring-matching.svelte">
      <div class="text-indigo-400">{JSON.stringify(route.route, null, 2)}</div>
    </Code>
  </div>
</div>


================================================
FILE: demo/src/routes/patterns/dump.svelte
================================================
<script lang="ts">
  import { BadgeInfo } from "lucide-svelte";

  import { Lightbulb } from "lucide-svelte";

  import Code from "$lib/components/code.svelte";
  let { route, ...rest } = $props();
</script>

<div class="flex flex-col gap-3">
  <Code
    title="usage:"
    file="src/routes/extras/dump.svelte"
    language="svelte"
    class="flex-1">
    {`<script lang="ts">
  let { route, ...rest } = $props();
  console.log(route, rest);
</script>`}
  </Code>

  <div class="flex gap-3">
    <Code
      title="$props().route"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(route, null, 2)}</div>
    </Code>

    <Code
      title="$props().rest"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(rest, null, 2)}</div>
    </Code>
  </div>
</div>


================================================
FILE: demo/src/routes/patterns/output.svelte
================================================
<div class="flex flex-col gap-4 p-4">
  <div>
    <h1 class="text-xl font-semibold text-slate-200">Nested Paths</h1>
    <p class="text-slate-400">
      This example will match any path that starts with `/path/path/path` and can be nested further.
    </p>
  </div>
  <div class="bg-slate-800/50 rounded-lg p-3">
    <h3 class="text-sm font-medium text-slate-300 mb-2">Route Props:</h3>
    <pre class="text-xs text-slate-400 overflow-auto">{JSON.stringify(props, null, 2)}</pre>
  </div>
</div>


================================================
FILE: demo/src/routes/patterns/parameter-extraction.svelte
================================================
<script lang="ts">
  import { route } from "@mateothegreat/svelte5-router";

  import Code from "$lib/components/code.svelte";
  let { route: r, ...rest } = $props();
</script>

<div class="flex flex-col gap-1">
  <h1 class="text-lg font-semibold text-slate-300 flex items-center gap-1">Parameter Extraction</h1>
  <div class="text-slate-500 text-sm">
    <p>
      Given the following route config, everything after the "/parameter-extraction" will be available as a named
      parameter. This means that the following paths will be matched:
    </p>
    <ul class="list-disc list-inside">
      <li>
        <a
          use:route
          href="/patterns/parameter-extraction/foo">
          parameter-extraction/foo
        </a>
      </li>
      <li>
        <a
          use:route
          href="/patterns/parameter-extraction/bar">
          parameter-extraction/bar
        </a>
      </li>
      <li>
        <a
          use:route
          href="/patterns/parameter-extraction/baz">
          parameter-extraction/baz
        </a>
      </li>
    </ul>
  </div>
</div>

<div class="flex gap-3">
  <Code
    title="RouteConfig:"
    language="typescript"
    class="flex-1">
    {`  const routes: RouteConfig[] = [
  {
    path: /^\/parameter-extraction\/(?<child>.*)$/,
    component: ParameterExtraction
  }
];`}
  </Code>
  <Code
    title="$props().route.result.path.params"
    language="json"
    class="flex-1">
    <div>{JSON.stringify(r.result.path.params, null, 2)}</div>
  </Code>
</div>


================================================
FILE: demo/src/routes/patterns/patterns.svelte
================================================
<script lang="ts">
  import { route, type RouteConfig } from "@mateothegreat/svelte5-router";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import type { TableColumn } from "@mateothegreat/svelte5-table";
  import { DropinTable } from "@mateothegreat/svelte5-table";
  import { CirclePlay, Lightbulb, MousePointerClick } from "lucide-svelte";
  import { writable, type Writable } from "svelte/store";
  import Dump from "./dump.svelte";
  import ParameterExtraction from "./parameter-extraction.svelte";

  type Component = {
    name: string;
    description: string;
    path: string;
  };

  const columns: TableColumn[] = [
    {
      field: "name",
      header: "Routing Pattern"
    },
    {
      field: "description",
      header: "Description"
    },
    {
      field: "actions",
      header: customHeader,
      class: "w-[300px]",
      renderer: action
    }
  ];

  const components: Writable<Component[]> = writable([
    {
      name: "Default Route",
      description: "If the path is empty, the route will be matched otherwise evaluation will continue.",
      path: "/default-route"
    },
    {
      name: "Single Path",
      description: "This example will match any path that starts with `/path`.",
      path: "/single-path"
    },
    {
      name: "Nested Paths",
      description: "This example will match any path that starts with `/path/path/path` and can be nested further.",
      path: "/nested-paths"
    },
    {
      name: "Parameter Extraction",
      description: "Combine arbitrary paths and extractable parameters.",
      path: "/parameter-extraction"
    },
    {
      name: "Named Parameters",
      description:
        "This example will match any path that starts with `/path/path/path/path` and can be nested further.",
      path: "/named-parameters"
    }
  ]);

  let selections = writable([]);
  const routes: RouteConfig[] = [
    {
      path: "default-route",
      component: Dump
    },
    {
      path: "single-path",
      component: Dump
    },
    {
      path: /^\/parameter-extraction\/(?<child>.*)$/,
      component: ParameterExtraction
    }
  ];
</script>

{#snippet customHeader()}
  <div class="flex items-center gap-1 pl-2">
    Navigate to Demo
    <CirclePlay class="h-4 w-4 text-green-500" />
  </div>
{/snippet}

{#snippet action(row: any)}
  <div class="flex flex-1 ml-2">
    <a
      use:route
      href={`/patterns${row.path}`}
      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">
      <MousePointerClick class="h-4 w-4" />
      <span class="text-green-500 flex items-center gap-0.5">
        goto("
        <span class="text-sky-400">
          {row.path}
        </span>
        ")
      </span>
    </a>
  </div>
{/snippet}

<div class="flex flex-col gap-4">
  <div class="flex flex-col gap-1">
    <h1 class="text-lg font-semibold text-slate-300 flex items-center gap-1">
      <Lightbulb class="h-5 w-5 text-cyan-400" />
      Routing Patterns
    </h1>
    <p class="text-slate-500 text-sm">This page contains an assortment of routing patterns to help you get started.</p>
  </div>
  <div class="bg-slate-700/20 text-slate-600 py-2 border-2 border-slate-700 rounded-lg">
    <DropinTable
      {columns}
      data={$components}
      bind:selections />
  </div>
  <div class="flex flex-1 flex-col gap-2">
    <Router
      basePath="/patterns"
      renavigation={true}
      myExtraRouterProp={{
        calledFrom: "patterns <Router />"
      }}
      {routes} />
  </div>
</div>

<style>
  :global(td) {
    padding: 8px !important;
  }
</style>


================================================
FILE: demo/src/routes/protected/account-state.svelte.ts
================================================
let token = $state(localStorage.getItem("token"));

export const client = {
  get loggedIn() {
    return token !== null;
  },
  set loggedIn(value: boolean) {
    token = value ? "true" : null;
    console.log("token", token);
    if (value) {
      localStorage.setItem("token", "true");
    } else {
      localStorage.removeItem("token");
    }
  }
};


================================================
FILE: demo/src/routes/protected/denied.svelte
================================================
<script lang="ts">
  import { route } from "@mateothegreat/svelte5-router";
  import { LockIcon, ShieldAlert } from "lucide-svelte";
</script>

<div class="flex items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
  <div class="w-full max-w-md space-y-8 rounded-lg bg-white p-8 shadow-lg">
    <div class="text-center">
      <div class="mb-4 flex justify-center">
        <ShieldAlert class="h-16 w-16 text-red-600" />
      </div>
      <h1 class="mb-2 text-3xl font-bold text-gray-900">Access Denied</h1>
      <div class="mb-4 flex items-center justify-center gap-2 text-red-600">
        <LockIcon class="h-5 w-5" />
        <span class="font-semibold">Secure Area</span>
      </div>
      <p class="mb-4 text-gray-600">For your security, access to this banking area has been denied. This may be due to:</p>
      <ul class="mb-6 ml-4 list-disc text-left text-gray-600">
        <li>Insufficient permissions</li>
        <li>Invalid authentication</li>
        <li>Session timeout</li>
      </ul>
      <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>
      <div class="mt-6">
        <a
          use:route
          href="/protected"
          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">
          Return to Homepage
        </a>
      </div>
    </div>
  </div>
</div>


================================================
FILE: demo/src/routes/protected/login.svelte
================================================
<script lang="ts">
  import { goto } from "@mateothegreat/svelte5-router";
  import { Shield } from "lucide-svelte";
  import { fade } from "svelte/transition";
  import { client } from "./account-state.svelte";
</script>

<div
  class="p-6"
  in:fade={{ duration: 300 }}>
  <div class="mx-auto max-w-md rounded-xl bg-white p-8 shadow-lg">
    <div class="mb-6 text-center">
      <div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100">
        <Shield class="h-8 w-8 text-blue-600" />
      </div>
      <h2 class="text-2xl font-bold text-gray-800">Secure Login</h2>
      <p class="mt-2 text-gray-600">Please log in to access your account</p>
    </div>

    <button
      class="w-full rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700"
      on:click={() => {
        client.loggedIn = true;
        goto("/protected/manage-account");
      }}>
      Login to Your Account
    </button>
  </div>
</div>


================================================
FILE: demo/src/routes/protected/main.svelte
================================================
<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { goto, registry, Router, RouterInstance } from "@mateothegreat/svelte5-router";
  import { ArrowRight, Building2, Loader2, Shield, Wallet } from "lucide-svelte";
  import Denied from "./denied.svelte";
  import Login from "./login.svelte";
  import { authGuardFast } from "./manage-account/auth-guard-fast";

  let router: RouterInstance = $state();
  let { route } = $props();

  /**
   * This is a helper state variable that can be used to determine if the
   * current route is the same as the route that is being rendered so
   * that we can show a badge to indicate this is the last router in the
   * nested routing hierarchy.
   */
  let end = $state(true);

  $effect(() => {
    end =
      router.current?.result.path.condition === "default-match" ||
      location.pathname === "/protected/login" ||
      location.pathname === "/protected/denied";
  });
</script>

{#snippet snippet()}
  <div class="rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300">
    <div class="mx-auto max-w-6xl px-4 py-12">
      <div class="mb-16 text-center">
        <h1 class="mb-6 text-4xl font-bold text-blue-500 md:text-6xl">Welcome to SPA Router Bank!</h1>
        <p class="mb-8 text-xl text-black">Your trusted partner in routing.</p>
        <button
          onclick={() => goto("/protected/login")}
          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">
          Login <ArrowRight size={20} />
        </button>
      </div>
      <div class="mb-16 grid gap-8 md:grid-cols-3">
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Shield class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Secure Banking</h3>
          <p class="text-gray-600">State-of-the-art security measures to protect your financial data</p>
        </div>
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Building2 class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Business Solutions</h3>
          <p class="text-gray-600">Comprehensive banking solutions for businesses of all sizes</p>
        </div>
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Wallet class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Personal Banking</h3>
          <p class="text-gray-600">Tailored financial services for your personal needs</p>
        </div>
      </div>
    </div>
  </div>
{/snippet}

<RouteWrapper
  {router}
  name="/protected"
  {route}
  {end}
  title={{
    file: "src/routes/protected/main.svelte",
    content: "Demo to show how you can use hooks to control the navigation of your app to control authentication, etc."
  }}
  links={[
    {
      href: "/protected",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/protected/login",
      label: "/protected/login"
    },
    {
      href: "/protected/manage-account",
      label: "/protected/manage-account"
    },
    {
      href: "/protected/denied",
      label: "/protected/denied"
    }
  ]}>
  <Router
    id="protected-router"
    basePath="/protected"
    bind:instance={router}
    routes={[
      {
        component: snippet
      },
      {
        path: "login",
        component: Login
      },
      {
        path: "manage-account",
        component: async () => import("./manage-account/manage-account.svelte"),
        hooks: {
          pre: authGuardFast
        },
      },
      {
        path: "denied",
        component: Denied
      }
    ]}
    {...myDefaultRouterConfig} />
</RouteWrapper>

{#if registry.get("manage-account-router")?.navigating}
  <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
    <div class="flex flex-col items-center gap-4 rounded-md border-2 border-green-400 bg-black/70 px-20 py-6">
      <Loader2 class="h-12 w-12 text-green-500  animate-spin" />
      <div class="text-slate-300 font-bold">Doing some work...</div>
      <div class="text-slate-400 w-96 text-center">
        We've added some pre and post hooks to the manage-account router to simulate doing some work.
      </div>
    </div>
  </div>
{/if}


================================================
FILE: demo/src/routes/protected/manage-account/auth-guard-fast.ts
================================================
import { goto, type RouteResult } from "@mateothegreat/svelte5-router";

import { session } from "$lib/session.svelte";

export const authGuardFast = async (route?: RouteResult): Promise<boolean> => {
  console.log(
    `🔍 route.hooks["pre"] has been triggered for %c${route?.route.absolute()}`,
    "color: #F9A710; font-weight: bold;"
  );

  // Crude example of checking if the user is logged in. A more
  // sophisticated example would use a real authentication system
  // and a server-side API.
  console.log("🚧 %cdoing some work here...", "color: #2196f3; font-weight: bold; font-style: italic;");

  if (!localStorage.getItem("token")) {
    console.log("%c❌ redirecting to denied", "color: #f44336; font-weight: bold; font-size: 1.1em;");
    goto(`${session.mode === "hash" ? "#" : ""}/protected/login`);
    return false;
  }

  // If the user is logged in, return true so that the router can
  // continue it's navigation to the requested route.
  console.log(
    `%c✅ allowed to continue to %c${route?.absolute()}`,
    "color: #53FF4D; font-weight: bold;",
    "color: #F9A710; font-weight: bold;"
  );
  console.log("%c✅ returning true", "background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;");

  return true;
};


================================================
FILE: demo/src/routes/protected/manage-account/auth-guard-slow.ts
================================================
import { goto, type RouteResult } from "@mateothegreat/svelte5-router";

import { session } from "$lib/session.svelte";

export const authGuardSlow = async (route?: RouteResult): Promise<boolean> => {
  console.log(
    `🔍 route.hooks["pre"] has been triggered for %c${route?.route.absolute()}`,
    "color: #F9A710; font-weight: bold;"
  );

  // Crude example of checking if the user is logged in. A more
  // sophisticated example would use a real authentication system
  // and a server-side API.
  console.log("🚧 %cdoing some work here...", "color: #2196f3; font-weight: bold; font-style: italic;");

  await new Promise((resolve) => setTimeout(resolve, 2000));

  if (!localStorage.getItem("token")) {
    console.log("%c❌ redirecting to denied", "color: #f44336; font-weight: bold; font-size: 1.1em;");
    goto(`${session.mode === "hash" ? "#" : ""}/protected/login`);
    return false;
  }

  // If the user is logged in, return true so that the router can
  // continue it's navigation to the requested route.
  console.log(
    `%c✅ allowed to continue to %c${route?.absolute()}`,
    "color: #53FF4D; font-weight: bold;",
    "color: #F9A710; font-weight: bold;"
  );
  console.log("%c✅ returning true", "background: #4caf50; color: white; padding: 2px 5px; border-radius: 3px;");

  return true;
};


================================================
FILE: demo/src/routes/protected/manage-account/balance.svelte
================================================
<script lang="ts">
  import { ArrowDownRight, ArrowUpRight, DollarSign } from "lucide-svelte";
  import { fade } from "svelte/transition";

  // Fake account data
  const accountBalance = (Math.random() * 100000).toFixed(2);
  const transactions = [
    { id: 1, type: "credit", amount: 2500.0, description: "Salary Deposit", date: "2024-03-15" },
    { id: 2, type: "debit", amount: 85.5, description: "Grocery Store", date: "2024-03-14" },
    { id: 3, type: "debit", amount: 125.0, description: "Electric Bill", date: "2024-03-13" },
    { id: 4, type: "credit", amount: 500.0, description: "Freelance Payment", date: "2024-03-12" },
    { id: 5, type: "debit", amount: 45.99, description: "Online Shopping", date: "2024-03-11" }
  ];
</script>

<div
  class="p-6"
  in:fade={{ duration: 300 }}>
  <div class="mb-8 rounded-xl bg-blue-600 p-8 text-white shadow-lg">
    <h2 class="mb-2 text-xl">Current Balance</h2>
    <div class="flex items-center gap-2">
      <DollarSign size={32} />
      <span class="text-4xl font-bold">{accountBalance}</span>
    </div>
  </div>

  <div class="rounded-xl bg-white p-6 shadow-lg">
    <h3 class="mb-4 text-xl font-semibold text-gray-800">Recent Transactions</h3>
    <div class="space-y-4">
      {#each transactions as transaction}
        <div
          class="flex items-center justify-between rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-50">
          <div class="flex items-center gap-3">
            {#if transaction.type === "credit"}
              <div class="rounded-full bg-green-100 p-2">
                <ArrowUpRight class="h-5 w-5 text-green-600" />
              </div>
            {:else}
              <div class="rounded-full bg-red-100 p-2">
                <ArrowDownRight class="h-5 w-5 text-red-600" />
              </div>
            {/if}
            <div>
              <p class="font-medium text-gray-800">{transaction.description}</p>
              <p class="text-sm text-gray-500">{transaction.date}</p>
            </div>
          </div>
          <span class={transaction.type === "credit" ? "text-green-600" : "text-red-600"}>
            {transaction.type === "credit" ? "+" : "-"}${transaction.amount.toLocaleString("en-US", {
              minimumFractionDigits: 2
            })}
          </span>
        </div>
      {/each}
    </div>
  </div>
</div>


================================================
FILE: demo/src/routes/protected/manage-account/home.svelte
================================================
<script lang="ts">
  import { route } from "@mateothegreat/svelte5-router";
  import { Activity, PiggyBank, Send, Wallet } from "lucide-svelte";
  import { fade } from "svelte/transition";

  // Fake user data
  const userData = {
    name: "Svelte Boss",
    lastLogin: new Date().toLocaleString(),
    quickStats: [
      { title: "Total Balance", amount: "$45,250.80", icon: Wallet },
      { title: "Monthly Savings", amount: "$2,450.00", icon: PiggyBank },
      { title: "Recent Activity", count: "12 transactions", icon: Activity }
    ]
  };
</script>

<div
  class="p-6"
  in:fade={{ duration: 300 }}>
  <div class="mb-6">
    <h1 class="text-2xl text-gray-400">
      Welcome back,
      <span class="text-indigo-400 font-bold">{userData.name}</span>
    </h1>
    <p class="text-gray-400">Last login: {userData.lastLogin}</p>
  </div>

  <div class="grid gap-6 md:grid-cols-3">
    {#each userData.quickStats as stat}
      <div class="rounded-xl bg-slate-700/30 p-6 shadow-lg transition-transform hover:scale-[1.02]">
        <div class="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
          <svelte:component
            this={stat.icon}
            class="h-6 w-6 text-blue-600" />
        </div>
        <h3 class="text-lg text-gray-400">{stat.title}</h3>
        <p class="mt-1 text-xl font-bold text-green-400">{stat.amount || stat.count}</p>
      </div>
    {/each}
  </div>

  <div class="mt-8">
    <a
      use:route
      href="/protected/manage-account/balance"
      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">
      <Send class="h-5 w-5" />
      View Balance
    </a>
  </div>
</div>


================================================
FILE: demo/src/routes/protected/manage-account/manage-account.svelte
================================================
<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { session } from "$lib/session.svelte";
  import { goto, Router, RouterInstance } from "@mateothegreat/svelte5-router";
  import { ArrowRight, Building2, Shield, Wallet } from "lucide-svelte";
  import { client } from "../account-state.svelte";
  import { authGuardSlow } from "./auth-guard-slow";
  import Balance from "./balance.svelte";
  import Home from "./home.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();
</script>

{#snippet snippet()}
  <div class="rounded-md border-4 border-slate-400 bg-gradient-to-b from-blue-100 to-blue-300">
    <div class="mx-auto max-w-6xl px-4 py-12">
      <div class="mb-16 text-center">
        <h1 class="mb-6 text-4xl font-bold text-blue-500 md:text-6xl">Welcome to SPA Router Bank!</h1>
        <p class="mb-8 text-xl text-black">Your trusted partner in routing.</p>
        <button
          onclick={() => goto("/protected/login")}
          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">
          Login <ArrowRight size={20} />
        </button>
      </div>
      <div class="mb-16 grid gap-8 md:grid-cols-3">
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Shield class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Secure Banking</h3>
          <p class="text-gray-600">State-of-the-art security measures to protect your financial data</p>
        </div>
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Building2 class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Business Solutions</h3>
          <p class="text-gray-600">Comprehensive banking solutions for businesses of all sizes</p>
        </div>
        <div class="rounded-xl bg-white p-6 shadow-md transition-shadow hover:shadow-lg">
          <Wallet class="mb-4 h-12 w-12 text-blue-600" />
          <h3 class="mb-2 text-xl font-semibold text-gray-800">Personal Banking</h3>
          <p class="text-gray-600">Tailored financial services for your personal needs</p>
        </div>
      </div>
    </div>
  </div>
{/snippet}

<RouteWrapper
  {router}
  name="/protected/manage-account"
  {route}
  end={true}
  title={{
    file: "src/routes/protected/manage-account/manage-account.svelte",
    content: "This router demonstrates how you can restrict access to routes based on the user's authentication state."
  }}
  links={[
    {
      href: "/protected/manage-account",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/protected/manage-account/balance",
      label: "/protected/manage-account/balance"
    },
    {
      href: "/protected/manage-account/logout",
      label: "/protected/manage-account/logout"
    }
  ]}>
  <Router
    id="manage-account-router"
    basePath="/protected/manage-account"
    bind:instance={router}
    routes={[
      /**
       * This route is optional, it's for demonstration purposes.
       * It's used to redirect to the balance page when the user
       * navigates to the manage-account route (the default path):
       */
      {
        component: Home
      },
      {
        path: "/balance",
        component: Balance,
        hooks: {
          pre: authGuardSlow
        }
      },
      {
        path: "/logout",
        hooks: {
          pre: () => {
            client.loggedIn = false;
            setTimeout(() => {
              goto(`${session.mode === "hash" ? "#" : ""}/protected/login`);
            }, 100);
          }
        }
      }
    ]}
    {...myDefaultRouterConfig} />
</RouteWrapper>


================================================
FILE: demo/src/routes/protected/manage-account/worker-client.svelte.ts
================================================
let working = $state(false);

export const workerClient = {
  get working() {
    return working;
  },
  set working(value: boolean) {
    working = value;
  }
};


================================================
FILE: demo/src/routes/transitions/fade.svelte
================================================
<script>
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";
  import { fade } from "svelte/transition";
</script>

<div in:fade={{ duration: 500 }}>
  <Container
    title="Fade"
    file="src/routes/transitions/fade.svelte">
    <div class="flex flex-col items-center gap-6 bg-sky-600 p-10 text-center">
      <Badge variant="info">This is a transition that is applied to the component directly</Badge>
      <div class="flex flex-col gap-3">
        This is a transition that is applied to the component directly:
        <InlineCode text={"<div in:fade={{ duration: 300 }}>..</div>"} />
      </div>
    </div>
  </Container>
</div>


================================================
FILE: demo/src/routes/transitions/slide.svelte
================================================
<script>
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";
  import { slide } from "svelte/transition";
</script>

<div in:slide={{ duration: 500 }}>
  <Container
    title="Slide"
    file="src/routes/transitions/slide.svelte">
    <div class="flex flex-col items-center gap-6 bg-indigo-600 p-10 text-center">
      <Badge variant="info">This is a transition that is applied to the component directly</Badge>
      <div class="flex flex-col gap-3">
        This is a transition that is applied to the component directly:
        <InlineCode text={"<div in:fade={{ duration: 300 }}>..</div>"} />
      </div>
    </div>
  </Container>
</div>


================================================
FILE: demo/src/routes/transitions/transitions.svelte
================================================
<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance, type Route } from "@mateothegreat/svelte5-router";
  import Fade from "./fade.svelte";
  import Slide from "./slide.svelte";

  let { route } = $props();
  let router: RouterInstance = $state();

  const routes: Route[] = [
    {
      component: snippet
    },
    {
      path: "fade",
      component: Fade,
      props: {
        file: "src/routes/transitions/fade.svelte"
      }
    },
    {
      path: "slide",
      component: Slide,
      props: {
        file: "src/routes/transitions/slide.svelte"
      }
    }
  ];
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/transitions/transitions.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/transitions"
  {route}
  end={true}
  title={{
    content:
      "Demo to show how to use transitions with the router (spoiler: they're applied at the content level rather than within the router itself)."
  }}
  links={[
    {
      href: "/transitions",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/transitions/fade",
      label: "/transitions/fade"
    },
    {
      href: "/transitions/slide",
      label: "/transitions/slide"
    }
  ]}>
  <Router
    id="transitions-router"
    basePath="/transitions"
    bind:instance={router}
    {routes}
    {...myDefaultRouterConfig} />
</RouteWrapper>


================================================
FILE: demo/src/vite-env.d.ts
================================================
/// <reference types="svelte" />
/// <reference types="vite/client" />

import type { CompilerConfig } from "@mateothegreat/svelte5-router";

interface ImportMetaEnv {
  SPA_ROUTER: CompilerConfig;
}


================================================
FILE: demo/svelte.config.js
================================================
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

export default {
  preprocess: vitePreprocess(),
  vitePlugin: {
    inspector: {
      toggleKeyCombo: "alt-x",
      showToggleButton: "always",
      toggleButtonPos: "top-right"
    }
  }
};


================================================
FILE: demo/tailwind.config.ts
================================================

/** @type {import('tailwindcss').Config} */
const config = {
  darkMode: ["class"],
  content: ["./src/**/*.{html,js,svelte,ts}", "../../src/**/*.{html,js,svelte,ts}"],
  safelist: ["dark"],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px"
      }
    },
    extend: {
      colors: {
        border: "hsl(var(--border) / <alpha-value>)",
        input: "hsl(var(--input) / <alpha-value>)",
        ring: "hsl(var(--ring) / <alpha-value>)",
        background: "hsl(var(--background) / <alpha-value>)",
        foreground: "hsl(var(--foreground) / <alpha-value>)",
        primary: {
          DEFAULT: "hsl(var(--primary) / <alpha-value>)",
          foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
          foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
          foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
        },
        muted: {
          DEFAULT: "hsl(var(--muted) / <alpha-value>)",
          foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
        },
        accent: {
          DEFAULT: "hsl(var(--accent) / <alpha-value>)",
          foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
        },
        popover: {
          DEFAULT: "hsl(var(--popover) / <alpha-value>)",
          foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
        },
        card: {
          DEFAULT: "hsl(var(--card) / <alpha-value>)",
          foreground: "hsl(var(--card-foreground) / <alpha-value>)"
        }
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)"
      },
      fontFamily: {
        sans: ["Inter"]
      }
    }
  }
};
export default config;


================================================
FILE: demo/tsconfig.deployed.json
================================================
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "compilerOptions": {
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "sourceMap": true,
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": true,
    "isolatedModules": true,
    "strict": true,
    "noEmit": true,
    "moduleResolution": "bundler"
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.svelte"
  ]
}

================================================
FILE: demo/tsconfig.json
================================================
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "compilerOptions": {
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "sourceMap": true,
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": true,
    "isolatedModules": true,
    "strict": true,
    "noEmit": true,
    "moduleResolution": "bundler",
    "paths": {
      "@mateothegreat/svelte5-router": [
        "../src/lib/index"
      ],
      "@mateothegreat/svelte5-router/*": [
        "../src/lib/*"
      ],
      "$lib": [
        "./src/lib"
      ],
      "$lib/*": [
        "./src/lib/*"
      ],
      "$routes": [
        "./src/routes"
      ],
      "$routes/*": [
        "./src/routes/*"
      ]
    }
  },
  "include": [
    "./src/**/*",
    "./../src/**/*"
  ]
}

================================================
FILE: demo/vercel.json
================================================
{
  "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }],
  "github": {
    "enabled": false
  }
}


================================================
FILE: demo/vite.config.ts
================================================
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { svelteInspector } from "@sveltejs/vite-plugin-svelte-inspector";

import tailwindcss from "@tailwindcss/vite";

import path from "path";

import { defineConfig } from "vite";

import { vitePluginVersionMark } from "vite-plugin-version-mark";

import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    tsconfigPaths(),
    svelte(),
    svelteInspector({
      toggleKeyCombo: "alt-x",
      showToggleButton: "always",
      toggleButtonPos: "bottom-left"
    }),
    tailwindcss(),
    vitePluginVersionMark({
      // name: 'test-app',
      // version: '0.0.1',
      // command: 'git describe --tags',
      name: "svelte5-router",
      ifGitSHA: true,
      ifShortSHA: true,
      ifMeta: true,
      ifLog: true,
      ifGlobal: true
    })
  ],
  build: {
    sourcemap: true
  },
  define: {
    /** @type {import('@mateothegreat/svelte5-router').runtime.Config} */
    /**
     * The (optional) router package configuration for the compiler.
     */
    "import.meta.env.SPA_ROUTER": {
      /**
       * If enabled, tracing will be enabled providing rich tracing capabilities.
       */
      tracing: {
        level: 3,
        enabled: true,
        console: true
      },
      /**
       * The logging configuration for the router.
       */
      logging: {
        /**
         * The logging level that is applied.
         */
        level: 4,
        /**
         * Whether to log the trace to the browser console (optional).
         */
        console: false,
        /**
         * This method is called when a new trace is created (optional).
         *
         * You could use this to send the trace to a remote server, or store it
         * in a local database.
         *
         * This example uses a promise in the event that you are needing to
         * use async functionality.
         */
        sink: async (trace: any) => {
          await new Promise((resolve) => {
            console.log(trace);
            resolve(void 0);
          });
        }
      }
    }
  },
  resolve: {
    /**
     * This is only needed for the demo environment.
     *
     * It is not needed for including the router package in your project.
     */
    alias: {
      $lib: path.resolve(__dirname, "./src/lib"),
      $routes: path.resolve(__dirname, "./src/routes"),
      "@mateothegreat/svelte5-router": path.resolve(__dirname, "../src/lib")
    }
  }
});


================================================
FILE: docs/CNAME
================================================
docs.router.svelte.spa

================================================
FILE: docs/actions.md
================================================
# Actions

The 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.

## Available Actions

| Action | Description |
|--------|-------------|
| [`route`](#route) | Manages both navigation and active states of links. |
| [`active`](#active) | Handles active state management for styling links. |  

## Examples

### Basic Navigation Link

```svelte
<a href="/home" use:route>Home</a>
```

### Active State with Multiple Classes

```svelte
<a 
  href="/profile" 
  use:route={{
    default: { class: ['text-gray-600', 'hover:text-gray-900'] },
    active: { class: ['text-blue-600', 'font-bold'] }
  }}
>
  Profile
</a>
```

### Exact Path Matching

```svelte
<a 
  href="/settings" 
  use:route={{
    active: {
      class: 'active-link',
      absolute: true // Only active when path exactly matches /settings
    }
  }}
>
  Settings
</a>
```

### Query String Sensitive Navigation

```svelte
<a 
  href="/search?type=users" 
  use:route={{
    active: {
      class: 'active-search',
      querystring: true // Only active when querystring matches exactly
    }
  }}
>
  User Search
</a>
```

### Notes

- The `route` action automatically prevents default link behavior and handles navigation through the History API.
- When using `active`, you'll need to handle navigation separately if needed.
- Classes are applied dynamically based on the current route state.
- The `absolute` option is useful for preventing parent routes from being marked as active when child routes are active.
- The `querystring` option allows for precise matching including query parameters.

---

## `route`

The `route` action is the primary action for handling routing in your application. It manages both navigation and active states of links.

```svelte
<a href="/dashboard" use:route>Dashboard</a>
```

The `route` action accepts an options object with the following configuration:

```typescript
{
  default?: {
    absolute?: boolean;
    querystring?: boolean;
    class?: string | string[];
  },
  active?: {
    absolute?: boolean;
    querystring?: boolean;
    class?: string | string[];
  },
  loading?: {
    absolute?: boolean;
    querystring?: boolean;
    class?: string | string[];
  },
  disabled?: {
    absolute?: boolean;
    querystring?: boolean;
    class?: string | string[];
  }
}
```

- `default`: Options applied when the route is inactive.
- `active`: Options applied when the route is active.
- `loading`: Options applied when the route is loading.
- `disabled`: Options applied when the route is disabled.

Each state accepts the following properties:

- `absolute`: When `true`, effects only apply on exact path matches.
- `querystring`: When `true`, effects only apply when querystring exactly matches.
- `class`: CSS class(es) to apply when the state is active.

Example with options:

```svelte
<a 
  href="/dashboard" 
  use:route={{
    default: { class: 'text-gray-600' },
    active: { 
      class: 'text-blue-600 font-bold',
      absolute: true 
    }
  }}
>
  Dashboard
</a>
```

## `active`

The `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.

```svelte
<a href="/dashboard" use:active>Dashboard</a>
```

The `active` action accepts a subset of the route options, focusing only on the active state configuration:

```typescript
{
  active?: {
    absolute?: boolean;
    querystring?: boolean;
    class?: string | string[];
  }
}
```

Example with options:

```svelte
<a 
  href="/dashboard" 
  use:active={{
    active: {
      class: ['text-blue-600', 'font-bold'],
      absolute: true,
      querystring: true
    }
  }}
>
  Dashboard
</a>
```


================================================
FILE: docs/assets/coverage.json
================================================
{"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"]}

================================================
FILE: docs/changelog.md
================================================
<div align="center">
<img src="tag.png" width="200" />
<h1><strong>@mateothegreat/svelte5-router</strong></h1>
<h3>📋 tl;dr changelog</h3>
<p>
<a href="https://github.com/mateothegreat/svelte5-router">github</a> •
<a href="https://www.npmjs.com/package/mateothegreat/svelte5-router">npm</a> •
<a href="https://svelte5-router.docs.matthewdavis.io">docs</a>
</p>
</div>

<div><em>Here are the last 100~ commits ✌️</em></div>

## [2.16.19](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.19) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.19`
- **Released**: August 29, 2025
- **Total Changes**: 2 commits

### 🚀 Features

-   Replace() helper ✨; closes #86  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`41801c1`](https://github.com/mateothegreat/svelte5-router/commit/41801c15b65e2b2478a6037a13c2bf4e8729fa3a))

-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`0fce87c`](https://github.com/mateothegreat/svelte5-router/commit/0fce87c345e53b4965a0199de33de23fbbf36f3f))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)
## [2.16.18](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.18) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.18`
- **Released**: August 29, 2025
- **Total Changes**: 3 commits

### 🚀 Features

-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`f9258d2`](https://github.com/mateothegreat/svelte5-router/commit/f9258d2ca6ee55ebecdd3b8c11776e498142a7f7))

-   Pop() helper ✨  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`ea1a2b9`](https://github.com/mateothegreat/svelte5-router/commit/ea1a2b98bb95382cb3e50ebef4fae7c03f96ca03))

### 📋 Other Changes

-   :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))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)
## [2.16.17](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.17) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.17`
- **Released**: August 29, 2025
- **Total Changes**: 1 commit

### 🔧 Maintenance

-   Fix package.json versioning  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`da3bf33`](https://github.com/mateothegreat/svelte5-router/commit/da3bf3346faf4e4dd517777dcf89dba517319577))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)
## [2.16.16](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.16) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.16`
- **Released**: August 29, 2025
- **Total Changes**: 1 commit

### 🔧 Maintenance

-   Fix package.json versioning  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`38f81f2`](https://github.com/mateothegreat/svelte5-router/commit/38f81f2bdeb329a84198219c4d363420bca10e52))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)
## [2.16.15](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.15) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.15`
- **Released**: August 29, 2025
- **Total Changes**: 1 commit

### 🔧 Maintenance

-   Cleanup cicd  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`d2bf150`](https://github.com/mateothegreat/svelte5-router/commit/d2bf150aa6d8b7e799ac7258f42e34ed805db38d))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)
## [2.16.14](https://github.com/mateothegreat/svelte5-router/releases/tag/2.16.14) - 2025-08-29

### 📊 Release Summary
- **Version**: `2.16.14`
- **Released**: August 29, 2025
- **Total Changes**: 7 commits

### 🐛 Bug Fixes

-   Fix esm exports  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`13e2c5e`](https://github.com/mateothegreat/svelte5-router/commit/13e2c5ebb862d6e7a0b93ff4b3ba40211209b810))

### 📦 Dependencies

- **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))

- **deps-dev**:   Bump esbuild  by [@dependabot[bot]](https://github.com/dependabot[bot])  ([`d879c2c`](https://github.com/mateothegreat/svelte5-router/commit/d879c2c4a8f4cf96ce64bccc5924f7c62a4ca254))

### 📋 Other Changes

-   Merge pull request #107 from mateothegreat/dependabot/npm_and_yarn/test/app/npm_and_yarn-037d2236c4

chore(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))

-   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))

-   Merge pull request #111 from mateothegreat/dependabot/npm_and_yarn/npm_and_yarn-c8c34112a0

chore(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))

-   Create dependabot.yml  by [@Matthew Davis](https://github.com/Matthew Davis)  ([`9203170`](https://github.com/mateothegreat/svelte5-router/commit/92031701dc52f71ecae7e987662a67c20909f7f0))

### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

- [@mateothegreat](https://github.com/mateothegreat)

- [@dependabot[bot]](https://github.com/dependabot[bot])


================================================
FILE: docs/cliff.toml
================================================
# https://git-cliff.org/docs/integration/github/

[changelog]
trim = true
render_always = true
header = """
{%- macro remote_url() -%}
  https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}

{%- macro npm_url() -%}
  https://www.npmjs.com/package/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}

{%- macro documentation_url() -%}
  https://{{ remote.github.repo }}.docs.matthewdavis.io
{%- endmacro -%}

<div align="center">
  <img src="tag.png" width="200" />
  <h1><strong>@mateothegreat/{{ remote.github.repo }}</strong></h1>
  <h3>📋 tl;dr changelog</h3>
  <p>
    <a href="{{ self::remote_url() }}">github</a> •
    <a href="{{ self::npm_url() }}">npm</a> •
    <a href="{{ self::documentation_url() }}">docs</a>
  </p>
</div>

<div><em>Here are the last 100~ commits ✌️</em></div>

"""

body = """

{%- macro remote_url() -%}
  https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}

{%- macro npm_url() -%}
  https://www.npmjs.com/package/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}

{%- macro documentation_url() -%}
  https://docs.router.svelte.spa
{%- endmacro -%}

{% if version %}\
    ## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/releases/tag/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
    ## [Unreleased]({{ self::remote_url() }}/compare/HEAD...main)
{% endif %}\

{% if version %}
### 📊 Release Summary
- **Version**: `{{ version | trim_start_matches(pat="v") }}`
- **Released**: {{ timestamp | date(format="%B %d, %Y") }}
- **Total Changes**: {{ commits | length }} commit{% if commits | length != 1 %}s{% endif %}
{% endif %}

{% for group, commits in commits | group_by(attribute="group") %}
  ### {{ group | striptags | trim | upper_first }}

  {% for commit in commits %}
    - {% if commit.scope %}**{{ commit.scope }}**: {% endif %} \
      {% if commit.breaking %}⚠️ **BREAKING**: {% endif %} \
      {{ commit.message | upper_first | trim }} \
      {% if commit.author %} by [@{{ commit.author.name }}](https://github.com/{{ commit.author.name }}) {% endif %} \
      ([`{{ commit.id | truncate(length=7, end="") }}`]({{ self::remote_url() }}/commit/{{ commit.id }}))
  {% endfor %}
{% endfor %}

{% if github.contributors | length != 0 %}
### 👥 Contributors

Thanks ❤️‍🔥 to the following people who contributed to this release:

{% for contributor in github.contributors %}
{% if contributor.username %}
- [@{{ contributor.username }}](https://github.com/{{ contributor.username }})
{%- endif %}
{%- endfor %}
{% endif %}
"""

# Footer with useful links and metadata
footer = """
---

<div align="center">
  <sub>
    generated by <a href="https://git-cliff.org">git-cliff</a> 💜 • 
    <a href="{{ self::remote_url() }}/issues">report Issues</a> • 
    <a href="{{ self::remote_url() }}/discussions">join discussions</a>
  </sub>
</div>
"""

# Replace placeholders with actual URLs
postprocessors = [
    { pattern = '<REPO>', replace = "https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}" },
    # Clean up multiple blank lines
    { pattern = '\n\n\n+', replace = "\n\n" },
]

[git]

conventional_commits = true
filter_unconventional = false
require_conventional = false
use_branch_tags = false
split_commits = false
limit_commits = 20
sort_commits = "newest"
topo_order = false
topo_order_commits = true
recurse_submodules = true

# Preprocess commits to add GitHub links
commit_preprocessors = [
    # Link to GitHub issues
    { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/issues/${2}))" },
    # Link to PRs
    { pattern = 'PR #([0-9]+)', replace = "[PR #${1}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/pull/${1})" },
]

# Protect breaking changes from being filtered
protect_breaking_commits = true

# Enhanced commit parsers with more categories
commit_parsers = [
    # Core features
    { message = "^feat", group = "<!-- 00 -->🚀 Features" },
    { message = "^feature", group = "<!-- 00 -->🚀 Features" },
    
    # Bug fixes
    { message = "^fix", group = "<!-- 01 -->🐛 Bug Fixes" },
    { message = "^bugfix", group = "<!-- 01 -->🐛 Bug Fixes" },
    
    # Performance improvements
    { message = "^perf", group = "<!-- 02 -->⚡ Performance" },
    { message = "^optimize", group = "<!-- 02 -->⚡ Performance" },
    
    # Code refactoring
    { message = "^refactor", group = "<!-- 03 -->♻️ Refactoring" },
    { message = "^refact", group = "<!-- 03 -->♻️ Refactoring" },
    
    # Documentation
    { message = "^docs?", group = "<!-- 04 -->📚 Documentation" },
    { message = "^readme", group = "<!-- 04 -->📚 Documentation" },
    
    # Testing
    { message = "^test", group = "<!-- 05 -->🧪 Testing" },
    { message = "^spec", group = "<!-- 05 -->🧪 Testing" },
    { message = "^e2e", group = "<!-- 05 -->🧪 Testing" },
    
    # Styling
    { message = "^style", group = "<!-- 06 -->🎨 Styling" },
    { message = "^css", group = "<!-- 06 -->🎨 Styling" },
    { message = "^ui", group = "<!-- 06 -->🎨 Styling" },
    
    # Dependencies
    { message = "^deps", group = "<!-- 07 -->📦 Dependencies" },
    { message = "^dep", group = "<!-- 07 -->📦 Dependencies" },
    { message = "update.*dependencies", group = "<!-- 07 -->📦 Dependencies" },
    
    # Build & CI
    { message = "^build", group = "<!-- 08 -->🏗️ Build System" },
    { message = "^ci", group = "<!-- 08 -->🏗️ Build System" },
    { message = "^pipeline", group = "<!-- 08 -->🏗️ Build System" },
    
    # Security
    { body = ".*security", group = "<!-- 09 -->🔒 Security" },
    { message = "^security", group = "<!-- 09 -->🔒 Security" },
    { message = "^vuln", group = "<!-- 09 -->🔒 Security" },
    
    # Breaking changes
    { body = ".*BREAKING CHANGE.*", group = "<!-- 10 -->💥 Breaking Changes" },
    { message = "^breaking", group = "<!-- 10 -->💥 Breaking Changes" },
    
    # Reverts
    { message = "^revert", group = "<!-- 11 -->⏪ Reverts" },
    
    # Types
    { message = "^types?", group = "<!-- 12 -->🏷️ Types" },
    { message = "^typing", group = "<!-- 12 -->🏷️ Types" },
    
    # Chores (but only important ones)
    { message = "^chore\\(release\\)", skip = true },
    { message = "^chore\\(deps\\)", skip = true },
    { message = "^chore\\(deps-dev\\)", skip = true },
    { message = "^chore", group = "<!-- 13 -->🔧 Maintenance" },
    
    # Examples
    { message = "^example", group = "<!-- 14 -->📝 Examples" },
    { message = "^demo", group = "<!-- 14 -->📝 Examples" },
    
    # Everything else
    { message = ".*", group = "<!-- 15 -->📋 Other Changes" },
]

# Enhanced link parsers
link_parsers = [
    # Parse GitHub usernames
    { pattern = "@([a-zA-Z0-9_-]+)", href = "https://github.com/${1}" },
    # Parse commit SHAs
    { pattern = "([a-f0-9]{7})[a-f0-9]{33}", href = "https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/commit/${0}" },
]


================================================
FILE: docs/debugging.md
================================================
# Debugging

There are a few way to debug things.

## Debug Logger

In your `vite.config.ts` file, you can add the following:

```ts
export default defineConfig({
  plugins: [svelte()],
  build: {
    sourcemap: true // If you want to use a debugger, add this!
  },
  define: {
    // Tell the router to log when we're in debug mode.
    // Otherwise, this statement is removed by the compiler (known as tree-shaking)
    // and all subsequent log statements are removed at build time:
    'import.meta.env.SPA_ROUTER': {
      logLevel: "debug"
    },
  }
});
```

This allows us to log when we're in debug mode otherwise
statements like this are removed by the compiler (known astree-shaking):

```ts
if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === "debug") {
  log.debug(this.config.id, "unregistered router instance", {
    id: this.config.id,
    routes: this.routes.size
  });
}
```

Putting it all together:

```svelte
<script lang="ts">
  import { Router, type RouterInstance } from "@mateothegreat/svelte5-router";

  let instance: RouterInstance;

  if (import.meta.env.SPA_ROUTER && import.meta.env.SPA_ROUTER.logLevel === "debug") {
    log.debug(instance.id, "dumping routes", {
      config: instance.config,
      routes: instance.routes,
      current: instance.current,
      navigating: instance.navigating
    });
  }
</script>

<Router bind:instance {routes}>
```

Example output:

![debug](./assets/debugging-logger.png)


================================================
FILE: docs/diagrams/component-hierarchy.mmd
================================================
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#2563eb',
    'primaryTextColor': '#fff',
    'primaryBorderColor': '#1e40af',
    'lineColor': '#64748b',
    'secondaryColor': '#4ade80',
    'tertiaryColor': '#f472b6'
  }
}}%%
graph TD
    A[Router Component] -->|Creates| B[RouterInstance]
    B -->|Registers with| C[Registry]
    B -->|Manages| D[Route Collection]
    D -->|Contains| E[Route Configurations]
    E -->|Has| F[Path Patterns]
    E -->|Has| G[Query Patterns]
    E -->|Has| H[Hooks]
    E -->|Renders| I[Route Components]
    
    style A fill:#3b82f6,stroke:#1d4ed8
    style B fill:#4ade80,stroke:#16a34a
    style C fill:#f472b6,stroke:#db2777
    style D fill:#4ade80,stroke:#16a34a
    style E fill:#f472b6,stroke:#db2777 


================================================
FILE: docs/diagrams/route-evaluations.mmd
================================================
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#2563eb',
    'primaryTextColor': '#fff',
    'primaryBorderColor': '#1e40af',
    'lineColor': '#64748b',
    'secondaryColor': '#4ade80',
    'tertiaryColor': '#f472b6'
  }
}}%%
flowchart TD
    A[Route Evaluation Start] --> B{Check Path Type}
    B -->|String| C[Direct Match]
    B -->|RegExp| D[Pattern Match]
    B -->|Function| E[Custom Match]
    
    C --> F{Path Matches?}
    D --> F
    E --> F
    
    F -->|Yes| G[Check Query Parameters]
    F -->|No| H[Try Next Route]
    
    G --> I{Query Matches?}
    I -->|Yes| J[Create Route Result]
    I -->|No| H
    
    H --> K{More Routes?}
    K -->|Yes| B
    K -->|No| L[Use Default Route]
    
    J --> M[Execute Pre Hooks]
    L --> M
    
    style A fill:#3b82f6,stroke:#1d4ed8
    style B fill:#4ade80,stroke:#16a34a
    style F fill:#f472b6,stroke:#db2777
    style I fill:#f472b6,stroke:#db2777
    style J fill:#4ade80,stroke:#16a34a
    style M fill:#3b82f6,stroke:#1d4ed8 


================================================
FILE: docs/diagrams/router-architecture.mmd
================================================
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#2563eb',
    'primaryTextColor': '#fff',
    'primaryBorderColor': '#1e40af',
    'lineColor': '#64748b',
    'secondaryColor': '#4ade80',
    'tertiaryColor': '#f472b6'
  }
}}%%
graph TB
    A[Browser URL Change] -->|Triggers| B[RouterInstance]
    B -->|Registers| C[Registry]
    B -->|Evaluates| D[Route Matching]
    D -->|Checks| E[Path Matching]
    D -->|Checks| F[Query Matching]
    E --> G[Route Resolution]
    F --> G
    G -->|Pre Hooks| H[Route Guards]
    H -->|Success| I[Component Rendering]
    H -->|Failure| J[Redirect/Deny]
    I -->|Post Hooks| K[Final Render]
    
    style A fill:#3b82f6,stroke:#1d4ed8
    style B fill:#4ade80,stroke:#16a34a
    style C fill:#f472b6,stroke:#db2777
    style D fill:#4ade80,stroke:#16a34a
    style G fill:#f472b6,stroke:#db2777
    style H fill:#3b82f6,stroke:#1d4ed8
    style I fill:#4ade80,stroke:#16a34a 


================================================
FILE: docs/diagrams/routing-lifecycle.mmd
================================================
%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#2563eb',
    'primaryTextColor': '#fff',
    'primaryBorderColor': '#1e40af',
    'lineColor': '#64748b',
    'secondaryColor': '#4ade80',
    'tertiaryColor': '#f472b6'
  }
}}%%
sequenceDiagram
    participant U as User/Browser
    participant R as Router
    participant RI as RouterInstance
    participant RG as Registry
    participant H as Hooks
    participant C as Component

    U->>R: URL Change
    R->>RG: Check Registration
    RG-->>R: Return Instance
    R->>RI: Handle State Change
    RI->>RI: Evaluate Route
    RI->>H: Execute Pre-Hooks
    alt Hook Success
        H-->>RI: Continue
        RI->>C: Render Component
        RI->>H: Execute Post-Hooks
        H-->>RI: Complete
    else Hook Failure
        H-->>RI: Abort/Redirect
        RI->>U: Navigate Away
    end 


================================================
FILE: docs/diagrams.md
================================================
# Router Architecture Diagrams

This 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.

## 1. Router Architecture

Shows the high-level architecture of the router, including how URL changes flow through the system to component rendering.

<div align="center">
  <img src="./diagrams/router-architecture.png" alt="Router Architecture" />
</div>

## 2. Routing Lifecycle

A sequence diagram showing the temporal flow of routing operations from URL change to final render.

<div align="center">
  <img src="./diagrams/routing-lifecycle.png" alt="Routing Lifecycle" />
</div>

## 3. Route Evaluation

A detailed flowchart showing how routes are evaluated, including path matching, query parameter checking, and hook execution.

<div align="center">
  <img src="./diagrams/route-evaluations.png" alt="Route Evaluations" />
</div>

## 4. Component Hierarchy

Shows the relationship between different components in the router system.

<div align="center">
  <img src="./diagrams/component-hierarchy.png" alt="Component Hierarchy" />
</div>


================================================
FILE: docs/getting-started.md
================================================
# Getting Started

## Installation

```bash
npm install @mateothegreat/svelte5-router
```

## Usage

In your `app.svelte` file, you can use the `Router` component to render your routes:

```svelte
<script lang="ts">
  import { Router, type RouteConfig } from "@mateothegreat/svelte5-router";

  const routes: RouteConfig[] = [
    {
      component: Home
    }
    {
      path: "products",
      component: Products
    },
    {
      path: "settings",
      component: Settings
    }
  ];
</script>

<Router {routes} />
```

When you navigate to the root route, the `Home` component will be rendered.

When you navigate to the `/products` route, the `Products` component will be rendered.

When you navigate to the `/settings` route, the `Settings` component will be rendered.


================================================
FILE: docs/helpers.md
================================================
# Helpers

There are a few helpers that are available to you when using the router.

## `goto(path: string, queryParams?: Record<string, string>)`

Navigates to the given path by calling `goto("/path")`.

Example:

```ts
import { goto } from "@mateothegreat/svelte5-router";

goto("/foo", { bar: "baz" });
```

This will navigate to `/foo?bar=baz`.

## `pop(delta?: number)`

Navigates backwards in the browser history N pages.

Example:

```ts
import { pop } from "@mateothegreat/svelte5-router";

pop(); // Navigate back 1 page
pop(2); // Navigate back 2 pages
```

## `replace(path: string, queryParams?: Record<string, unknown>)`

Navigates to the given path by calling `replace("/path")` and replacing the current browser history entry instead of adding a new one.

Example:

```ts
import { replace } from "@mateothegreat/svelte5-router";

replace("/foo"); // Navigates to "/foo"
replace("/foo", { bar: "baz" }); // Navigates to "/foo?bar=baz"
```

## `query(key: string): string | null`

Returns the value of the query parameter for the given key or null if the key does not exist.

## The `QueryString` class

A helper class for working with the query string.

> Check it out at [src/lib/query.svelte.ts](../src/lib/query.svelte.ts) and start using it now!

Basic usage:

```ts
import { QueryString } from "@mateothegreat/svelte5-router";

const query = new QueryString();

query.get("foo", "bar"); // "bar"
query.set("baz", "qux");
query.toString(); // "foo=bar&baz=qux"
```

Using it with navigation:

```ts
import { QueryString } from "@mateothegreat/svelte5-router";

const query = new QueryString();

// ...
query.set("foo", "baz");
query.set("baz", "qux");
// ...

query.goto("/test"); // Navigates to "/test?foo=baz&baz=qux"
```

You can also pass a query object to the `goto` method:

```ts
goto("/test", { foo: "baz" }); // Navigates to "/test?foo=baz"
```


================================================
FILE: docs/hooks.md
================================================
# Routing Hooks

 | Order | Event  | Scope       | Description                                |
 | ----- | ------ | ----------- | ------------------------------------------ |
 | 1.    | `pre`  | `<Router/>` | Always runs *before* a route is attempted. |
 | 2.    | `pre`  | `Route`     | Runs *before* the route is rendered.       |
 | 3.    | `post` | `Route`     | Runs *after* the route is rendered.        |
 | 4.    | `post` | `<Router/>` | Always runs *after* a route is rendered.   |

```ts
import { goto, type RouteResult } from "@mateothegreat/svelte5-router";

export const authGuard = async (route: RouteResult): Promise<boolean> => {
  console.log("simulating some login/auth check...");
  // Crude example of checking if the user is logged in. A more
  // sophisticated example would use a real authentication system
  // and a server-side API.
  if (!localStorage.getItem("token")) {
    console.warn("redirecting to denied");
    goto("/protected/denied");
    return false;
  }
  return true;
}

const globalPostHook1 = (route: RouteResult): boolean => {
  console.warn("globalPostHook1", route);
  // Return true so that the route can continue down its evaluation path.
  return true;
};

const globalPostHook2 = async (route: RouteResult): Promise<boolean> => {
  console.warn("globalPostHook2", route);
  // Return true so that the route can continue down its evaluation path.
  return true;
};
```

You can pass an array or single method for the `pre` and `post` hooks and you can
also mix and match asynchronous and synchronous hooks.

```svelte
<Router
  {routes}
  hooks={{
    pre: authGuard,
    post: [
      globalPostHook1,
      globalPostHook2
    ]
  }}
/>
```


================================================
FILE: docs/llms.txt
================================================
Directory Structure:

└── ./
    ├── .github
    │   └── ISSUE_TEMPLATE
    │       ├── bug_report.md
    │       └── feature_request.md
    ├── demo
    │   ├── cypress
    │   │   ├── e2e
    │   │   │   └── route-activation.cy.ts
    │   │   └── support
    │   │       ├── commands.ts
    │   │       └── e2e.ts
    │   ├── src
    │   │   ├── lib
    │   │   │   ├── components
    │   │   │   │   ├── routes
    │   │   │   │   │   ├── route-link.svelte
    │   │   │   │   │   ├── route-title.svelte
    │   │   │   │   │   └── route-wrapper.svelte
    │   │   │   │   ├── badge.svelte
    │   │   │   │   ├── code.svelte
    │   │   │   │   ├── container.svelte
    │   │   │   │   ├── default.svelte
    │   │   │   │   ├── file-link.svelte
    │   │   │   │   └── inline-code.svelte
    │   │   │   ├── default-route-config.ts
    │   │   │   ├── router-history.ts
    │   │   │   └── session.svelte.ts
    │   │   ├── routes
    │   │   │   ├── extras
    │   │   │   │   ├── dump.svelte
    │   │   │   │   ├── extras.svelte
    │   │   │   │   └── passing-down-props.svelte
    │   │   │   ├── hash
    │   │   │   │   └── hash.svelte
    │   │   │   ├── nested
    │   │   │   │   ├── level-1
    │   │   │   │   │   ├── level-2
    │   │   │   │   │   │   ├── level-3
    │   │   │   │   │   │   │   └── level-3.svelte
    │   │   │   │   │   │   └── level-2.svelte
    │   │   │   │   │   └── level-1.svelte
    │   │   │   │   └── nested.svelte
    │   │   │   ├── paths-and-params
    │   │   │   │   ├── custom-not-found.svelte
    │   │   │   │   ├── display-params.svelte
    │   │   │   │   ├── paths-and-params.svelte
    │   │   │   │   └── querystring-matching.svelte
    │   │   │   ├── patterns
    │   │   │   │   ├── dump.svelte
    │   │   │   │   ├── output.svelte
    │   │   │   │   ├── parameter-extraction.svelte
    │   │   │   │   └── patterns.svelte
    │   │   │   ├── protected
    │   │   │   │   ├── manage-account
    │   │   │   │   │   ├── auth-guard-fast.ts
    │   │   │   │   │   ├── auth-guard-slow.ts
    │   │   │   │   │   ├── balance.svelte
    │   │   │   │   │   ├── home.svelte
    │   │   │   │   │   ├── manage-account.svelte
    │   │   │   │   │   └── worker-client.svelte.ts
    │   │   │   │   ├── account-state.svelte.ts
    │   │   │   │   ├── denied.svelte
    │   │   │   │   ├── login.svelte
    │   │   │   │   └── main.svelte
    │   │   │   ├── transitions
    │   │   │   │   ├── fade.svelte
    │   │   │   │   ├── slide.svelte
    │   │   │   │   └── transitions.svelte
    │   │   │   ├── delayed.svelte
    │   │   │   ├── home.svelte
    │   │   │   └── not-found.svelte
    │   │   ├── app.css
    │   │   ├── app.svelte
    │   │   ├── main.ts
    │   │   └── vite-env.d.ts
    │   ├── cypress.config.ts
    │   ├── index.html
    │   ├── svelte.config.ts
    │   ├── tailwind.config.ts
    │   └── vite.config.ts
    ├── docs
    │   ├── actions.md
    │   ├── changelog.md
    │   ├── debugging.md
    │   ├── getting-started.md
    │   ├── helpers.md
    │   ├── hooks.md
    │   ├── props.md
    │   ├── readme.md
    │   ├── registry.md
    │   ├── routing-patterns.md
    │   ├── routing.md
    │   ├── statuses.md
    │   └── styling.md
    ├── src
    │   ├── lib
    │   │   ├── actions
    │   │   │   ├── active.svelte.ts
    │   │   │   ├── apply-classes.ts
    │   │   │   ├── index.ts
    │   │   │   ├── options.ts
    │   │   │   └── route.svelte.ts
    │   │   ├── helpers
    │   │   │   ├── evaluators.test.ts
    │   │   │   ├── evaluators.ts
    │   │   │   ├── goto.ts
    │   │   │   ├── identify.ts
    │   │   │   ├── index.ts
    │   │   │   ├── logging.ts
    │   │   │   ├── marshal.test.ts
    │   │   │   ├── marshal.ts
    │   │   │   ├── normalize.ts
    │   │   │   ├── objects.ts
    │   │   │   ├── query.ts
    │   │   │   ├── regexp.ts
    │   │   │   ├── runtime.ts
    │   │   │   ├── tracing.svelte.ts
    │   │   │   ├── urls.test.ts
    │   │   │   └── urls.ts
    │   │   ├── hash.test.ts
    │   │   ├── hash.ts
    │   │   ├── hooks.ts
    │   │   ├── index.ts
    │   │   ├── path.ts
    │   │   ├── query.svelte.ts
    │   │   ├── query.test.ts
    │   │   ├── registry.svelte.ts
    │   │   ├── route.svelte.ts
    │   │   ├── router-instance-config.ts
    │   │   ├── router-instance.svelte.ts
    │   │   ├── router.svelte
    │   │   ├── statuses.ts
    │   │   └── utilities.svelte.ts
    │   └── vite-env.d.ts
    ├── svelte.config.ts
    ├── vite.config.ts
    ├── vitest.config.ts
    └── vitest.setup.ts



---
File: /.github/ISSUE_TEMPLATE/bug_report.md
---

---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: mateothegreat

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Logs**
If available please provide any available logs, screenshots, etc.

**Additional context**
Add any other context about the problem here.



---
File: /.github/ISSUE_TEMPLATE/feature_request.md
---

---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: mateothegreat

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.



---
File: /demo/cypress/e2e/route-activation.cy.ts
---

/// <reference types="cypress" />

const routes = require("../fixtures/routes.json");
describe.only("route activation", () => {
  beforeEach(() => {
    cy.viewport(1500, 1500);
    cy.visit("http://localhost:8173");
  });

  routes.forEach((route) => {
    it(`should activate ${route.id} when visiting ${route.path}`, () => {
      cy.clickAndValidateActiveClasses(`a[href='${route.path}']`, "active", route.active);
    });
  });

  // it.only("displays two todo items by default", () => {
  //   // cy.get("a[href='/props']").should("have.length", 1).click();
  //   // cy.contains("props.svelte").should("exist");

  //   // cy.get("a[href='/props/foo']").should("have.length", 1).click();
  //   // cy.contains("display-params.svelte").should("exist");
  //   // cy.contains(`"child": "foo"`).should("exist");

  //   // cy.get("a").filter('[href^="/props/bar?"]').should("have.length", 1).click();
  //   // cy.contains("display-params.svelte").should("exist");
  //   // cy.contains(`"child": "bar"`).should("exist");

  //   // cy.get("a").filter('[href="/props/foo"]').should("not.have.class", "active");
  //   // cy.get("a").filter('[href^="/props/bar"]').should("have.class", "active");

  //   cy.clickAndValidateActiveClasses("a[href='/props']", "active");
  // });

  // it("can add new todo items", () => {
  //   // We'll store our item text in a variable so we can reuse it
  //   const newItem = "Feed the cat";

  //   // Let's get the input element and use the `type` command to
  //   // input our new list item. After typing the content of our item,
  //   // we need to type the enter key as well in order to submit the input.
  //   // This input has a data-test attribute so we'll use that to select the
  //   // element in accordance with best practices:
  //   // https://on.cypress.io/selecting-elements
  //   cy.get("[data-test=new-todo]").type(`${newItem}{enter}`);

  //   // Now that we've typed our new item, let's check that it actually was added to the list.
  //   // Since it's the newest item, it should exist as the last element in the list.
  //   // In addition, with the two default items, we should have a total of 3 elements in the list.
  //   // Since assertions yield the element that was asserted on,
  //   // we can chain both of these assertions together into a single statement.
  //   cy.get(".todo-list li")
  //     .should("have.length", 3)
  //     .last()
  //     .should("have.text", newItem);
  // });

  // it("can check off an item as completed", () => {
  //   // In addition to using the `get` command to get an element by selector,
  //   // we can also use the `contains` command to get an element by its contents.
  //   // However, this will yield the <label>, which is lowest-level element that contains the text.
  //   // In order to check the item, we'll find the <input> element for this <label>
  //   // by traversing up the dom to the parent element. From there, we can `find`
  //   // the child checkbox <input> element and use the `check` command to check it.
  //   cy.contains("Pay electric bill")
  //     .parent()
  //     .find("input[type=checkbox]")
  //     .check();

  //   // Now that we've checked the button, we can go ahead and make sure
  //   // that the list element is now marked as completed.
  //   // Again we'll use `contains` to find the <label> element and then use the `parents` command
  //   // to traverse multiple levels up the dom until we find the corresponding <li> element.
  //   // Once we get that element, we can assert that it has the completed class.
  //   cy.contains("Pay electric bill")
  //     .parents("li")
  //     .should("have.class", "completed");
  // });

  // context("with a checked task", () => {
  //   beforeEach(() => {
  //     // We'll take the command we used above to check off an element
  //     // Since we want to perform multiple tests that start with checking
  //     // one element, we put it in the beforeEach hook
  //     // so that it runs at the start of every test.
  //     cy.contains("Pay electric bill")
  //       .parent()
  //       .find("input[type=checkbox]")
  //       .check();
  //   });

  //   it("can filter for uncompleted tasks", () => {
  //     // We'll click on the "active" button in order to
  //     // display only incomplete items
  //     cy.contains("Active").click();

  //     // After filtering, we can assert that there is only the one
  //     // incomplete item in the list.
  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .first()
  //       .should("have.text", "Walk the dog");

  //     // For good measure, let's also assert that the task we checked off
  //     // does not exist on the page.
  //     cy.contains("Pay electric bill").should("not.exist");
  //   });

  //   it("can filter for completed tasks", () => {
  //     // We can perform similar steps as the test above to ensure
  //     // that only completed tasks are shown
  //     cy.contains("Completed").click();

  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .first()
  //       .should("have.text", "Pay electric bill");

  //     cy.contains("Walk the dog").should("not.exist");
  //   });

  //   it("can delete all completed tasks", () => {
  //     // First, let's click the "Clear completed" button
  //     // `contains` is actually serving two purposes here.
  //     // First, it's ensuring that the button exists within the dom.
  //     // This button only appears when at least one task is checked
  //     // so this command is implicitly verifying that it does exist.
  //     // Second, it selects the button so we can click it.
  //     cy.contains("Clear completed").click();

  //     // Then we can make sure that there is only one element
  //     // in the list and our element does not exist
  //     cy.get(".todo-list li")
  //       .should("have.length", 1)
  //       .should("not.have.text", "Pay electric bill");

  //     // Finally, make sure that the clear button no longer exists.
  //     cy.contains("Clear completed").should("not.exist");
  //   });
  // });
});



---
File: /demo/cypress/support/commands.ts
---

/// <reference types="cypress" />



---
File: /demo/cypress/support/e2e.ts
---

import "./commands";

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Custom command to check if navigation link is active based on href.
       *
       * @param selector - The selector to check against
       * @param attribute - The attribute to check against
       * @param value - The value to check against
       *
       * @example cy.hasClassByAttr('nav a', 'href', '/about', 'foo')
       */
      allowedClassesByAttr(selector: string, attribute: string, allowed: string | string[], className: string): Chainable<Element>;

      /**
       * Custom command to navigate to a specific path and validate the number of active elements.
       *
       * @param selector - The selector to check against
       * @param path - The path to navigate to
       * @param expected - The expected number of active elements
       *
       * @example cy.clickAndValidateActiveClasses("a[href='/props/foo']", "active", 1)
       */
      clickAndValidateActiveClasses(selector: string, path: string, expected: number): Chainable<Element>;
    }
  }
}

Cypress.Commands.add("allowedClassesByAttr", (selector: string, attribute: string, allowed: string | string[], className: string) => {
  cy.get(selector).then(($elements) => {
    $elements.each((_, $el) => {
      cy.wrap($el).then(($el) => {
        if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {
          if (Array.isArray(allowed) && !allowed.includes($el.attr(attribute) || "")) {
            throw new Error(`allowedClassesByAttr: ${attribute}="${$el.attr(attribute)}" has class "${className}" (allowed: "${allowed}")`);
          } else if ($el.attr(attribute) !== allowed && $el.hasClass(className)) {
            throw new Error(`allowedClassesByAttr: ${attribute}="${$el.attr(attribute)}" has class "${className}" (allowed: "${allowed}")`);
          }
        }
      });
    });
  });
});

Cypress.Commands.add("clickAndValidateActiveClasses", (selector: string, className: string, expected: number) => {
  cy.get(selector).then(($el) => {
    if ($el.length !== 1) {
      throw new Error(`clickAndValidateActiveClasses: ${selector} should match only 1 element, has ${$el.length}`);
    }
    cy.get(selector).click();
    cy.allowedClassesByAttr("nav a", "href", $el.attr("href") || "", className);
  });
});



---
File: /demo/src/lib/components/routes/route-link.svelte
---

<script lang="ts">
  import { session } from "$lib/session.svelte";
  import { route, RouteOptions } from "@mateothegreat/svelte5-router";

  export type RouteLinkProps = {
    options?: RouteOptions;
    href: string;
    label: string;
  };

  let { options, href, label }: RouteLinkProps = $props();
  if (!options) {
    options = new RouteOptions();
  }

  if (!options.active) {
    options.active = {
      class: ["active", "bg-indigo-600", "text-white", "border-indigo-400"]
    };
  }
  if (!options.default) {
    options.default = {
      class: ["inactive", "text-slate-300", "border-slate-500/50"]
    };
  }
  if (!options.loading) {
    options.loading = {
      class: ["loading", "bg-orange-500"]
    };
  }
  if (!options.disabled) {
    options.disabled = {
      class: ["disabled", "bg-gray-500"]
    };
  }

  if (!options.active.class) {
    options.active.class = ["active", "bg-indigo-600", "text-white", "border-indigo-400"];
  }

  if (!options.default.class) {
    options.default.class = ["inactive", "text-slate-300", "border-slate-500/50"];
  }
  if (!options.loading.class) {
    options.loading.class = ["loading", "bg-orange-500"];
  }
  if (!options.disabled.class) {
    options.disabled.class = ["disabled", "bg-gray-500"];
  }
</script>

<a
  use:route={options}
  href={session.mode === "hash" ? `/#${href}` : href}
  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">
  {#if session.mode === "hash"}
    <span>/#</span>
    <span>/{label.startsWith("/") ? label.slice(1) : label}</span>
  {:else}
    <span>
      {label}
    </span>
  {/if}
</a>



---
File: /demo/src/lib/components/routes/route-title.svelte
---

<script lang="ts">
  import type { RouteResult, RouterInstance } from "@mateothegreat/svelte5-router";
  import { ArrowDown, ArrowRight, ArrowRightFromLine, StopCircle } from "lucide-svelte";
  import FileLink from "../file-link.svelte";

  export type RouteTitleProps = {
    router?: RouterInstance;
    route?: RouteResult;
    file?: string;
    content?: any;
    end?: boolean;
  };

  let { router = $bindable(), route, file, content, end }: RouteTitleProps = $props();
</script>

<div class="flex flex-col gap-4">
  <div class="flex items-center gap-3 rounded-md bg-black/50 p-1.5 px-2 border-2">
    {#if router}
      <div class="flex flex-wrap items-center rounded-sm bg-gray-800 px-1.5 py-0.5 text-sm text-slate-500">
        <ArrowRightFromLine class="h-4 w-4 text-green-400 mr-1" />
        {router.config.id}
        {#if router.navigating}
          <span class="px-1 py-0.5 text-red-400">(hooks firing)</span>
        {:else}
          <span class="px-1 py-0.5 text-slate-600">(idle)</span>
        {/if}
        routed the path
        <span class="px-1 py-0.5 text-green-400">
          {route?.absolute?.()}
        </span>
        and nesting&nbsp;
        {#if end}
          <span class="flex items-center gap-1 whitespace-nowrap">
            <span class="text-red-400">stopped</span>
            <StopCircle class="h-4 w-4 text-red-400" />
          </span>
        {:else}
          <span class="flex items-center gap-1 whitespace-nowrap">
            <span class="text-green-400">continued</span>
            <ArrowDown class="h-4 w-4 text-green-400" />
          </span>
        {/if}
      </div>
    {/if}
    <ArrowRight class="h-4 w-4 text-slate-500" />
    <FileLink {file} />
  </div>
  {#if content}
    <div class="p-2">
      {#if typeof content === "string"}
        <div class="flex flex-col items-center gap-2 text-center text-slate-400">
          <div class="max-w-3xl text-sm text-slate-500">
            {content}
          </div>
        </div>
      {:else}
        <div class="flex items-center">
          {@render content()}
        </div>
      {/if}
    </div>
  {/if}
</div>



---
File: /demo/src/lib/components/routes/route-wrapper.svelte
---

<script lang="ts">
  import type { RouteResult, RouterInstance } from "@mateothegreat/svelte5-router";
  import { Anchor } from "lucide-svelte";
  import type { RouteLinkProps } from "./route-link.svelte";
  import RouteLink from "./route-link.svelte";
  import type { RouteTitleProps } from "./route-title.svelte";
  import RouteTitle from "./route-title.svelte";

  export type RouteWrapperProps = {
    router?: RouterInstance;
    name: string;
    route?: RouteResult;
    end?: boolean;
    title: RouteTitleProps;
    links: RouteLinkProps[];
    children?: any;
  };

  let { router = $bindable(), name, route, title, links, children, end = $bindable() }: RouteWrapperProps = $props();
</script>

<div class="flex flex-col gap-3 border-2 rounded-md h-full p-2.5">
  <div class="flex flex-col gap-4">
    <RouteTitle
      {router}
      {route}
      {end}
      file={title.file}
      content={title.content} />
    <div class="flex w-fit items-center gap-2 rounded-md border-2 border-slate-900/80 bg-slate-700/80 p-1.5">
      <p class="flex items-center gap-1 text-sm text-slate-300">
        <Anchor class="h-4 w-4 text-indigo-500" />
        <span class="text-pink-400">{router?.config.id}</span>
        routes:
      </p>
      {#each links as link}
        <RouteLink {...link} />
      {/each}
    </div>
  </div>
  <div class="flex-1">
    {@render children?.()}
  </div>
</div>



---
File: /demo/src/lib/components/badge.svelte
---

<script lang="ts">
  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "lucide-svelte";
  import type { Snippet } from "svelte";

  let {
    icon,
    text,
    variant = "info",
    class: className,
    children
  }: {
    icon?: any;
    text?: string;
    variant?: "info" | "success" | "warning" | "error";
    class?: string;
    children?: Snippet;
  } = $props();

  const variants = {
    info: {
      classes: "bg-blue-900 text-white",
      icon: InfoIcon
    },
    success: {
      classes: "bg-green-600/80 text-white",
      icon: CheckCircleIcon
    },
    warning: {
      classes: "bg-yellow-200/80 text-black",
      icon: AlertTriangleIcon
    },
    error: {
      classes: "bg-red-900/80 text-white",
      icon: XCircleIcon
    }
  };

  let Icon = icon ?? variants[variant].icon;
</script>

<span
  class="inline-flex items-center gap-1.5 rounded-sm p-2 text-sm font-medium {variants[variant].classes} {className}">
  {#if Icon}
    <Icon class="inline-block h-5 w-5" />
  {/if}
  {text}
  {@render children?.()}
</span>



---
File: /demo/src/lib/components/code.svelte
---

<script lang="ts">
  import { transformerColorizedBrackets } from "@shikijs/colorized-brackets";
  import { Code2 } from "lucide-svelte";
  import { createHighlighter, type Highlighter } from "shiki";
  import { onMount, type Snippet } from "svelte";
  import FileLink from "./file-link.svelte";

  let {
    title,
    file,
    class: className,
    children,
    language = "typescript",
    theme = "github-dark-dimmed"
  }: {
    title?: string;
    file?: string;
    class?: string;
    children?: Snippet;
    language?: string;
    theme?: string;
  } = $props();

  // State for the highlighted HTML content
  let highlightedContent = $state<string>("");
  // State for the reference to the hidden temporary element
  let tempElement: HTMLElement | undefined = $state(undefined);
  // State for the Shiki highlighter instance
  let shikiHighlighter: Highlighter | null = $state(null);

  /**
   * Initializes the Shiki highlighter instance once when the component mounts.
   */
  onMount(async () => {
    shikiHighlighter = await createHighlighter({
      themes: [theme],
      langs: ["typescript", "svelte", "html", "css", "json"]
    });
  });

  /**
   * Reactive effect that updates the syntax highlighting whenever
   * the children, language, or highlighter instance changes.
   */
  $effect(() => {
    if (!shikiHighlighter || !children || !tempElement) {
      if (!children) {
        highlightedContent = "";
      }
      return;
    }

    /**
     * At this point, Svelte has rendered the output of `children()` into `tempElement`
     * due to the `{@render children()}` directive in the hidden div.
     * We can now extract the raw text content from it.
     */
    const codeContent = (tempElement.textContent || "").trim();

    if (codeContent) {
      try {
        // Convert the raw code string to HTML using Shiki.
        highlightedContent = shikiHighlighter.codeToHtml(codeContent, {
          lang: language,
          theme: theme,
          transformers: [transformerColorizedBrackets()]
        });
      } catch (error) {
        console.error("Shiki highlighting error:", error, { language, codeContent });
        // Fallback to showing raw code (escaped) if highlighting fails.
        highlightedContent = `<pre class="shiki-fallback"><code>${escapeHtml(codeContent)}</code></pre>`;
      }
    } else {
      // Clear highlighted content if there's no text content.
      highlightedContent = "";
    }
  });

  /**
   * Helper function to escape HTML special characters.
   * Used for the fallback when Shiki highlighting fails.
   */
  function escapeHtml(unsafe: string): string {
    return unsafe
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }
</script>

<div class="code-block-container flex flex-col rounded-md border-2 border-slate-700 bg-black/80 {className}">
  <div class="header flex items-center justify-between bg-zinc-900/70 text-sm p-1.5 px-3 flex-shrink-0">
    <p class="flex items-center gap-1.5 font-mono text-xs text-slate-300">
      <Code2 class="h-4 w-4 text-orange-500 shrink-0" />
      {#if title}
        <span>{title}</span>
      {:else}
        <span>Code</span>
      {/if}
    </p>
    {#if file}
      <FileLink {file} />
    {/if}
  </div>

  <!--
    Temporary hidden element.
    The `children` snippet is rendered here by Svelte using `{@render children()}`.
    This component's `$effect` then reads `tempElement.textContent` to get the
    raw string representation of the rendered children for Shiki to process.
  -->
  <div
    bind:this={tempElement}
    style="display: none;"
    aria-hidden="true">
    {#if children}
      {@render children()}
    {/if}
  </div>

  {#if highlightedContent}
    <div class="p-2.5 text-[13px] leading-[18px] overflow-auto bg-[#0a0a0a] flex-1 min-h-0">
      {@html highlightedContent}
    </div>
  {:else if children}
    <pre class="fallback-pre p-3 font-mono text-sm text-slate-200 overflow-auto flex-1 min-h-0">
      {@render children()}
    </pre>
  {:else}
    <div class="p-3 text-slate-500 text-sm font-mono">Loading syntax highlighter...</div>
  {/if}
</div>

<style>
  :global(.shiki) {
    background: #090909 !important;
  }
</style>



---
File: /demo/src/lib/components/container.svelte
---

<script lang="ts">
  import FileLink from "./file-link.svelte";

  let { title, file, children }: { title?: string; file?: string; children: any } = $props();
</script>

<div class="flex flex-col gap-1 rounded-md border-2 border-slate-700 bg-gray-900 text-slate-400">
  <div class="flex items-center justify-between bg-slate-800 p-2 text-sm text-slate-500">
    <p class="flex items-center gap-2 font-mono">
      {#if title}
        &lt;{title} /&gt;
      {/if}
    </p>
    {#if file}
      <FileLink {file} />
    {/if}
  </div>
  {@render children()}
</div>



---
File: /demo/src/lib/components/default.svelte
---

<script lang="ts">
  import { BadgeInfo } from "lucide-svelte";
  import type { Snippet } from "svelte";
  import Badge from "./badge.svelte";

  let { class: className, children }: { class?: string; children?: Snippet } = $props();
</script>

<div class="flex flex-col gap-4 rounded-md border-4 border-slate-900/80 p-4 text-center text-gray-400 {className}">
  {#if children}
    {@render children()}
  {:else}
    <p>
      <Badge
        variant="info"
        icon={BadgeInfo}>
        There was no path provided to the router, so the default route was used (declared as a snippet).
      </Badge>
    </p>
    Click on a link above to see the different effects!
  {/if}
</div>



---
File: /demo/src/lib/components/file-link.svelte
---

<script lang="ts">
  import { FileCode2, GithubIcon } from "lucide-svelte";

  let { file } = $props();
</script>

<div class="flex gap-1">
  <div class="flex flex-1 items-center text-slate-500">
    <FileCode2 class="h-4 w-4" />
  </div>
  <a
    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}
    class="flex cursor-pointer items-center gap-1 text-center text-sm text-orange-400 hover:font-medium hover:text-indigo-400">
    {file}
  </a>
  <a
    href={`https://github.com/mateothegreat/svelte5-router/blob/main/demo/${file}`}
    target="_blank"
    class="flex cursor-pointer items-center gap-1 text-center text-sm text-slate-500 hover:font-medium hover:text-indigo-400">
    view source
    <GithubIcon class="h-4 w-4" />
  </a>
</div>



---
File: /demo/src/lib/components/inline-code.svelte
---

<script lang="ts">
  export type InlineCodeProps = {
    text: string;
    class?: string;
  };

  let { text, class: className }: InlineCodeProps = $props();

  if (!className) {
    className = "text-green-400 bg-black/50";
  }
</script>

<span class="whitespace-nowrap rounded-md p-1 px-2 font-mono text-sm {className}">{text}</span>



---
File: /demo/src/lib/default-route-config.ts
---

import { StatusCode, type RouteResult } from "@mateothegreat/svelte5-router";

import NotFound from "$routes/not-found.svelte";

/**
 * Surface a reusable configuration for routers to import
 * and apply to their router instances:
 *
 * @example
 * ```ts
 * <script lang="ts">
 *   import { RouteConfig } from "@mateothegreat/svelte5-router";
 *   import { myDefaultRouterConfig } from "$lib/default-route-config";
 *
 *   const routes: RouteConfig[] = [
 *     {
 *       path: "/home",
 *       component: Home
 *     }
 *   ];
 * </script>
 *
 * <Router
 *   id="my-main-router"
 *   {routes}
 *   {...myDefaultRouterConfig} />
 * ```
 */
export const myDefaultRouterConfig = {
  statuses: {
    /**
     * You can use a function to return a new route or a promise that
     * resolves to a new route:
     */
    [StatusCode.NotFound]: (result: RouteResult) => {
      console.log(result);
      return {
        component: NotFound,
        props: {
          somethingExtra: new Date().toISOString()
        }
      };
    }
    /**
     * You can also use an object to return a new route while having access
     * to the path and querystring:
     *
     * [StatusCode.NotFound]: (path: RouteResult) => ({
     *   component: NotFound,
     *   props: {
     *     somethingExtra: new Date().toISOString()
     *   }
     * }),
     *
     *
     * or simply return an object with a component and props:
     *
     * [StatusCode.NotFound]: {
     *   component: NotFound,
     *   props: {
     *     somethingExtra: new Date().toISOString()
     *   }
     * }
     */
  }
};



---
File: /demo/src/lib/router-history.ts
---

import type { Route } from "@mateothegreat/svelte5-router";

export const history = $state<Route[]>([]);

export const appendHistory = (route: Route) => {
  history.push(route);
};



---
File: /demo/src/lib/session.svelte.ts
---

let _state: {
  mode: "hash" | "path";
} = $state({
  mode: (localStorage.getItem("mode") as "hash" | "path") || "path"
});

export const session = {
  set mode(value: "hash" | "path") {
    localStorage.setItem("mode", value);
    _state = {
      ..._state,
      mode: value
    };
    if (value === "hash") {
      window.history.pushState({}, "", `/#/`);
    } else {
      window.history.pushState({}, "", "/");
    }
  },
  get mode() {
    return _state.mode;
  }
};



---
File: /demo/src/routes/extras/dump.svelte
---

<script lang="ts">
  import Code from "$lib/components/code.svelte";
  let { route, ...rest } = $props();
</script>

<div class="flex flex-col gap-3">
  <Code
    title="usage:"
    file="src/routes/extras/dump.svelte"
    class="flex-1">
    {`<script lang="ts">
  let { route, ...rest } = $props();
  console.log(route, rest);
</script>`}
  </Code>

  <div class="flex gap-3">
    <Code
      title="$props().route"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(route, null, 2)}</div>
    </Code>

    <Code
      title="$props().rest"
      language="json"
      class="flex-1">
      <div>{JSON.stringify(rest, null, 2)}</div>
    </Code>
  </div>
</div>



---
File: /demo/src/routes/extras/extras.svelte
---

<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { RouterInstance } from "@mateothegreat/svelte5-router";
  import type { RouteConfig } from "@mateothegreat/svelte5-router/route.svelte";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import { onDestroy } from "svelte";
  import PassingDownProps from "./passing-down-props.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();

  let randoms = $state({
    float: (Math.random() * 1000).toFixed(2),
    int: (Math.random() * 1000).toFixed(0),
    string: (Math.random() * 1000).toFixed(2).toString()
  });

  const interval = setInterval(() => {
    randoms.float = (Math.random() * 1000).toFixed(2);
    randoms.int = (Math.random() * 1000).toFixed(0);
    randoms.string = (Math.random() * 1000).toFixed(2).toString();
  }, 750);

  onDestroy(() => {
    clearInterval(interval);
  });

  const routes: RouteConfig[] = [
    {
      component: overview
    },
    {
      path: "passing-down-props",
      component: PassingDownProps,
      props: {
        route: "passing-down-props"
      },
      hooks: {
        pre: () => {
          console.log("pre");
          return true;
        }
      }
    }
  ];
</script>

{#snippet overview()}
  <div class="p-2">
    <div class="flex flex-col items-center gap-2 text-center text-slate-400">
      <div class="flex max-w-3xl flex-col gap-2 text-sm text-slate-500">
        <p class="text-fuchsia-500">Extra & cool stuff can be demoed here.</p>
        <p>Click a route above to see the different effects!</p>
      </div>
    </div>
  </div>
{/snippet}

<RouteWrapper
  {router}
  name="extras"
  {route}
  title={{
    file: "src/routes/extras/extras.svelte"
  }}
  links={[
    {
      href: "/extras/passing-down-props",
      label: "passing-down-props"
    }
  ]}>
  <Router
    basePath="/extras"
    bind:instance={router}
    {routes} />
</RouteWrapper>



---
File: /demo/src/routes/extras/passing-down-props.svelte
---

<script lang="ts">
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { RouterInstance } from "@mateothegreat/svelte5-router";
  import type { RouteConfig } from "@mateothegreat/svelte5-router/route.svelte";
  import Router from "@mateothegreat/svelte5-router/router.svelte";
  import Dump from "./dump.svelte";

  let router: RouterInstance = $state();
  let { route } = $props();

  const routes: RouteConfig[] = [
    {
      component: Dump,
      props: {
        foo: "bar",
        baz: {
          awesome: true
        }
      }
    }
  ];
</script>

<RouteWrapper
  {router}
  name="passing-down-props"
  {route}
  end={true}
  title={{
    file: "src/routes/extras/passing-down-props.svelte"
  }}
  links={[
    {
      href: "/extras/passing-down-props",
      label: "default path"
    }
  ]}>
  <Router
    basePath="/extras/passing-down-props"
    bind:instance={router}
    myAdditionalProp="I was added to the <Router/> component directly."
    {routes} />
</RouteWrapper>



---
File: /demo/src/routes/hash/hash.svelte
---

<script lang="ts">
  import { route } from "@mateothegreat/svelte5-router";

  console.log(location.hash);
</script>

<a
  use:route
  href="/hash/#b?test=4">
  b
</a>

<a
  use:route
  href="/hash/#a?test=123">
  a
</a>



---
File: /demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte
---

<script>
  import Container from "$lib/components/container.svelte";
</script>

<Container
  title={"Level_3"}
  file="src/routes/nested/level-1/level-2/level-3/level-3.svelte">
  <div class="flex flex-col gap-3 bg-green-500 p-4 text-center font-medium text-white">
    You've reached the deepest level of nested routing (level-3)!
  </div>
</Container>



---
File: /demo/src/routes/nested/level-1/level-2/level-2.svelte
---

<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance, type Route } from "@mateothegreat/svelte5-router";
  import Level_3 from "./level-3/level-3.svelte";

  const routes: Route[] = [
    {
      path: "level-3",
      component: Level_3,
      hooks: {
        pre: () => {
          console.log(`Route "/nested/level-1/level-2" matched (I'm a pre hook in the level-2.svelte route)`);
          return true;
        }
      }
    },
    {
      component: snippet
    }
  ];

  let router: RouterInstance = $state();
  let { route } = $props();
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/level-1/level-2/level-2.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested/level-2"
  end={true}
  {route}
  title={{
    file: "src/routes/nested/level-1/level-2/level-2.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested/level-1/level-2",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1/level-2/level-3",
      label: "/nested/level-1/level-2/level-3"
    }
  ]}>
  <Router
    id="nested-level-2-router"
    basePath="/nested/level-1/level-2"
    bind:instance={router}
    {routes}
    {...myDefaultRouterConfig} />
</RouteWrapper>



---
File: /demo/src/routes/nested/level-1/level-1.svelte
---

<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance } from "@mateothegreat/svelte5-router";
  import Level_2 from "./level-2/level-2.svelte";

  let router: RouterInstance = $state();
  let { route, foo } = $props();

  /**
   * Demonstrate how support for additional props is working.
   */
  console.log("additionalProps.foo in ../nested.svelte is passed to this component as:", foo);

  /**
   * This is a helper state variable that can be used to determine if the
   * current route is the same as the route that is being rendered so
   * that we can show a badge to indicate this is the last router in the
   * nested routing hierarchy.
   */
  let end = $state(false);
  $effect(() => {
    end = router.current?.result.path.condition === "default-match";
  });
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/level-1/level-1.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested/level-1"
  {end}
  {route}
  title={{
    file: "src/routes/nested/level-1/level-1.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested/level-1",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1/level-2",
      label: "/nested/level-1/level-2"
    }
  ]}>
  <Router
    id="nested-level-1-router"
    basePath="/nested/level-1"
    bind:instance={router}
    routes={[
      {
        path: "level-2",
        component: Level_2,
        hooks: {
          pre: () => {
            console.log(`Route "/nested/level-1/level-2" matched (I'm a pre hook in the level-1.svelte route)`);
            return true;
          }
        }
      },
      /**
       * Default routes can be placed anywhere in the routes array
       * and will be matched if no other routes match regardless of
       * their position in the array:
       */
      {
        component: snippet
      }
    ]}
    {...myDefaultRouterConfig} />
</RouteWrapper>



---
File: /demo/src/routes/nested/nested.svelte
---

<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { myDefaultRouterConfig } from "$lib/default-route-config";
  import { Router, RouterInstance, type Route } from "@mateothegreat/svelte5-router";
  import Level_1 from "./level-1/level-1.svelte";

  const routes: Route[] = [
    {
      component: snippet
    },
    {
      path: "level-1",
      component: Level_1,
      hooks: {
        pre: () => {
          console.log(`Route "/nested/level-1" matched (I'm a pre hook in the nested.svelte route)`);
          return true;
        }
      }
    }
  ];

  let router: RouterInstance = $state();
  let { route } = $props();

  /**
   * This is a helper state variable that can be used to determine if the
   * current route is the same as the route that is being rendered so
   * that we can show a badge to indicate this is the last router in the
   * nested routing hierarchy.
   */
  let end = $state(false);
  $effect(() => {
    end = router.current?.result.path.condition === "default-match";
  });

  const additionalProps = {
    foo: {
      bar: "baz"
    }
  };
</script>

{#snippet snippet()}
  <Container
    title={"{#snippet snippet()}"}
    file="src/routes/nested/nested.svelte">
    <div class="flex flex-col items-center gap-6 p-10 text-center">
      <Badge>There was no path provided to the router, so the default route was used (declared as a snippet).</Badge>
      Click on a link above to see the different effects!
    </div>
  </Container>
{/snippet}

<RouteWrapper
  {router}
  name="/nested"
  {route}
  {end}
  title={{
    file: "src/routes/nested/nested.svelte",
    content:
      "This demo shows how to use nested routing with the router where multiple routers can be nested within each other."
  }}
  links={[
    {
      href: "/nested",
      label: "default path",
      options: {
        active: {
          absolute: true
        }
      }
    },
    {
      href: "/nested/level-1",
      label: "/nested/level-1"
    }
  ]}>
  <Router
    id="nested-router"
    basePath="/nested"
    bind:instance={router}
    {...myDefaultRouterConfig}
    {routes}
    {...additionalProps} />
</RouteWrapper>



---
File: /demo/src/routes/paths-and-params/custom-not-found.svelte
---

<script lang="ts">
  let { route } = $props();
</script>

<div class="flex flex-col items-center justify-center gap-4">
  <pre class="rounded-md bg-gray-800 p-2 text-sm text-emerald-500">included from "custom-not-found.svelte":</pre>
  <h1 class="text-2xl font-bold">404 not found :(</h1>
  <p class="text-sm text-gray-500">The page you are looking for does not exist.</p>
  <pre class="rounded-md bg-gray-900 p-2 text-sm text-gray-400">$props():

{JSON.stringify(route, null, 2)}
</pre>
</div>



---
File: /demo/src/routes/paths-and-params/display-params.svelte
---

<script lang="ts">
  import Code from "$lib/components/code.svelte";
  import InlineCode from "$lib/components/inline-code.svelte";

  let { route } = $props();
</script>

{#snippet content()}
  The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes
  it to the component as the `params` prop.
{/snippet}

<Code
  title="params.route value:"
  file="src/routes/props/display-params.svelte">
  <div>{JSON.stringify(route, null, 2)}</div>
</Code>



---
File: /demo/src/routes/paths-and-params/paths-and-params.svelte
---

<script lang="ts">
  import Badge from "$lib/components/badge.svelte";
  import Container from "$lib/components/container.svelte";
  import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
  import { getStatusByValue, RouterInstance, StatusCode } from "@mateothegreat/svelte5-router";
  import type { RouteConfig, RouteResult } from
Download .txt
gitextract_rt2wkk2r/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── demo.yaml
│       ├── docs.yaml
│       ├── release.yaml
│       ├── setup.yaml
│       └── test.yaml
├── LICENSE
├── demo/
│   ├── cypress/
│   │   ├── e2e/
│   │   │   └── route-activation.cy.ts
│   │   ├── fixtures/
│   │   │   └── routes.json
│   │   └── support/
│   │       ├── commands.ts
│   │       └── e2e.ts
│   ├── cypress.config.ts
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── app.css
│   │   ├── app.svelte
│   │   ├── lib/
│   │   │   ├── components/
│   │   │   │   ├── badge.svelte
│   │   │   │   ├── code.svelte
│   │   │   │   ├── container.svelte
│   │   │   │   ├── default.svelte
│   │   │   │   ├── file-link.svelte
│   │   │   │   ├── inline-code.svelte
│   │   │   │   └── routes/
│   │   │   │       ├── route-link.svelte
│   │   │   │       ├── route-title.svelte
│   │   │   │       └── route-wrapper.svelte
│   │   │   ├── default-route-config.ts
│   │   │   ├── router-history.ts
│   │   │   └── session.svelte.ts
│   │   ├── main.ts
│   │   ├── routes/
│   │   │   ├── delayed.svelte
│   │   │   ├── extras/
│   │   │   │   ├── dump.svelte
│   │   │   │   ├── extras.svelte
│   │   │   │   └── passing-down-props.svelte
│   │   │   ├── hash/
│   │   │   │   └── hash.svelte
│   │   │   ├── home.svelte
│   │   │   ├── nested/
│   │   │   │   ├── level-1/
│   │   │   │   │   ├── level-1.svelte
│   │   │   │   │   └── level-2/
│   │   │   │   │       ├── level-2.svelte
│   │   │   │   │       └── level-3/
│   │   │   │   │           └── level-3.svelte
│   │   │   │   └── nested.svelte
│   │   │   ├── not-found.svelte
│   │   │   ├── paths-and-params/
│   │   │   │   ├── custom-not-found.svelte
│   │   │   │   ├── display-params.svelte
│   │   │   │   ├── paths-and-params.svelte
│   │   │   │   └── querystring-matching.svelte
│   │   │   ├── patterns/
│   │   │   │   ├── dump.svelte
│   │   │   │   ├── output.svelte
│   │   │   │   ├── parameter-extraction.svelte
│   │   │   │   └── patterns.svelte
│   │   │   ├── protected/
│   │   │   │   ├── account-state.svelte.ts
│   │   │   │   ├── denied.svelte
│   │   │   │   ├── login.svelte
│   │   │   │   ├── main.svelte
│   │   │   │   └── manage-account/
│   │   │   │       ├── auth-guard-fast.ts
│   │   │   │       ├── auth-guard-slow.ts
│   │   │   │       ├── balance.svelte
│   │   │   │       ├── home.svelte
│   │   │   │       ├── manage-account.svelte
│   │   │   │       └── worker-client.svelte.ts
│   │   │   └── transitions/
│   │   │       ├── fade.svelte
│   │   │       ├── slide.svelte
│   │   │       └── transitions.svelte
│   │   └── vite-env.d.ts
│   ├── svelte.config.js
│   ├── tailwind.config.ts
│   ├── tsconfig.deployed.json
│   ├── tsconfig.json
│   ├── vercel.json
│   └── vite.config.ts
├── docs/
│   ├── CNAME
│   ├── actions.md
│   ├── assets/
│   │   └── coverage.json
│   ├── changelog.md
│   ├── cliff.toml
│   ├── debugging.md
│   ├── diagrams/
│   │   ├── component-hierarchy.mmd
│   │   ├── route-evaluations.mmd
│   │   ├── router-architecture.mmd
│   │   └── routing-lifecycle.mmd
│   ├── diagrams.md
│   ├── getting-started.md
│   ├── helpers.md
│   ├── hooks.md
│   ├── llms.txt
│   ├── makefile
│   ├── package.json
│   ├── props.md
│   ├── puppeteer.config.cjs
│   ├── readme.md
│   ├── registry.md
│   ├── routing-patterns.md
│   ├── routing.md
│   ├── statuses.md
│   ├── styling.md
│   ├── tsconfig.json
│   └── typedoc.json
├── llms.txt
├── makefile
├── package.json
├── src/
│   ├── lib/
│   │   ├── actions/
│   │   │   ├── active.svelte.ts
│   │   │   ├── apply-classes.ts
│   │   │   ├── index.ts
│   │   │   ├── options.ts
│   │   │   └── route.svelte.ts
│   │   ├── hash.test.ts
│   │   ├── hash.ts
│   │   ├── helpers/
│   │   │   ├── evaluators.test.ts
│   │   │   ├── evaluators.ts
│   │   │   ├── goto.ts
│   │   │   ├── identify.ts
│   │   │   ├── index.ts
│   │   │   ├── logging.ts
│   │   │   ├── marshal.test.ts
│   │   │   ├── marshal.ts
│   │   │   ├── normalize.ts
│   │   │   ├── objects.ts
│   │   │   ├── pop.ts
│   │   │   ├── query.ts
│   │   │   ├── regexp.ts
│   │   │   ├── replace.ts
│   │   │   ├── runtime.ts
│   │   │   ├── tracing.svelte.ts
│   │   │   ├── urls.test.ts
│   │   │   └── urls.ts
│   │   ├── hooks.ts
│   │   ├── index.ts
│   │   ├── path.ts
│   │   ├── query.svelte.ts
│   │   ├── query.test.ts
│   │   ├── registry.svelte.ts
│   │   ├── route.svelte.ts
│   │   ├── router-instance-config.ts
│   │   ├── router-instance.svelte.ts
│   │   ├── router-integration.test.ts
│   │   ├── router-patterns-demo.test.ts
│   │   ├── router-remount.test.ts
│   │   ├── router.svelte
│   │   ├── statuses.ts
│   │   └── utilities.svelte.ts
│   └── vite-env.d.ts
├── svelte.config.js
├── test/
│   └── app/
│       ├── LICENSE
│       ├── index.html
│       ├── package.json
│       ├── readme.md
│       ├── src/
│       │   ├── app.css
│       │   ├── app.svelte
│       │   ├── main.ts
│       │   └── routes/
│       │       ├── test-a/
│       │       │   └── test-a.svelte
│       │       └── test-b/
│       │           └── test-b.svelte
│       ├── svelte.config.ts
│       ├── tsconfig.json
│       └── vite.config.ts
├── tsconfig.build.json
├── tsconfig.json
├── vite.config.ts
├── vitest.config.ts
└── vitest.setup.ts
Download .txt
SYMBOL INDEX (104 symbols across 28 files)

FILE: demo/cypress.config.ts
  method setupNodeEvents (line 16) | setupNodeEvents(on, config) {

FILE: demo/cypress/support/e2e.ts
  type Chainable (line 5) | interface Chainable {

FILE: demo/src/lib/session.svelte.ts
  method mode (line 8) | set mode(value: "hash" | "path") {
  method mode (line 20) | get mode() {

FILE: demo/src/routes/protected/account-state.svelte.ts
  method loggedIn (line 4) | get loggedIn() {
  method loggedIn (line 7) | set loggedIn(value: boolean) {

FILE: demo/src/routes/protected/manage-account/worker-client.svelte.ts
  method working (line 4) | get working() {
  method working (line 7) | set working(value: boolean) {

FILE: demo/src/vite-env.d.ts
  type ImportMetaEnv (line 6) | interface ImportMetaEnv {

FILE: src/lib/actions/active.svelte.ts
  method destroy (line 30) | destroy() {

FILE: src/lib/actions/options.ts
  type RouteOptionState (line 7) | type RouteOptionState = {
  class RouteOptions (line 32) | class RouteOptions {
    method constructor (line 53) | constructor(options?: Partial<RouteOptions>) {

FILE: src/lib/actions/route.svelte.ts
  method destroy (line 41) | destroy() {

FILE: src/lib/hash.ts
  type Hash (line 3) | type Hash = {

FILE: src/lib/helpers/evaluators.ts
  type Condition (line 10) | type Condition =
  type Evaluation (line 42) | type Evaluation = {
  type EvaluationResult (line 52) | type EvaluationResult = {

FILE: src/lib/helpers/identify.ts
  type Identity (line 1) | type Identity =

FILE: src/lib/helpers/logging.ts
  type LogLevel (line 12) | enum LogLevel {
  type Group (line 24) | type Group = {
  type Log (line 34) | type Log = Group | Group[] | any | any[];

FILE: src/lib/helpers/marshal.ts
  type MarshallableType (line 3) | type MarshallableType = string | number | boolean | RegExp | Function | ...
  type Marshalled (line 5) | type Marshalled<T> = {

FILE: src/lib/helpers/runtime.ts
  type Config (line 47) | type Config = {

FILE: src/lib/helpers/tracing.svelte.ts
  class Span (line 11) | class Span {
    method constructor (line 20) | constructor(span: Span, prefix?: string) {
    method trace (line 29) | trace?(trace: Trace, prefix?: string): Trace {
    method get (line 39) | get?(): MapIterator<Trace> {
  class Trace (line 49) | class Trace {
    method constructor (line 59) | constructor(trace: Trace, index?: number, span?: Span, prefix?: string) {
    method toConsole (line 75) | toConsole?(level?: logging.LogLevel): void {

FILE: src/lib/helpers/urls.ts
  type Param (line 21) | type Param = string | number | boolean;
  type ReturnParam (line 38) | type ReturnParam =
  type URL (line 48) | type URL = {

FILE: src/lib/hooks.ts
  type HookReturn (line 12) | type HookReturn = void | boolean | Promise<boolean> | Promise<void>;
  type Hook (line 13) | type Hook = (route: RouteResult) => HookReturn;

FILE: src/lib/path.ts
  type PathType (line 12) | type PathType = string | number | RegExp | Function | Promise<unknown>;
  class Path (line 14) | class Path {
    method constructor (line 20) | constructor() {
    method toURL (line 29) | toURL(): string {
    method toURI (line 33) | toURI(): string {

FILE: src/lib/query.svelte.ts
  type QueryType (line 12) | type QueryType<T = unknown> = Record<string, string | number | RegExp | ...
  type QueryEvaluationResult (line 19) | type QueryEvaluationResult = {
  class Query (line 29) | class Query {
    method constructor (line 33) | constructor(query?: Record<string, string> | string | Query | Record<s...
    method get (line 53) | get<T>(key: string, defaultValue?: T): T {
    method set (line 60) | set(key: string, value: string) {}
    method delete (line 65) | delete(key: string) {
    method clear (line 72) | clear() {
    method goto (line 76) | goto(path: string) {
    method test (line 80) | test(inbound: Query): QueryEvaluationResult {
    method toString (line 133) | toString() {
    method toJSON (line 158) | toJSON(preserveOriginal?: boolean) {

FILE: src/lib/registry.svelte.ts
  class Registry (line 16) | class Registry {
    method constructor (line 22) | constructor() {
    method register (line 50) | register(config: RouterInstanceConfig, applyFn: ApplyFn, span?: Span):...
    method deregister (line 83) | deregister(id: string, span?: Span): void {
    method get (line 108) | get(id: string): RouterInstance {

FILE: src/lib/route.svelte.ts
  class RouteResult (line 51) | class RouteResult {
    method constructor (line 264) | constructor(result: RouteResult) {
    method toString (line 272) | toString(): string {
  type ApplyFn (line 295) | type ApplyFn = (result: RouteResult, span?: Span) => void;
  type ApplyFn2 (line 302) | type ApplyFn2 = (result: RouteResult, span?: Span) => void;
  type Testing (line 313) | type Testing<T> = T;
  class RouteConfig (line 315) | class RouteConfig {
    method constructor (line 329) | constructor(config: RouteConfig) {
    method toJSON (line 340) | toJSON?(): any {
  class Route (line 372) | class Route {
    method constructor (line 478) | constructor(config: RouteConfig) {
    method test (line 498) | test?(path: PathType): Evaluation {
    method absolute (line 552) | absolute?(): string {

FILE: src/lib/router-instance-config.ts
  type RouterInstanceConfigOptions (line 10) | interface RouterInstanceConfigOptions {
  class RouterInstanceConfig (line 33) | class RouterInstanceConfig {
    method constructor (line 101) | constructor(config: RouterInstanceConfigOptions) {
    method toJSON (line 118) | toJSON(): any {

FILE: src/lib/router-instance.svelte.ts
  type RouterHandlers (line 26) | type RouterHandlers = {
  class RouterInstance (line 57) | class RouterInstance {
    method constructor (line 99) | constructor(config: RouterInstanceConfig, applyFn: ApplyFn) {
    method handleStateChange (line 145) | async handleStateChange(url: string, span?: Span): Promise<void> {
    method evaluateHooks (line 276) | async evaluateHooks(route: RouteResult, hooks: Hook | Hook[]): Promise...
    method get (line 304) | async get(path: string, query?: Query, span?: Span): Promise<RouteResu...
    method deregister (line 542) | deregister(span?: Span): void {
    method routesArray (line 556) | get routesArray(): Route[] {
    method toJSON (line 565) | toJSON(): any {

FILE: src/lib/statuses.ts
  type StatusCode (line 14) | enum StatusCode {
  type Statuses (line 57) | type Statuses = Partial<{

FILE: src/lib/utilities.svelte.ts
  function wait (line 10) | async function wait(predicate: () => boolean, timeout = 5000): Promise<v...
  function isPromise (line 38) | function isPromise(value: any): boolean {
  class ReactiveMap (line 64) | class ReactiveMap<K, V> extends Map<K, V> {
    method size (line 67) | get size() {
    method #trig (line 72) | #trig() {
    method add (line 76) | add(key: K, value: V) {
    method get (line 83) | get(key: K) {
    method set (line 88) | set(key: K, value: V) {
    method delete (line 94) | delete(key: K) {
    method clear (line 100) | clear() {
    method keys (line 106) | keys() {
    method values (line 110) | values() {
    method entries (line 114) | entries() {
    method forEach (line 118) | forEach(fn: (value: V, key: K, map: Map<K, V>) => void) {
  method [Symbol.iterator] (line 123) | [Symbol.iterator]() {

FILE: src/vite-env.d.ts
  type ImportMetaEnv (line 5) | interface ImportMetaEnv {

FILE: vite.config.ts
  function copySvelteSources (line 11) | function copySvelteSources() {
Condensed preview — 159 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (893K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 520,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: mateothegreat\n\n---\n\n**De"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 478,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: mateothegreat"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 523,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/demo.yaml",
    "chars": 716,
    "preview": "name: 📱 Demo\non:\n  workflow_dispatch:\n  workflow_call:\n    secrets:\n      VERCEL_TOKEN:\n        required: true\nconcurren"
  },
  {
    "path": ".github/workflows/docs.yaml",
    "chars": 1944,
    "preview": "name: 📚 Docs\non:\n  workflow_dispatch:\n  workflow_call:\npermissions:\n  id-token: write\n  pages: write\ndefaults:\n  run:\n  "
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 4492,
    "preview": "name: 🚀 Release\nrun-name: 🚀 Release (${{ github.event.inputs.label }})\non:\n  workflow_dispatch:\n    inputs:\n      label:"
  },
  {
    "path": ".github/workflows/setup.yaml",
    "chars": 718,
    "preview": "name: 🔧 Setup\non:\n  workflow_call:\njobs:\n  node:\n    name: \"Runtime\"  \n    environment:\n      name: dev\n    runs-on: ubu"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 1995,
    "preview": "name: ⚡ Test Runner\non:\n  workflow_dispatch:\n  workflow_call:\npermissions:\n  contents: write\njobs:\n  setup:\n    name: \"🔧"
  },
  {
    "path": "LICENSE",
    "chars": 1096,
    "preview": "MIT License\n\nCopyright (c) 2025 Matthew Davis <matthew@matthewdavis.io>\n\nPermission is hereby granted, free of charge, t"
  },
  {
    "path": "demo/cypress/e2e/route-activation.cy.ts",
    "chars": 6138,
    "preview": "/// <reference types=\"cypress\" />\n\nconst routes = require(\"../fixtures/routes.json\");\ndescribe.only(\"route activation\", "
  },
  {
    "path": "demo/cypress/fixtures/routes.json",
    "chars": 739,
    "preview": "[\n  {\n    \"path\": \"/home\",\n    \"id\": \"home.svelte\",\n    \"active\": 2\n  },\n  {\n    \"path\": \"/props\",\n    \"id\": \"props.svel"
  },
  {
    "path": "demo/cypress/support/commands.ts",
    "chars": 34,
    "preview": "/// <reference types=\"cypress\" />\n"
  },
  {
    "path": "demo/cypress/support/e2e.ts",
    "chars": 2330,
    "preview": "import \"./commands\";\n\ndeclare global {\n  namespace Cypress {\n    interface Chainable {\n      /**\n       * Custom command"
  },
  {
    "path": "demo/cypress.config.ts",
    "chars": 349,
    "preview": "import { defineConfig } from \"cypress\";\n\nexport default defineConfig({\n  component: {\n    devServer: {\n      framework: "
  },
  {
    "path": "demo/index.html",
    "chars": 358,
    "preview": "<!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."
  },
  {
    "path": "demo/package.json",
    "chars": 1444,
    "preview": "{\n  \"name\": \"test-app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --p"
  },
  {
    "path": "demo/src/app.css",
    "chars": 2690,
    "preview": "@import \"tailwindcss\";\n@config '../tailwind.config.ts';\n\n/*\n  The default border color has changed to `currentColor` in "
  },
  {
    "path": "demo/src/app.svelte",
    "chars": 9267,
    "preview": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterC"
  },
  {
    "path": "demo/src/lib/components/badge.svelte",
    "chars": 1071,
    "preview": "<script lang=\"ts\">\n  import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from \"lucide-svelte\";\n  import"
  },
  {
    "path": "demo/src/lib/components/code.svelte",
    "chars": 4283,
    "preview": "<script lang=\"ts\">\n  import { transformerColorizedBrackets } from \"@shikijs/colorized-brackets\";\n  import { Code2 } from"
  },
  {
    "path": "demo/src/lib/components/container.svelte",
    "chars": 563,
    "preview": "<script lang=\"ts\">\n  import FileLink from \"./file-link.svelte\";\n\n  let { title, file, children }: { title?: string; file"
  },
  {
    "path": "demo/src/lib/components/default.svelte",
    "chars": 682,
    "preview": "<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n  import type { Snippet } from \"svelte\";\n  import Badge "
  },
  {
    "path": "demo/src/lib/components/file-link.svelte",
    "chars": 775,
    "preview": "<script lang=\"ts\">\n  import { FileCode2, GithubIcon } from \"lucide-svelte\";\n\n  let { file } = $props();\n</script>\n\n<div "
  },
  {
    "path": "demo/src/lib/components/inline-code.svelte",
    "chars": 337,
    "preview": "<script lang=\"ts\">\n  export type InlineCodeProps = {\n    text: string;\n    class?: string;\n  };\n\n  let { text, class: cl"
  },
  {
    "path": "demo/src/lib/components/routes/route-link.svelte",
    "chars": 1693,
    "preview": "<script lang=\"ts\">\n  import { session } from \"$lib/session.svelte\";\n  import { route, RouteOptions } from \"@mateothegrea"
  },
  {
    "path": "demo/src/lib/components/routes/route-title.svelte",
    "chars": 2127,
    "preview": "<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { ArrowD"
  },
  {
    "path": "demo/src/lib/components/routes/route-wrapper.svelte",
    "chars": 1398,
    "preview": "<script lang=\"ts\">\n  import type { RouteResult, RouterInstance } from \"@mateothegreat/svelte5-router\";\n  import { Anchor"
  },
  {
    "path": "demo/src/lib/default-route-config.ts",
    "chars": 1586,
    "preview": "import { StatusCode, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport NotFound from \"$routes/not-found.s"
  },
  {
    "path": "demo/src/lib/router-history.ts",
    "chars": 181,
    "preview": "import type { Route } from \"@mateothegreat/svelte5-router\";\n\nexport const history = $state<Route[]>([]);\n\nexport const a"
  },
  {
    "path": "demo/src/lib/session.svelte.ts",
    "chars": 475,
    "preview": "let _state: {\n  mode: \"hash\" | \"path\";\n} = $state({\n  mode: (localStorage.getItem(\"mode\") as \"hash\" | \"path\") || \"path\"\n"
  },
  {
    "path": "demo/src/main.ts",
    "chars": 177,
    "preview": "import { mount } from \"svelte\";\n\nimport './app.css';\nimport App from './app.svelte';\n\nconst app = mount(App, {\n  target:"
  },
  {
    "path": "demo/src/routes/delayed.svelte",
    "chars": 171,
    "preview": "<div class=\"bg-indigo-400 p-10\">\n  <h1>\n    a delayed route component, the text for \"Navigating\" will be \"Navigating: bu"
  },
  {
    "path": "demo/src/routes/extras/dump.svelte",
    "chars": 684,
    "preview": "<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  let { route, ...rest } = $props();\n</script>\n\n<di"
  },
  {
    "path": "demo/src/routes/extras/extras.svelte",
    "chars": 1967,
    "preview": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance }"
  },
  {
    "path": "demo/src/routes/extras/passing-down-props.svelte",
    "chars": 1022,
    "preview": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { RouterInstance }"
  },
  {
    "path": "demo/src/routes/hash/hash.svelte",
    "chars": 221,
    "preview": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  console.log(location.hash);\n</script>\n\n<a"
  },
  {
    "path": "demo/src/routes/home.svelte",
    "chars": 6278,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-1.svelte",
    "chars": 2646,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/containe"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-2/level-2.svelte",
    "chars": 1962,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/containe"
  },
  {
    "path": "demo/src/routes/nested/level-1/level-2/level-3/level-3.svelte",
    "chars": 354,
    "preview": "<script>\n  import Container from \"$lib/components/container.svelte\";\n</script>\n\n<Container\n  title={\"Level_3\"}\n  file=\"s"
  },
  {
    "path": "demo/src/routes/nested/nested.svelte",
    "chars": 2308,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/containe"
  },
  {
    "path": "demo/src/routes/not-found.svelte",
    "chars": 507,
    "preview": "<script lang=\"ts\">\n  let { route } = $props();\n  $inspect(route);\n</script>\n\n<div class=\"flex flex-col items-center just"
  },
  {
    "path": "demo/src/routes/paths-and-params/custom-not-found.svelte",
    "chars": 495,
    "preview": "<script lang=\"ts\">\n  let { route } = $props();\n</script>\n\n<div class=\"flex flex-col items-center justify-center gap-4\">\n"
  },
  {
    "path": "demo/src/routes/paths-and-params/display-params.svelte",
    "chars": 512,
    "preview": "<script lang=\"ts\">\n  import Code from \"$lib/components/code.svelte\";\n  import InlineCode from \"$lib/components/inline-co"
  },
  {
    "path": "demo/src/routes/paths-and-params/paths-and-params.svelte",
    "chars": 6965,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/containe"
  },
  {
    "path": "demo/src/routes/paths-and-params/querystring-matching.svelte",
    "chars": 2267,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Code from \"$lib/components/code.svelte\";"
  },
  {
    "path": "demo/src/routes/patterns/dump.svelte",
    "chars": 798,
    "preview": "<script lang=\"ts\">\n  import { BadgeInfo } from \"lucide-svelte\";\n\n  import { Lightbulb } from \"lucide-svelte\";\n\n  import "
  },
  {
    "path": "demo/src/routes/patterns/output.svelte",
    "chars": 497,
    "preview": "<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   "
  },
  {
    "path": "demo/src/routes/patterns/parameter-extraction.svelte",
    "chars": 1512,
    "preview": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n\n  import Code from \"$lib/components/code.sv"
  },
  {
    "path": "demo/src/routes/patterns/patterns.svelte",
    "chars": 3731,
    "preview": "<script lang=\"ts\">\n  import { route, type RouteConfig } from \"@mateothegreat/svelte5-router\";\n  import Router from \"@mat"
  },
  {
    "path": "demo/src/routes/protected/account-state.svelte.ts",
    "chars": 356,
    "preview": "let token = $state(localStorage.getItem(\"token\"));\n\nexport const client = {\n  get loggedIn() {\n    return token !== null"
  },
  {
    "path": "demo/src/routes/protected/denied.svelte",
    "chars": 1543,
    "preview": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { LockIcon, ShieldAlert } from \"luc"
  },
  {
    "path": "demo/src/routes/protected/login.svelte",
    "chars": 990,
    "preview": "<script lang=\"ts\">\n  import { goto } from \"@mateothegreat/svelte5-router\";\n  import { Shield } from \"lucide-svelte\";\n  i"
  },
  {
    "path": "demo/src/routes/protected/main.svelte",
    "chars": 4612,
    "preview": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterC"
  },
  {
    "path": "demo/src/routes/protected/manage-account/auth-guard-fast.ts",
    "chars": 1250,
    "preview": "import { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/auth-guard-slow.ts",
    "chars": 1312,
    "preview": "import { goto, type RouteResult } from \"@mateothegreat/svelte5-router\";\n\nimport { session } from \"$lib/session.svelte\";\n"
  },
  {
    "path": "demo/src/routes/protected/manage-account/balance.svelte",
    "chars": 2361,
    "preview": "<script lang=\"ts\">\n  import { ArrowDownRight, ArrowUpRight, DollarSign } from \"lucide-svelte\";\n  import { fade } from \"s"
  },
  {
    "path": "demo/src/routes/protected/manage-account/home.svelte",
    "chars": 1740,
    "preview": "<script lang=\"ts\">\n  import { route } from \"@mateothegreat/svelte5-router\";\n  import { Activity, PiggyBank, Send, Wallet"
  },
  {
    "path": "demo/src/routes/protected/manage-account/manage-account.svelte",
    "chars": 3958,
    "preview": "<script lang=\"ts\">\n  import RouteWrapper from \"$lib/components/routes/route-wrapper.svelte\";\n  import { myDefaultRouterC"
  },
  {
    "path": "demo/src/routes/protected/manage-account/worker-client.svelte.ts",
    "chars": 163,
    "preview": "let working = $state(false);\n\nexport const workerClient = {\n  get working() {\n    return working;\n  },\n  set working(val"
  },
  {
    "path": "demo/src/routes/transitions/fade.svelte",
    "chars": 760,
    "preview": "<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";"
  },
  {
    "path": "demo/src/routes/transitions/slide.svelte",
    "chars": 767,
    "preview": "<script>\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/container.svelte\";"
  },
  {
    "path": "demo/src/routes/transitions/transitions.svelte",
    "chars": 1979,
    "preview": "<script lang=\"ts\">\n  import Badge from \"$lib/components/badge.svelte\";\n  import Container from \"$lib/components/containe"
  },
  {
    "path": "demo/src/vite-env.d.ts",
    "chars": 200,
    "preview": "/// <reference types=\"svelte\" />\n/// <reference types=\"vite/client\" />\n\nimport type { CompilerConfig } from \"@mateothegr"
  },
  {
    "path": "demo/svelte.config.js",
    "chars": 259,
    "preview": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess(),\n  viteP"
  },
  {
    "path": "demo/tailwind.config.ts",
    "chars": 1979,
    "preview": "\n/** @type {import('tailwindcss').Config} */\nconst config = {\n  darkMode: [\"class\"],\n  content: [\"./src/**/*.{html,js,sv"
  },
  {
    "path": "demo/tsconfig.deployed.json",
    "chars": 554,
    "preview": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsisten"
  },
  {
    "path": "demo/tsconfig.json",
    "chars": 924,
    "preview": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsisten"
  },
  {
    "path": "demo/vercel.json",
    "chars": 106,
    "preview": "{\n  \"routes\": [{ \"src\": \"/[^.]+\", \"dest\": \"/\", \"status\": 200 }],\n  \"github\": {\n    \"enabled\": false\n  }\n}\n"
  },
  {
    "path": "demo/vite.config.ts",
    "chars": 2487,
    "preview": "import { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-ins"
  },
  {
    "path": "docs/CNAME",
    "chars": 22,
    "preview": "docs.router.svelte.spa"
  },
  {
    "path": "docs/actions.md",
    "chars": 3915,
    "preview": "# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These action"
  },
  {
    "path": "docs/assets/coverage.json",
    "chars": 2518,
    "preview": "{\"percent\":51,\"expected\":218,\"actual\":112,\"notDocumented\":[\"MarshallableType\",\"URL\",\"URL.protocol\",\"URL.host\",\"URL.port\""
  },
  {
    "path": "docs/changelog.md",
    "chars": 6136,
    "preview": "<div align=\"center\">\n<img src=\"tag.png\" width=\"200\" />\n<h1><strong>@mateothegreat/svelte5-router</strong></h1>\n<h3>📋 tl;"
  },
  {
    "path": "docs/cliff.toml",
    "chars": 7019,
    "preview": "# https://git-cliff.org/docs/integration/github/\n\n[changelog]\ntrim = true\nrender_always = true\nheader = \"\"\"\n{%- macro re"
  },
  {
    "path": "docs/debugging.md",
    "chars": 1468,
    "preview": "# 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 follo"
  },
  {
    "path": "docs/diagrams/component-hierarchy.mmd",
    "chars": 772,
    "preview": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    '"
  },
  {
    "path": "docs/diagrams/route-evaluations.mmd",
    "chars": 1022,
    "preview": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    '"
  },
  {
    "path": "docs/diagrams/router-architecture.mmd",
    "chars": 944,
    "preview": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    '"
  },
  {
    "path": "docs/diagrams/routing-lifecycle.mmd",
    "chars": 864,
    "preview": "%%{init: {\n  'theme': 'base',\n  'themeVariables': {\n    'primaryColor': '#2563eb',\n    'primaryTextColor': '#fff',\n    '"
  },
  {
    "path": "docs/diagrams.md",
    "chars": 1168,
    "preview": "# Router Architecture Diagrams\n\nThis document contains Mermaid diagrams that illustrate the architecture and flow of the"
  },
  {
    "path": "docs/getting-started.md",
    "chars": 779,
    "preview": "# Getting Started\n\n## Installation\n\n```bash\nnpm install @mateothegreat/svelte5-router\n```\n\n## Usage\n\nIn your `app.svelte"
  },
  {
    "path": "docs/helpers.md",
    "chars": 1873,
    "preview": "# Helpers\n\nThere are a few helpers that are available to you when using the router.\n\n## `goto(path: string, queryParams?"
  },
  {
    "path": "docs/hooks.md",
    "chars": 1692,
    "preview": "# Routing Hooks\n\n | Order | Event  | Scope       | Description                                |\n | ----- | ------ | ----"
  },
  {
    "path": "docs/llms.txt",
    "chars": 243851,
    "preview": "Directory Structure:\n\n└── ./\n    ├── .github\n    │   └── ISSUE_TEMPLATE\n    │       ├── bug_report.md\n    │       └── fe"
  },
  {
    "path": "docs/makefile",
    "chars": 602,
    "preview": ".PHONY: llms.txt diagrams\n\nsetup:\n\t@if [ ! -d \"node_modules\" ]; then \\\n\t\techo \"Installing dependencies...\"; \\\n\t\tnpm inst"
  },
  {
    "path": "docs/package.json",
    "chars": 1633,
    "preview": "{\n  \"name\": \"@mateothegreat/svelte5-router\",\n  \"version\": \"2.15.3\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"docs:build\":"
  },
  {
    "path": "docs/props.md",
    "chars": 1216,
    "preview": "# Passing Props\n\nYou can pass props to a route by using the `props` property on any route.\n\nThese props will be passed t"
  },
  {
    "path": "docs/puppeteer.config.cjs",
    "chars": 93,
    "preview": "module.exports = {\n  launch: {\n    args: [\"--no-sandbox\", \"--disable-setuid-sandbox\"]\n  }\n};\n"
  },
  {
    "path": "docs/readme.md",
    "chars": 3440,
    "preview": "# Svelte 5 SPA Router 🚀 🔥\n\n![logo](https://raw.githubusercontent.com/mateothegreat/svelte5-router/refs/heads/dev/docs/as"
  },
  {
    "path": "docs/registry.md",
    "chars": 1532,
    "preview": "# Router Registry\n\nThe router [registry](../src/lib/registry.svelte.ts) is a global object that is used to store route\ni"
  },
  {
    "path": "docs/routing-patterns.md",
    "chars": 4592,
    "preview": "# Routing Patterns\n\nAs your application grows, you'll likely need to use more complex routing patterns. This guide will "
  },
  {
    "path": "docs/routing.md",
    "chars": 6395,
    "preview": "# Routing Usage\n\nWe provide an array of `RouteConfig` objects to the `Router` component.\n\nEach `RouteConfig` object desc"
  },
  {
    "path": "docs/statuses.md",
    "chars": 3959,
    "preview": "# Route Statuses\n\nEach router instance can have a set of statuses that are rendered when a route\nreturns a specific stat"
  },
  {
    "path": "docs/styling.md",
    "chars": 1913,
    "preview": "# Routing Styling\n\nYou can have the router apply a class to the active route by setting the `active.class` option\nwhen c"
  },
  {
    "path": "docs/tsconfig.json",
    "chars": 36,
    "preview": "{\n  \"extends\": \"../tsconfig.json\"\n}\n"
  },
  {
    "path": "docs/typedoc.json",
    "chars": 3744,
    "preview": "// https://app.studyraid.com/en/courses/15016/typedoc-automated-api-documentation-for-typescript\n{\n  \"$schema\": \"https:/"
  },
  {
    "path": "llms.txt",
    "chars": 293748,
    "preview": "# Actions\n\nThe Svelte router provides powerful actions that can be used to enhance your routing experience. These action"
  },
  {
    "path": "makefile",
    "chars": 466,
    "preview": ".PHONY: demo docs/llms.txt\n\ninstall:\n\tnpm install\n\tcd demo && npm install\n\ndemo:\n\tif [ ! -d \"demo/node_modules\" ]; then "
  },
  {
    "path": "package.json",
    "chars": 2274,
    "preview": "{\n  \"name\": \"@mateothegreat/svelte5-router\",\n  \"version\": \"2.16.19\",\n  \"type\": \"module\",\n  \"moduleResolution\": \"bundler\""
  },
  {
    "path": "src/lib/actions/active.svelte.ts",
    "chars": 798,
    "preview": "import { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions "
  },
  {
    "path": "src/lib/actions/apply-classes.ts",
    "chars": 1676,
    "preview": "import { urls, type URL } from \"../helpers/urls\";\n\nimport { RouteOptions } from \"./options\";\n\n/**\n * Applies the active "
  },
  {
    "path": "src/lib/actions/index.ts",
    "chars": 92,
    "preview": "export * from \"./apply-classes\";\nexport * from \"./options\";\nexport * from \"./route.svelte\";\n"
  },
  {
    "path": "src/lib/actions/options.ts",
    "chars": 1268,
    "preview": "/**\n * Options that are applied to the html element when the route is active, inactive,\n * loading, or disabled.\n *\n * @"
  },
  {
    "path": "src/lib/actions/route.svelte.ts",
    "chars": 1264,
    "preview": "import { urls } from \"../helpers/urls\";\n\nimport { applyActiveClass } from \"./apply-classes\";\nimport type { RouteOptions "
  },
  {
    "path": "src/lib/hash.test.ts",
    "chars": 933,
    "preview": "// import { describe, expect, test } from \"vitest\";\n\n// import { hash } from \"./hash\";\n\n// describe(\"simple params\", () "
  },
  {
    "path": "src/lib/hash.ts",
    "chars": 587,
    "preview": "import { Query } from \"./query.svelte\";\n\nexport type Hash = {\n  path: string;\n  query: Query;\n  hash: string;\n};\n\nexport"
  },
  {
    "path": "src/lib/helpers/evaluators.test.ts",
    "chars": 1459,
    "preview": "import { describe, expect, test } from \"vitest\";\n\nimport { evaluators } from \"./evaluators\";\nimport { Identities } from "
  },
  {
    "path": "src/lib/helpers/evaluators.ts",
    "chars": 3918,
    "preview": "import { identify, Identities } from \"./identify\";\nimport { marshal } from \"./marshal\";\nimport type { ReturnParam } from"
  },
  {
    "path": "src/lib/helpers/goto.ts",
    "chars": 589,
    "preview": "/**\n * Navigate to a new path by using the browser's history API (pushState specifically).\n *\n * @param path - The path "
  },
  {
    "path": "src/lib/helpers/identify.ts",
    "chars": 1145,
    "preview": "export type Identity =\n  | string\n  | number\n  | boolean\n  | null\n  | undefined\n  | RegExp\n  | Function\n  | Object\n  | A"
  },
  {
    "path": "src/lib/helpers/index.ts",
    "chars": 378,
    "preview": "export * from \"./evaluators\";\nexport * from \"./goto\";\nexport * from \"./identify\";\nexport * from \"./logging\";\nexport * fr"
  },
  {
    "path": "src/lib/helpers/logging.ts",
    "chars": 2179,
    "preview": "import { runtime } from \"./runtime\";\n\n/**\n * Logging facility.\n *\n * @category Helpers\n */\nexport namespace logging {\n  "
  },
  {
    "path": "src/lib/helpers/marshal.test.ts",
    "chars": 929,
    "preview": "import { describe, expect, test } from \"vitest\";\n\nimport { Identities } from \"./identify\";\nimport { marshal } from \"./ma"
  },
  {
    "path": "src/lib/helpers/marshal.ts",
    "chars": 5657,
    "preview": "import { Identities, type Identity } from \"./identify\";\n\nexport type MarshallableType = string | number | boolean | RegE"
  },
  {
    "path": "src/lib/helpers/normalize.ts",
    "chars": 318,
    "preview": "/**\n * Normalize a path to ensure it starts with a slash.\n *\n * @param {string} path The path to normalize.\n *\n * @retur"
  },
  {
    "path": "src/lib/helpers/objects.ts",
    "chars": 731,
    "preview": "/**\n * Convert a value to a primitive value.\n *\n * @param obj - The value to convert.\n *\n * @returns The primitive value"
  },
  {
    "path": "src/lib/helpers/pop.ts",
    "chars": 412,
    "preview": "/**\n * Navigate backwards in the browser history N pages.\n *\n * @param delta - The number of pages to navigate backwards"
  },
  {
    "path": "src/lib/helpers/query.ts",
    "chars": 334,
    "preview": "/**\n * Get a query parameter from the current URL.\n *\n * @param key - The key of the query parameter to get\n *\n * @retur"
  },
  {
    "path": "src/lib/helpers/regexp.ts",
    "chars": 1181,
    "preview": "/**\n * Regular expression utilities.\n *\n * @module regexp\n * @category Helpers\n */\nexport namespace regexp {\n  /**\n   * "
  },
  {
    "path": "src/lib/helpers/replace.ts",
    "chars": 633,
    "preview": "/**\n * Navigate to a new path by replacing the current browser history entry\n * instead of adding a new one (history.rep"
  },
  {
    "path": "src/lib/helpers/runtime.ts",
    "chars": 2277,
    "preview": "import { logging } from \"./logging\";\n\n/**\n * Runtime level configuration functionality.\n *\n * @category Helpers\n */\nexpo"
  },
  {
    "path": "src/lib/helpers/tracing.svelte.ts",
    "chars": 3526,
    "preview": "import { ReactiveMap } from \"../utilities.svelte\";\n\nimport { logging } from \"./logging\";\nimport { runtime } from \"./runt"
  },
  {
    "path": "src/lib/helpers/urls.test.ts",
    "chars": 3880,
    "preview": "import { expect, test } from \"vitest\";\n\nimport { urls } from \"./urls\";\n\ntest.only(\"parses with no query parameters\", () "
  },
  {
    "path": "src/lib/helpers/urls.ts",
    "chars": 3644,
    "preview": "import { hash, type Hash } from \"../hash\";\nimport { Query } from \"../query.svelte\";\n\nimport { normalize } from \"./normal"
  },
  {
    "path": "src/lib/hooks.ts",
    "chars": 518,
    "preview": "import type { RouteResult } from \"./route.svelte\";\n\n/**\n * Hooks are functions that can be used to modify the behavior o"
  },
  {
    "path": "src/lib/index.ts",
    "chars": 1304,
    "preview": "export * from \"./actions\";\nexport { active } from \"./actions/active.svelte\";\nexport { RouteOptions } from \"./actions/opt"
  },
  {
    "path": "src/lib/path.ts",
    "chars": 1002,
    "preview": "/**\n * @remarks\n * Future home of more path related functionality.\n */\nimport { Query } from \"./query.svelte\";\n\n/**\n * T"
  },
  {
    "path": "src/lib/query.svelte.ts",
    "chars": 4777,
    "preview": "import { evaluators, type Condition } from \"./helpers/evaluators\";\nimport { goto } from \"./helpers/goto\";\nimport { Ident"
  },
  {
    "path": "src/lib/query.test.ts",
    "chars": 515,
    "preview": "import { describe, expect, test } from \"vitest\";\n\nimport { Query } from \"./query.svelte\";\n\ndescribe(\"query\", () => {\n  t"
  },
  {
    "path": "src/lib/registry.svelte.ts",
    "chars": 3539,
    "preview": "import type { ApplyFn } from \"./route.svelte\";\nimport { RouterInstanceConfig } from \"./router-instance-config\";\nimport {"
  },
  {
    "path": "src/lib/route.svelte.ts",
    "chars": 16631,
    "preview": "/**\n * This is the doc comment for file1.ts\n *\n * @packageDocumentation\n */\nimport type { Component, Snippet } from \"sve"
  },
  {
    "path": "src/lib/router-instance-config.ts",
    "chars": 3291,
    "preview": "import { type Component } from \"svelte\";\n\nimport type { Hook } from \"./hooks\";\nimport { RouteConfig } from \"./route.svel"
  },
  {
    "path": "src/lib/router-instance.svelte.ts",
    "chars": 17180,
    "preview": "import { Query, registry, RouterInstanceConfig, Span, type ApplyFn, type Hook } from \".\";\nimport { Route, RouteResult } "
  },
  {
    "path": "src/lib/router-integration.test.ts",
    "chars": 3127,
    "preview": "import { describe, it, expect, vi, beforeEach } from 'vitest';\n\ndescribe('Router Remount Integration', () => {\n  it('sho"
  },
  {
    "path": "src/lib/router-patterns-demo.test.ts",
    "chars": 10741,
    "preview": "import { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { RouterInstanceConfig } from './router-instance-c"
  },
  {
    "path": "src/lib/router-remount.test.ts",
    "chars": 2368,
    "preview": "import { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { RouterInstanceConfig } from './router-instance-c"
  },
  {
    "path": "src/lib/router.svelte",
    "chars": 2764,
    "preview": "<script lang=\"ts\">\n  import { onDestroy, unmount, type Component } from \"svelte\";\n  import { createSpan, Span } from \"./"
  },
  {
    "path": "src/lib/statuses.ts",
    "chars": 1720,
    "preview": "import type { Component } from \"svelte\";\n\nimport type { Route, RouteResult } from \"./route.svelte\";\n\nimport type { Span "
  },
  {
    "path": "src/lib/utilities.svelte.ts",
    "chars": 2684,
    "preview": "/**\n * Wait for a predicate to become true with timeout handling.\n *\n * @param predicate Function that returns boolean t"
  },
  {
    "path": "src/vite-env.d.ts",
    "chars": 245,
    "preview": "/// <reference types=\"vite/client\" />\n\nimport type { TracingConfig } from \"@mateothegreat/svelte5-router\";\n\ninterface Im"
  },
  {
    "path": "svelte.config.js",
    "chars": 118,
    "preview": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n\tpreprocess: [vitePreprocess({})]\n};\n"
  },
  {
    "path": "test/app/LICENSE",
    "chars": 1096,
    "preview": "MIT License\n\nCopyright (c) 2025 Matthew Davis <matthew@matthewdavis.io>\n\nPermission is hereby granted, free of charge, t"
  },
  {
    "path": "test/app/index.html",
    "chars": 331,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-w"
  },
  {
    "path": "test/app/package.json",
    "chars": 929,
    "preview": "{\n  \"name\": \"@mateothegreat/svelte-template-app\",\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"v"
  },
  {
    "path": "test/app/readme.md",
    "chars": 261,
    "preview": "# 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>)"
  },
  {
    "path": "test/app/src/app.css",
    "chars": 23,
    "preview": "@import \"tailwindcss\";\n"
  },
  {
    "path": "test/app/src/app.svelte",
    "chars": 1691,
    "preview": "<script lang=\"ts\">\n  import { route, Router, type RouteConfig, type RouterInstance } from \"@mateothegreat/svelte5-router"
  },
  {
    "path": "test/app/src/main.ts",
    "chars": 157,
    "preview": "import { mount } from \"svelte\";\n\nimport \"./app.css\";\nimport App from \"./app.svelte\";\n\nconst app = mount(App, {\n  target:"
  },
  {
    "path": "test/app/src/routes/test-a/test-a.svelte",
    "chars": 176,
    "preview": "<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  <di"
  },
  {
    "path": "test/app/src/routes/test-b/test-b.svelte",
    "chars": 495,
    "preview": "<script lang=\"ts\">\n  import { replace } from \"@mateothegreat/svelte5-router\";\n\n  setTimeout(() => {\n    replace(\"/test-a"
  },
  {
    "path": "test/app/svelte.config.ts",
    "chars": 115,
    "preview": "import { vitePreprocess } from \"@sveltejs/vite-plugin-svelte\";\n\nexport default {\n  preprocess: vitePreprocess()\n};\n"
  },
  {
    "path": "test/app/tsconfig.json",
    "chars": 516,
    "preview": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsisten"
  },
  {
    "path": "test/app/vite.config.ts",
    "chars": 427,
    "preview": "import { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport { svelteInspector } from \"@sveltejs/vite-plugin-svelte-ins"
  },
  {
    "path": "tsconfig.build.json",
    "chars": 486,
    "preview": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"ESNext\",\n    \"moduleResolution\": "
  },
  {
    "path": "tsconfig.json",
    "chars": 548,
    "preview": "{\n  \"extends\": \"@tsconfig/svelte/tsconfig.json\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"forceConsisten"
  },
  {
    "path": "vite.config.ts",
    "chars": 1380,
    "preview": "// vite.config.ts\nimport { svelte } from \"@sveltejs/vite-plugin-svelte\";\nimport fs from \"fs-extra\";\nimport path from \"pa"
  },
  {
    "path": "vitest.config.ts",
    "chars": 190,
    "preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      reporter: ["
  },
  {
    "path": "vitest.setup.ts",
    "chars": 43,
    "preview": "import '@testing-library/jest-dom/vitest';\n"
  }
]

About this extraction

This page contains the full source code of the mateothegreat/svelte5-router GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 159 files (815.7 KB), approximately 222.3k tokens, and a symbol index with 104 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!