Full Code of pateketrueke/yrv for AI

master 9b853a2d9ebb cached
42 files
76.3 KB
23.7k tokens
28 symbols
1 requests
Download .txt
Repository: pateketrueke/yrv
Branch: master
Commit: 9b853a2d9ebb
Files: 42
Total size: 76.3 KB

Directory structure:
gitextract_601t9p7l/

├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── release.yml
│       └── testing.yml
├── .gitignore
├── CHANGELOG.md
├── Makefile
├── README.md
├── components.js
├── debug.js
├── e2e/
│   ├── app/
│   │   ├── import.esm.js
│   │   ├── routers.js
│   │   └── testing.js
│   ├── cases/
│   │   ├── main.test.js
│   │   └── routers.test.js
│   ├── components/
│   │   ├── Example.svelte
│   │   ├── Import.svelte
│   │   ├── Main.svelte
│   │   ├── Testing.svelte
│   │   └── nested-routers/
│   │       ├── App.svelte
│   │       ├── Home.svelte
│   │       ├── List.svelte
│   │       ├── Login.svelte
│   │       ├── NewTeam.svelte
│   │       ├── NotFound.svelte
│   │       └── Players.svelte
│   └── helpers.js
├── package.json
├── src/
│   ├── index.js
│   ├── lib/
│   │   ├── Link.svelte
│   │   ├── Route.svelte
│   │   ├── Router.svelte
│   │   ├── router.js
│   │   └── utils.js
│   ├── test/
│   │   ├── _layout.pug
│   │   ├── folder.pug
│   │   ├── import.pug
│   │   ├── index.pug
│   │   └── routers.pug
│   └── vendor.js
└── types/
    └── index.d.ts

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

================================================
FILE: .eslintignore
================================================
e2e/public/assets
e2e/components/Import.svelte


================================================
FILE: .eslintrc
================================================
{
  "env": {
    "es6": true,
    "browser": true
  },
  "extends": "airbnb-base",
  "plugins": [
    "svelte3"
  ],
  "overrides": [
    {
      "files": ["*.svelte"],
      "processor": "svelte3/svelte3"
    }
  ],
  "parserOptions": {
    "ecmaVersion": 2019,
    "sourceType": "module"
  },
  "rules" : {
    "max-len": ["error", {
      "code": 150
    }],
    "arrow-parens": ["error", "as-needed"],
    "indent": 0,
    "strict": 0,
    "prefer-const": 0,
    "no-console": 0,
    "no-labels": 0,
    "no-unused-labels": 0,
    "no-restricted-syntax": 0,
    "no-multi-assign": 0,
    "prefer-destructuring": 0,
    "function-paren-newline": 0,
    "global-require": 0,
    "prefer-spread": 0,
    "prefer-rest-params": 0,
    "prefer-arrow-callback": 0,
    "arrow-body-style": 0,
    "no-restricted-globals": 0,
    "consistent-return": 0,
    "no-param-reassign": 0,
    "no-underscore-dangle": 0,
    "no-multiple-empty-lines": 0,
    "import/first": 0,
    "import/extensions": 0,
    "import/no-unresolved": 0,
    "import/no-dynamic-require": 0,
    "import/no-mutable-exports": 0,
    "import/no-extraneous-dependencies": 0,
    "import/prefer-default-export": 0
  }
}


================================================
FILE: .gitattributes
================================================
package-lock.json -diff
dist/* -diff


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    branches:
      - master

jobs:
  Release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          persist-credentials: false
          fetch-depth: 0

      - uses: actions/setup-node@v3
        with:
          node-version: 14

      - run: npm i
      - run: npm test
      - run: HASHCHANGE=true npm test

      - run: |
          git config --local user.name "Release Bot"
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"

      - run: npm run release

      - name: Push Changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}
          tags: true

      - name: Publish
        run: make release && npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


================================================
FILE: .github/workflows/testing.yml
================================================
name: build

on:
  pull_request:
    branches:
    - master

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: tacoss/nodejs@v2
        with:
          args: make ci


================================================
FILE: .gitignore
================================================
.DS_Store
*.todo
dist
build
cache.json
node_modules
e2e/public/assets
package-lock.json


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [0.0.57](https://github.com/pateketrueke/yrv/compare/v0.0.56...v0.0.57) (2022-06-26)


### Bug Fixes

* use actions/setup-node instead ([7106391](https://github.com/pateketrueke/yrv/commit/7106391a64783c514c9578147e129021c201087b))

### [0.0.56](https://github.com/pateketrueke/yrv/compare/v0.0.55...v0.0.56) (2022-06-26)

### [0.0.55](https://github.com/pateketrueke/yrv/compare/v0.0.52...v0.0.55) (2022-06-26)


### Bug Fixes

* revert alias support ([8e1b907](https://github.com/pateketrueke/yrv/commit/8e1b9072f1b0bc462018c19ddcac3f5b53b896c7))
* run tests before releasing ([77e30d8](https://github.com/pateketrueke/yrv/commit/77e30d840cc73acdf49a084da55b147b33accd18))

### [0.0.54](https://github.com/pateketrueke/yrv/compare/v0.0.52...v0.0.54) (2022-06-26)


### Bug Fixes

* revert alias support ([8e1b907](https://github.com/pateketrueke/yrv/commit/8e1b9072f1b0bc462018c19ddcac3f5b53b896c7))
* run tests before releasing ([77e30d8](https://github.com/pateketrueke/yrv/commit/77e30d840cc73acdf49a084da55b147b33accd18))

### [0.0.52](https://github.com/pateketrueke/yrv/compare/v0.0.51...v0.0.52) (2022-01-21)

### [0.0.51](https://github.com/pateketrueke/yrv/compare/v0.0.50...v0.0.51) (2022-01-16)

### [0.0.50](https://github.com/pateketrueke/yrv/compare/v0.0.49...v0.0.50) (2021-08-13)


### Bug Fixes

* make sure vendor.js is bundled ([b863696](https://github.com/pateketrueke/yrv/commit/b8636962b82bb5488c2ec5286c2c9c3097165104))

### [0.0.49](https://github.com/pateketrueke/yrv/compare/v0.0.48...v0.0.49) (2021-08-13)

### [0.0.48](https://github.com/pateketrueke/yrv/compare/v0.0.47...v0.0.48) (2021-08-11)

### [0.0.47](https://github.com/pateketrueke/yrv/compare/v0.0.46...v0.0.47) (2021-06-30)

### [0.0.46](https://github.com/pateketrueke/yrv/compare/v0.0.45...v0.0.46) (2021-06-27)


### Bug Fixes

* run tests on CI for PRs only, master is for release ([c5ad7a4](https://github.com/pateketrueke/yrv/commit/c5ad7a4c66cab235cd09970f6f9bffe13560701a))
* setup NPM token before releasing ([258dbe2](https://github.com/pateketrueke/yrv/commit/258dbe2211514c4d544470e8b00e6c8011e7fdae))

### [0.0.45](https://github.com/pateketrueke/yrv/compare/v0.0.44...v0.0.45) (2021-06-27)


### Bug Fixes

* move release step outside npm scripts ([40a870c](https://github.com/pateketrueke/yrv/commit/40a870cbe99a7dc05cdb4a633083914011c4fac4))

### [0.0.44](https://github.com/pateketrueke/yrv/compare/v0.0.43...v0.0.44) (2021-06-27)


### Bug Fixes

* debug entry-point ([e500aad](https://github.com/pateketrueke/yrv/commit/e500aad01fb3314f2a84cdf7079b5f194b5015ad))
* enable DEBUG while on dev and CI ([2bddc76](https://github.com/pateketrueke/yrv/commit/2bddc761be5e791005b345d97e43b0b06e10a084))
* rebuild before tests to enable cross-env changes ([6aea3f8](https://github.com/pateketrueke/yrv/commit/6aea3f8d9cc5daac53e97127d56901ae33362ed2))
* use ...activeProps for active Route components ([cf41085](https://github.com/pateketrueke/yrv/commit/cf41085c3aaa20bd4d25d76f9e1b6650a997e808))

### [0.0.43](https://github.com/pateketrueke/yrv/compare/v0.0.42...v0.0.43) (2021-06-27)

### [0.0.42](https://github.com/pateketrueke/yrv/compare/v0.0.41...v0.0.42) (2021-05-09)

### [0.0.41](https://github.com/pateketrueke/yrv/compare/v0.0.40...v0.0.41) (2021-05-09)

### [0.0.40](https://github.com/pateketrueke/yrv/compare/v0.0.39...v0.0.40) (2021-05-09)

### [0.0.39](https://github.com/pateketrueke/yrv/compare/v0.0.38...v0.0.39) (2021-03-31)

### [0.0.38](https://github.com/pateketrueke/yrv/compare/v0.0.37...v0.0.38) (2021-03-10)


### Bug Fixes

* make sure types are included on the package; closes [#72](https://github.com/pateketrueke/yrv/issues/72) ([72c07f2](https://github.com/pateketrueke/yrv/commit/72c07f2fa57aefef23af093fab58430e318231a9))

### [0.0.37](https://github.com/pateketrueke/yrv/compare/v0.0.35...v0.0.37) (2021-03-10)


### Features

* adds in auto release and auto-publishing ([f587f9b](https://github.com/pateketrueke/yrv/commit/f587f9b35c48586b05bcc08f645021ed801c073b))
* Adds in Standard Version ([af0a5e4](https://github.com/pateketrueke/yrv/commit/af0a5e4d6b9f25480a5b3b88ed3151c00516792c)), closes [#28](https://github.com/pateketrueke/yrv/issues/28)
* Adds in TypeScript definitions for the repository ([4273f09](https://github.com/pateketrueke/yrv/commit/4273f09a778ff38b7c11eb0ef108209ac5f12749))


### Bug Fixes

* Adds changes to move to ubuntu runner instead of self-hosted ([83f605c](https://github.com/pateketrueke/yrv/commit/83f605ce8f0eedc58de761af1b9ee8901a0dda8c))
* patch top-level pending component on routers, thanks to [@jhechtf](https://github.com/jhechtf) - closes [#34](https://github.com/pateketrueke/yrv/issues/34) ([83f74bc](https://github.com/pateketrueke/yrv/commit/83f74bcae474ba122c5dacc8c4aeb0625b2645ea))

### [0.0.36](https://github.com/pateketrueke/yrv/compare/v0.0.35...v0.0.36) (2021-03-09)


### Features

* adds in auto release and auto-publishing ([f587f9b](https://github.com/pateketrueke/yrv/commit/f587f9b35c48586b05bcc08f645021ed801c073b))
* Adds in Standard Version ([af0a5e4](https://github.com/pateketrueke/yrv/commit/af0a5e4d6b9f25480a5b3b88ed3151c00516792c)), closes [#28](https://github.com/pateketrueke/yrv/issues/28)
* Adds in TypeScript definitions for the repository ([4273f09](https://github.com/pateketrueke/yrv/commit/4273f09a778ff38b7c11eb0ef108209ac5f12749))


### Bug Fixes

* Adds changes to move to ubuntu runner instead of self-hosted ([83f605c](https://github.com/pateketrueke/yrv/commit/83f605ce8f0eedc58de761af1b9ee8901a0dda8c))
* patch top-level pending component on routers, thanks to [@jhechtf](https://github.com/jhechtf) - closes [#34](https://github.com/pateketrueke/yrv/issues/34) ([83f74bc](https://github.com/pateketrueke/yrv/commit/83f74bcae474ba122c5dacc8c4aeb0625b2645ea))

### [0.0.35](https://github.com/pateketrueke/yrv/compare/v0.0.34...v0.0.35) (2020-11-13)

### [0.0.34](https://github.com/pateketrueke/yrv/compare/v0.0.33...v0.0.34) (2020-11-13)


### Bug Fixes

* skip :headless ([1e4d0eb](https://github.com/pateketrueke/yrv/commit/1e4d0eb426945635c48c150a8b26dbf970dc2841))
* wait 1sec before check ([a6479cf](https://github.com/pateketrueke/yrv/commit/a6479cfd12d49350e14189d21e58d70bd60e5132))

### [0.0.33](https://github.com/pateketrueke/yrv/compare/v0.0.32...v0.0.33) (2020-09-24)


### Bug Fixes

* clean ./build before test ([60fc680](https://github.com/pateketrueke/yrv/commit/60fc6800e9bd952d14fa6cb98cc8267c106aeb3f))
* dev, test and build ready; lock deps ([1dfe683](https://github.com/pateketrueke/yrv/commit/1dfe683e5e03b8cd0f7b6ed91d86585aee384200))
* disable DEBUG on dist, include src/ files ([eea9b5a](https://github.com/pateketrueke/yrv/commit/eea9b5a15704e660a3d2b070f6f73b4363d783b7))
* enable DEBUG for whole testing ([64a21cf](https://github.com/pateketrueke/yrv/commit/64a21cfd48065bdeb582f38f76af10f52ff9d46d))
* import from ./src during development ([6616e98](https://github.com/pateketrueke/yrv/commit/6616e986483731ba443fb675482583a1b369e918))
* print fallback branch when missing DEBUG ([d34b6ab](https://github.com/pateketrueke/yrv/commit/d34b6abb8d291bc0e749abaf310c844cbf02071f))
* remove cache too ([a438b4e](https://github.com/pateketrueke/yrv/commit/a438b4e2080e0849298d38c1b6b36a2e7088dc1e))
* right setup for DEBUG mode ([7f247fd](https://github.com/pateketrueke/yrv/commit/7f247fd259702149982f20674f3265c7e1ac352d))
* split sub tasks ([7fca020](https://github.com/pateketrueke/yrv/commit/7fca020283f955b8826923323029199bd98ba8b3))

### [0.0.32](https://github.com/pateketrueke/yrv/compare/v0.0.31...v0.0.32) (2020-09-23)


### Bug Fixes

* package built files, not sources ([610f016](https://github.com/pateketrueke/yrv/commit/610f0169d7c1dc38c11ca68efaa15a976c5596f1))

### [0.0.31](https://github.com/pateketrueke/yrv/compare/v0.0.30...v0.0.31) (2020-09-23)

### [0.0.30](https://github.com/pateketrueke/yrv/compare/v0.0.29...v0.0.30) (2020-04-11)

### [0.0.29](https://github.com/pateketrueke/yrv/compare/v0.0.28...v0.0.29) (2020-04-10)

### [0.0.28](https://github.com/pateketrueke/yrv/compare/v0.0.27...v0.0.28) (2020-04-10)

### [0.0.27](https://github.com/pateketrueke/yrv/compare/v0.0.26...v0.0.27) (2020-03-25)

### [0.0.26](https://github.com/pateketrueke/yrv/compare/v0.0.25...v0.0.26) (2020-03-15)

### [0.0.25](https://github.com/pateketrueke/yrv/compare/v0.0.24...v0.0.25) (2020-03-06)

### [0.0.24](https://github.com/pateketrueke/yrv/compare/v0.0.23...v0.0.24) (2020-02-29)

### [0.0.23](https://github.com/pateketrueke/yrv/compare/v0.0.22...v0.0.23) (2020-02-29)

### [0.0.22](https://github.com/pateketrueke/yrv/compare/v0.0.21...v0.0.22) (2020-02-29)

### [0.0.21](https://github.com/pateketrueke/yrv/compare/v0.0.20...v0.0.21) (2020-02-29)

### [0.0.20](https://github.com/pateketrueke/yrv/compare/v0.0.19...v0.0.20) (2020-02-29)

### [0.0.19](https://github.com/pateketrueke/yrv/compare/v0.0.18...v0.0.19) (2020-02-20)

### [0.0.18](https://github.com/pateketrueke/yrv/compare/v0.0.17...v0.0.18) (2020-01-25)

### [0.0.17](https://github.com/pateketrueke/yrv/compare/v0.0.16...v0.0.17) (2020-01-20)

### [0.0.16](https://github.com/pateketrueke/yrv/compare/v0.0.15...v0.0.16) (2019-12-14)

### [0.0.15](https://github.com/pateketrueke/yrv/compare/v0.0.14...v0.0.15) (2019-12-07)

### [0.0.14](https://github.com/pateketrueke/yrv/compare/v0.0.13...v0.0.14) (2019-11-19)

### [0.0.13](https://github.com/pateketrueke/yrv/compare/v0.0.12...v0.0.13) (2019-11-16)

### [0.0.12](https://github.com/pateketrueke/yrv/compare/v0.0.10...v0.0.12) (2019-11-08)

### [0.0.10](https://github.com/pateketrueke/yrv/compare/v0.0.9...v0.0.10) (2019-11-04)

### [0.0.9](https://github.com/pateketrueke/yrv/compare/v0.0.8...v0.0.9) (2019-11-01)

### [0.0.8](https://github.com/pateketrueke/yrv/compare/v0.0.7...v0.0.8) (2019-10-28)

### [0.0.7](https://github.com/pateketrueke/yrv/compare/v0.0.6...v0.0.7) (2019-10-26)

### [0.0.6](https://github.com/pateketrueke/yrv/compare/v0.0.5...v0.0.6) (2019-10-18)

### [0.0.5](https://github.com/pateketrueke/yrv/compare/v0.0.4...v0.0.5) (2019-10-18)

### [0.0.4](https://github.com/pateketrueke/yrv/compare/v0.0.3...v0.0.4) (2019-10-18)

### [0.0.3](https://github.com/pateketrueke/yrv/compare/v0.0.2...v0.0.3) (2019-10-17)

### [0.0.2](https://github.com/pateketrueke/yrv/compare/v0.0.1...v0.0.2) (2019-10-17)


================================================
FILE: Makefile
================================================
ifneq ($(DEBUG),)
	E2E_FLAGS=--debug-on-fail
endif

help: Makefile
	@awk -F':.*?##' '/^[a-z0-9\\%!:-]+:.*##/{gsub("%","*",$$1);gsub("\\\\",":*",$$1);printf "\033[36m%8s\033[0m %s\n",$$1,$$2}' $<

ci: src deps clean ## Run CI scripts
	@npm test -- --color $(E2E_FLAGS)
	@HASHCHANGE=true npm test -- --color $(E2E_FLAGS)

dev: src deps ## Start dev tasks
	@npm run dev

e2e: src deps ## Run E2E tests locally
	@npm run test:e2e -- e2e/cases $(E2E_FLAGS)

test: src deps clean
	@npm test

deps: package*.json
	@(((ls node_modules | grep .) > /dev/null 2>&1) || npm i) || true

clean:
	@rm -f cache.json
	@rm -rf build/*

release: clean
	@NODE_ENV=production npm run build
ifneq ($(CI),)
	@echo '//registry.npmjs.org/:_authToken=$${NODE_AUTH_TOKEN}' > .npmrc
endif


================================================
FILE: README.md
================================================
<div align="center">

![yrv](Japan_road_sign_201-D.svg)

![Build status](https://github.com/pateketrueke/yrv/workflows/build/badge.svg)
[![NPM version](https://img.shields.io/npm/v/yrv)](https://www.npmjs.com/package/yrv)
[![Known Vulnerabilities](https://snyk.io/test/npm/yrv/badge.svg)](https://snyk.io/test/npm/yrv)
[![donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY)

</div>

> The `v` is for Svelte

Built on top of [abstract-nested-router](https://www.npmjs.com/package/abstract-nested-router), so you can use nested routers, also:

- Advanced parameters can be used, e.g. `/:id<\d+>` &mdash; [see docs](https://www.npmjs.com/package/abstract-nested-router#params)
- ARIA-compliant, sets `[aria-current="page"]` on active links
- Seamless `<base href="..." />` integration
- Conditionals and redirection through props
- Fallback `<Route />` handlers
- Hash and URI-based routes
- Support for [query-string](https://www.npmjs.com/package/query-string)
- [REPL ready!](https://svelte.dev/repl/0f07c6134b16432591a9a3a0095a80de?version=3.12.1)

> `yrv` will use any _base-href_ found on the current page to rewrite links and routes.

## Usage

Install `yrv` through NPM or Yarn:

```html
<script>
  import { Router, Route, Link } from 'yrv';
</script>

<Link href="/">Home</Link>
| <Link href="/World">Hello</Link>
| <Link href="/not/found">NotFound</Link>

<p>
  <Router>
    <Route exact>Hello World</Route>
    <Route fallback>Not found</Route>
    <Route exact path="/:name" let:router>Hey {router.params.name}!</Route>
  </Router>
</p>
```

> Notice `fallback` routes can’t be placed at the beginning, otherwise further routes will not be mounted. :bomb:

## Components

> You MUST declare at least, one top-level `Router` to setup the bindings.

### `<Router {path} {pending} {disabled} {condition} {nofallback} />`

This component will hold any given routes as children, `path` is always derived from parent routes.

Available props:

- `{path}` &mdash; Any segment to derive a fullpath from, defaults to `/`
- `{pending}` &mdash; Svelte-component or String; top-level `pending` support
- `{disabled}` &mdash; Boolean; Similar to condition, but for bound props
- `{condition}` &mdash; Function; if given, render only if evaluates to true
- `{nofallback}` &mdash; If set, non-matched routes will never raise a failure

> Nested routers does not need the same path to be declared inside, e.g. if the router for `/top` has a `/sub` router inside, inner router will use the route `/top/sub`, (the same as declaring `/top/sub` route outside the parent router).

### `<Route {key} {path} {exact} {pending} {fallback} {component} {disabled} {condition} {redirect} let:router />`

Main container for routing, they can hold any component or children.

Available props:

- `{key}` &mdash; The route identity, not its path; defaults to random pseudo-hash
- `{path}` &mdash; Any segment to derive a fullpath from, default to `/`
- `{exact}` &mdash; If set, the route will render only if the route exactly matches
- `{pending}` &mdash; Svelte-component or String; rendered during the loading of dynamic components
- `{fallback}` &mdash; If set, the route will render only if no more routes were matched
- `{component}` &mdash; Accepts either a valid svelte-component, a promise, or a dynamic import function
- `{disabled}` &mdash; Boolean; Similar to `condition`, but for bound props
- `{condition}` &mdash; Function; if given, the route will render only if evaluates to `true`
- `{redirect}` &mdash; Alternate redirection location, only if the previous condition was `true`
- `let:router` &mdash; Injects the `router` context, it also provides `failure` in case of errors

> If you omit `exact`, then `/x` would match both `/` and `/x` routes &mdash; [see docs](https://www.npmjs.com/package/abstract-nested-router#params)

When `yrv` adds a new route, it'll use any given `key` from its props &mdash; once routes are detached they're also removed from the router registry, due to that, the next time the same route is mounted a new key is generated (if isn't present already).

```html
<script>
  import SvelteComponent from 'path/to/svelte-component.svelte';
</script>

<Link href="/">Home</Link>
| <Link href="/svelte-component">Svelte component</Link>
| <Link href="/promise">Promised component</Link>
| <Link href="/lazy">Lazy component</Link>

<p>
  <Router>
    <Route exact>Hello World</Route>
    <Route exact path="/svelte-component" component={SvelteComponent}/>
    <Route exact path="/promise" component="{import('path/to/other-component.svelte')}"/>
    <Route exact path="/lazy" component="{() => import('path/to/another-component.svelte')}"/>
  </Router>
</p>
```

> Behind the scenes, for making dynamic-imports work, the bundler _should_ inline them or just write-out the required chunks to make it work natively (`<script type="module" />`) or through `shimport`, etc.

### `<Link {go} {href} {open} {title} {exact} {reload} {replace} {class} />`

In order to navigate, you can use `Link` components, or regular links, etc.

> All `href` values MUST be absolute, only links starting with `/` or `#` are allowed.

Available props:

- `{go}` &mdash; History shortcut (see below)
- `{href}` &mdash; New location, default to `/`
- `{open}` &mdash; Same behavior as `<a target="_blank">`
- `{title}` &mdash; HTML title-attribute value
- `{button}` &mdash; If set, will use button-tag instead
- `{exact}` &mdash; Determine if link should match exactly to be set as active
- `{reload}` &mdash; Use `location.href` instead
- `{replace}` &mdash; Use `history.replaceState()` instead
- `{class}` &mdash; Custom class-name for the mounted anchor

> The value for `open` can be a string including the window specs, e.g. `width=400,height=200` &mdash; a `on:close` event will be fired once the opened window is closed.

Normal `on:click` events are still allowed, so you can use:

```html
<Link on:click={() => location.reload()}>Reload</Link>
```

> Active _links_ will gain the `[aria-current]` attribute, and `[disabled]` if they're buttons.

Aditionally, you can setup  `go` to move around:

- `"back"` &mdash; String; if given, will invoke `history.back()`
- `"fwd"` &mdash; String; if given, will invoke `history.fwd()`
- `n` &mdash; Number; if given, it'll be used to invoke `history.go(n)`

> If navigating through `history` is not possible a normal redirection will run. :anchor:

## Public API

- `navigateTo(path[, options])` &mdash; Change the location, supported options are:
  - `reload` &mdash; If true, it will use `document.location.href` instead
  - `replace` &mdash; If true, it will use `history.replaceState()` instead
  - `params` &mdash; Used to replace `:placeholders` from given path
  - `queryParams` &mdash; Additional search-params for the new location
- `$router` &mdash; Store with shared routeInfo details, similar to `let:router`

> `yrv` gracefully degrades to `location.hash` on environments where `history` is not suitable, also it can be forced through `router.hashchange = true`.

### Route Info

Route changes are propagated through stores, if you want to listen too just subscribe, e.g.

```js
import { router } from 'yrv';

router.subscribe(e => {
  if (!e.initial) console.log(e);
});
```

Using this technique you gain access to the same detail object as `let:router` does.

> Notice the `initial` property is present as soon the store is initialized, consecutive changes will not have it anymore.

### IE11 support

Support for IE11 is _granted_ if you include, at least, the following polyfills before your application:

```html
<script>if (!!window.MSInputMethodContext && !!document.documentMode)
  document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Promise,Object.getOwnPropertyDescriptors"><\/script>');</script>
<script src="your-app.js"></script>
```

> `document.write()` is used because conditional comments were dropped in IE10, so this way you can conditionally load polyfills anyway.

Also, you MUST [enable either `buble` or `babel`](https://github.com/sveltejs/svelte/issues/2621) within your build pipeline to transpile down to ES5.

### Frequently Asked Questions

**How to conditionally render a `<Router />` component?**

Both Route/Router components support the `disabled` and `condition` props, but:

- Use `condition` to allow/disallow route-dispatching dynamically
- Use `disabled` to skip from rendering, it will add/remove the route

This new `disabled` prop would work as you're expecting:

```html
<Router disabled={!showNavBar}>
  ...
</Router>
```

**What means the `exact` property and how it works?**

Say you have three routes:

- `/a` (exact)
- `/a/b` (non-exact)
- `/a/b/c` (exact)

Now, you navigate from `/a` to `/a/b/c`:

- Since `/a` was active, and it was exact, `yrv` clears out the `routeInfo` for that route.
- Since `/a/b` is not exact, `yrv` activate this route because is half-way to the final route.

> If you plan to have more routes nested, then the route will never be `exact` (at least at top-levels).

This is also true for `<Link />` components &mdash; as soon as they match the `[aria-current]` attribute will be added on them to denote _active_ links.

If the link for `/a` were also `exact`, then it'll be _active_ if the matching route is `/a` only.

**Why `path` can't be an empty string like other routers does?**

Even if browsers treat `http://localhost:8080` and `http://localhost:8080/` as the same thing I wanted to keep paths clear as possible.

Internally `yrv` normalizes any given URI to keep a trailing slash, so `/foo` is `/foo/` for matching purposes.

Also, the default path is usually `/` so there's no point on having to declare anything else:

```html
<Route>OK</Route>
<Route path="/">OK</Route>
```

**What is `routeInfo` and how can I access it outside routes?**

This object is very similar to what you get with `let:router` inside components.

Use the `$router` store to access it, e.g.

```html
<script>
  import { router } from 'yrv';
</script>
<pre>{JSON.stringify($router, null, 2)}</pre>
```

**Why does Yrv not work with Parcel or webpack/snowpack?**

If you're getting any of the errors below:

- store.subscribe is not a function
- Class constructor SvelteComponent cannot be invoked without 'new'
- 'on_outro' is not exported by [...]
- 'target' is a required option

Make sure you're using the right settings:

1. Add mainFields into resolve config, e.g. `mainFields: ['svelte', 'browser', 'module', 'main']`
2. Remove `exclude: /node_modules/` from `svelte-loader` config

> If you're using an online tool that is not the official Svelte REPL the behavior is unexpected and no further support will be granted.

**Can I use hash-based routes _à la_ Gmail? e.g. `index.html#/profile`, `index.html#/book/42`?**

Yes, URIs like that are suitable for embedded apps like Electron, where normal URLs would fail.

Also this mode is the default used on the Svelte REPL, because is not an iframe, nor a regular webpage... it's a weird thing!

> If you enable `router.hashchange = true` all your regular links will be automatically rewritten to hash-based URIs instead, see how it works in our test suite.

**Why I'm getting `<Component> was created with unknown prop 'router'` in the browser's console?**

If you're not using the `router` prop inside your route-components then just add:

```html
<script>
  export const router = null;
</script>
```

That will remove the warning and also will make `eslint-plugin-svelte3` in your workflow happy.

**Why `router.subscribe` is called two times when I first open the page?**

Any subscription to stores will fire twice as they have an initial value, once the router resolves (e.g. the initial route) then a second event is fired.

> In this case, and additional property `initial` is added to identify such event.

**Is there any method that allows me to detect route change?**

Yes, you can subscribe to the router store, e.g. `router.subscribe(...)` &mdash; [see above](#route-info).

**Is there a way to reduce the bundle size of yrv?**

Since `v0.0.46` you'll be getting the most reduced version we can ship, however it comes without development warnings.

> Consume it as `import { ... } from 'yrv/debug'` right away and you'll get a more complete version with included DEBUG information.


================================================
FILE: components.js
================================================
export { default as Router } from './src/lib/Router.svelte';
export { default as Route } from './src/lib/Route.svelte';
export { default as Link } from './src/lib/Link.svelte';

export { navigateTo, router } from './src/lib/utils';


================================================
FILE: debug.js
================================================
export * from './build/dev/index.js';


================================================
FILE: e2e/app/import.esm.js
================================================
import App from '../components/Import.svelte';

new App({ target: document.body }); // eslint-disable-line


================================================
FILE: e2e/app/routers.js
================================================
import App from '../components/nested-routers/App.svelte';

new App({ target: document.body }); // eslint-disable-line


================================================
FILE: e2e/app/testing.js
================================================
import App from '../components/Main.svelte';

/* global USE_HASH_CHANGE */
let hashchange;
if (typeof USE_HASH_CHANGE !== 'undefined' && USE_HASH_CHANGE) {
  hashchange = USE_HASH_CHANGE;
}

new App({ target: document.body, props: { hashchange } }); // eslint-disable-line


================================================
FILE: e2e/cases/main.test.js
================================================
import { Selector } from 'testcafe';
import { url, href } from '../helpers';

/* global fixture, test */

fixture('yrv (dsl)')
  .page(url('/'));

test('it just loads!', async t => {
  await t.expect(Selector('h1').withText('Example page').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
  await t.expect(Selector('a').withText('Home').getAttribute('href')).eql(href('/'));
  await t.expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok();
});

test('it would mount Route-less content', async t => {
  await t.expect(Selector('p[data-test=routeless]').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test('it should mount from slot-content nodes', async t => {
  await t.click(Selector('a').withText('Test page'));
  await t.expect(Selector('h2').withText('Testing features').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(2);
});

test('it should allow to bind <Link {href} /> and such', async t => {
  await t.typeText(Selector('[data-test=custominput]'), 'success');
  await t.expect(Selector('[data-test=customhref]').getAttribute('href')).contains('/success');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

fixture('yrv (example)')
  .page(url('/example'));

test('it should mount "Hello World"', async t => {
  await t.expect(Selector('[data-test=example]').withText('Hello World').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test('it should mount nested content', async t => {
  await t.click(Selector('a').withText('Link'));
  await t.expect(Selector('[data-test=example]').withText('Hello a').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(2);
  await t.expect(Selector('[data-test=unordered]').innerText).notContains('III');

  await t.click(Selector('a').withText('List'));
  await t.expect(Selector('[data-test=unordered]').innerText).eql('III');

  await t.click(Selector('a').withText('Show'));
  await t.expect(Selector('[data-test=unordered]').innerText).eql('II III');

  await t.click(Selector('a').withText('Edit'));
  await t.expect(Selector('[data-test=unordered]').innerText).eql('I II III');
});

test('it should fallback on unmatched routes', async t => {
  await t.click(Selector('a').withText('Broken link'));
  await t.expect(Selector('[data-test=example]').withText('Not found').visible).ok();
  await t.expect(Selector('[data-test=example]').innerText).notContains('Hello a');
  await t.expect(Selector('[data-test=counter]').innerText).contains(2);
});

fixture('yrv (fallback)')
  .page(url('/e'));

test('should not mount any fallback et all', async t => {
  await t.expect(Selector('h2[data-test=fallback]').exists).notOk();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test.page(url('/e/im_not_exists'))('should handle non-matched routes as fallback', async t => {
  await t.expect(Selector('h2').withText('NOT FOUND').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

fixture('yrv (buttons)')
  .page(url('/test'));

test('it should disable Link buttons if they are active', async t => {
  const UndoButton = Selector('button').withText('Undo');
  const Parameters = Selector('[data-test=parameters]');

  await t.expect(UndoButton.visible).ok();
  await t.expect(UndoButton.hasAttribute('disabled')).ok();
  await t.click(Selector('a').withText('Test props'));

  await t.expect(Parameters.visible).ok();
  await t.expect(UndoButton.hasAttribute('disabled')).notOk();

  await t.click(UndoButton);
  await t.expect(Parameters.exists).notOk();
  await t.expect(UndoButton.hasAttribute('disabled')).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(3);
});

fixture('yrv (query params)')
  .page(url('/test/props'));

test('it should parse from location.search', async t => {
  await t.expect(Selector('li').withText('query: {}').exists).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);

  await t.expect(Selector('a').withText('Test page').getAttribute('href')).eql(href('/test'));
  await t.expect(Selector('a').withText('Test page').hasAttribute('aria-current')).ok();

  await t.expect(Selector('a').withText('Test props').getAttribute('href')).eql(href('/test/props'));
  await t.expect(Selector('a').withText('Test props').hasAttribute('aria-current')).ok();
});

test('it should take queryParams from navigateTo()', async t => {
  await t.click(Selector('a').withText('Do not click!'));
  await t.expect(Selector('li').withText('query: {"truth":"42"}').exists).ok();

  await t.typeText(Selector('[data-test=key]'), 'x');
  await t.typeText(Selector('[data-test=value]'), 'y');
  await t.click(Selector('[data-test=append]'));

  await t.expect(Selector('li').withText('query: {"truth":"42","x":"y"}').exists).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(3);
});

fixture('yrv (middleware)')
  .page(url('/test/props'));

test('it should redirect if the given route matches', async t => {
  await t.click(Selector('a').withText('Redirect'));
  await t.expect(Selector('button').withText('Undo').hasAttribute('disabled')).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(3);
});

test('it should mount or redirect based on given condition', async t => {
  await t.setNativeDialogHandler(() => false);
  await t.click(Selector('a').withText('Protected'));
  await t.expect(Selector('[data-test=redirect]').innerText).contains('Wrong!');

  await t.setNativeDialogHandler(() => true);
  await t.click(Selector('a').withText('Protected'));
  await t.expect(Selector('[data-test=redirect]').innerText).contains('Yay!');
  await t.expect(Selector('[data-test=counter]').innerText).contains(4);
});

fixture('yrv (nested params)')
  .page(url('/test/props'));

test('it should inject params from resolved routes', async t => {
  await t.click(Selector('a').withText('Hello World.'));
  await t.expect(Selector('p').withText('Value: Hello World').visible).ok();
  await t.expect(Selector('[data-test=counter]').innerText).contains(2);
});

if (!process.env.HASHCHANGE) {
  fixture('yrv (anchored routes)')
    .page(url('/sub'));

  test('it should inject params from resolved routes', async t => {
    await t.click(Selector('a').withText('Root'));
    await t.expect(Selector('p[data-test=anchored]').innerText).contains('HOME');
    await t.expect(Selector('p[data-test=anchored]').innerText).notContains('ABOUT');
    await t.expect(Selector('[data-test=counter]').innerText).contains(2);
  });

  test('it should skip non-exact routes from matched ones', async t => {
    await t.click(Selector('a').withText('About page'));
    await t.expect(Selector('p[data-test=anchored]').innerText).contains('ABOUT');
    await t.expect(Selector('p[data-test=anchored]').innerText).notContains('HOME');
    await t.expect(Selector('[data-test=counter]').innerText).contains(2);
  });

  test('it should handle non-matched routes as fallback', async t => {
    await t.click(Selector('a').withText('Broken anchor'));
    await t.expect(Selector('h2[data-test=fallback]').exists).notOk();
    await t.expect(Selector('fieldset').innerText).contains("Unreachable '/sub#broken'");
    await t.expect(Selector('[data-test=counter]').innerText).contains(2);
  });
}

fixture('yrv (nested routes)')
  .page(url('/top'));

test('it should nothing at top-level', async t => {
  await t.expect(Selector('p[data-test=nested]').innerText).contains('?');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('a');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('b');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('c');

  await t.click(Selector('a').withText('1'));
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('?');
  await t.expect(Selector('p[data-test=nested]').innerText).contains('a');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('b');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('c');

  await t.click(Selector('a').withText('2'));
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('?');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('a');
  await t.expect(Selector('p[data-test=nested]').innerText).contains('b');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('c');

  await t.click(Selector('a').withText('3'));
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('?');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('a');
  await t.expect(Selector('p[data-test=nested]').innerText).notContains('b');
  await t.expect(Selector('p[data-test=nested]').innerText).contains('c');
  await t.expect(Selector('[data-test=counter]').innerText).contains(4);
});

fixture('yrv (hashed routes)')
  .page(url('/gist'));

test('it should load root-handlers', async t => {
  await t.expect(Selector('[data-test=hashed]').innerText).contains('GIST INFO');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('SHA1: N/A');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test.page(url('/gist#test'))('it should load sub-handlers', async t => {
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test.page(url('/gist#test/edit'))('it should load nested sub-handlers (/edit)', async t => {
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('(edit)');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test.page(url('/gist#test/save'))('it should load nested root-handlers (/save)', async t => {
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('(save)');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

test.page(url('/gist#test/not_found'))('it should fail on unreachable routes', async t => {
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('GIST INFO');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('SHA1: test');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(edit)');
  await t.expect(Selector('[data-test=hashed]').innerText).notContains('(save)');
  await t.expect(Selector('[data-test=hashed]').innerText).contains('Unreachable');
  await t.expect(Selector('[data-test=counter]').innerText).contains(1);
});

fixture('yrv (conditional routes)')
  .page(url('/auth'));

test('it should redirect from protected pages', async t => {
  await t.click(Selector('a').withText('Protected page'));
  await t.expect(Selector('[data-test=logged]').innerText).contains('Log-in');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.');
  await t.expect(Selector('[data-test=counter]').innerText).contains(4);
});

test('it should skip redirections otherwise', async t => {
  await t.click(Selector('[data-test=logged]').find('input'));
  await t.click(Selector('a').withText('→'));
  await t.expect(Selector('[data-test=logged]').innerText).contains('Welcome back.');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('Log-in');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.');
  await t.expect(Selector('[data-test=counter]').innerText).contains(3);
});

test('it should allow routes if conditions are met', async t => {
  await t.expect(Selector('[data-test=secret]').exists).notOk();
  await t.click(Selector('[data-test=logged]').find('input'));
  await t.click(Selector('a').withText('Protected page'));
  await t.expect(Selector('[data-test=secret]').innerText).contains('Top-secret');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('Log-in');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.');
  await t.expect(Selector('[data-test=logged]').innerText).contains('O.K.');

  await t.click(Selector('[data-test=logged]').find('input'));
  await t.expect(Selector('[data-test=secret]').exists).notOk();
  await t.expect(Selector('[data-test=logged]').innerText).contains('Log-in');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('Welcome back.');
  await t.expect(Selector('[data-test=logged]').innerText).notContains('O.K.');
  await t.expect(Selector('[data-test=counter]').innerText).contains(4);
});

fixture('yrv (dynamic imports)')
  .page(url('/import', true));

test('it should allow routes to be loaded through dynamic-imports', async t => {
  await t
    .expect(Selector('[data-test=container]').innerText).contains('Loading...')
    .expect(Selector('[data-test=import]').exists)
    .ok();
});

if (!process.env.HASHCHANGE) {
  fixture('yrv (base-href)')
    .page(url('/folder', true));

  test('it should rebase all links to preserve base-href location', async t => {
    await t.expect(Selector('a').withText('Home').getAttribute('href')).eql(href('/folder'));
    await t.expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok();
  });

  test('it should handle <base href="..." /> on all routes and links', async t => {
    await t.click(Selector('a').withText('Test page'));
    await t.expect(Selector('h2').withText('Testing features').visible).ok();

    await t.click(Selector('a').withText('Test props'));
    await t.click(Selector('a').withText('Do not click!'));
    await t.expect(Selector('li').withText('query: {"truth":"42"}').exists).ok();

    await t.click(Selector('a').withText('Anchor page'));
    await t.click(Selector('a').withText('Root'));

    await t.expect(Selector('p[data-test=anchored]').innerText).contains('HOME');
    await t.expect(Selector('p[data-test=anchored]').innerText).notContains('ABOUT');

    await t.click(Selector('a').withText('Link'));
    await t.expect(Selector('p[data-test=example').innerText).contains('Hello a');
    await t.expect(Selector('[data-test=counter]').innerText).contains(7);
  });
}

// this test seems to be working if you don't use chrom*:headless
if (!process.argv.some(x => x.includes(':headless'))) {
  fixture('yrv (links a new-tab)')
    .page(url('/'));

  test('should open links on new tabs', async t => {
    const initialURL = await t.eval(() => document.documentURI);

    await t.click(Selector('a').withText('Test page'), { modifiers: { meta: true } }).wait(1000);

    const openedURL = await t.eval(() => document.documentURI);

    await t.expect(initialURL).notEql(openedURL);

    try {
      // this crashes the browser after closing
      await t.eval(() => window.close());
    } catch (e) {
      // ok
    }

    const prevURL = await t.eval(() => document.documentURI);

    await t.expect(initialURL).eql(prevURL);
  });
}


================================================
FILE: e2e/cases/routers.test.js
================================================
import { Selector } from 'testcafe';
import { url } from '../helpers';

/* global fixture, test */

fixture('yrv (nested routers)')
  .page(url('/routers', true));

test('should keep working as expected when isLoggedIn is unchecked', async t => {
  await t
    .click(Selector('input[type=checkbox]'))
    .click(Selector('input[type=checkbox]'));

  await t
    .expect(Selector('h1').innerText).contains('This is Home')
    .expect(Selector('a').withText('Home').hasAttribute('aria-current')).ok();

  await t
    .click(Selector('a').withText('Players'))
    .expect(Selector('h1').innerText).contains('This is Players');

  await t.expect(Selector('h2').innerText).contains('This is List');

  await t
    .expect(Selector('a').withText('Players').hasAttribute('aria-current')).ok()
    .expect(Selector('a').withText('List').hasAttribute('aria-current')).ok();

  await t
    .click(Selector('a').withText('New Team'))
    .expect(Selector('h2').innerText).contains('This is New Team');

  await t.expect(Selector('a').withText('New Team').hasAttribute('aria-current')).ok();

  await t
    .click(Selector('a').withText('Not found'))
    .expect(Selector('h1').innerText).contains('This is NotFound');

  await t.expect(Selector('a').withText('Not found').hasAttribute('aria-current')).ok();
});

test('should fallback to <Login /> when isLoggedIn is unchecked', async t => {
  await t
    .click(Selector('input[type=checkbox]'));

  await t
    .expect(Selector('a').withText('Home').exists).ok()
    .expect(Selector('h1').innerText).contains('This is Login');

  await t
    .click(Selector('a').withText('Players'))
    .expect(Selector('h1').innerText).contains('This is Login');

  await t
    .click(Selector('a').withText('Not found'))
    .expect(Selector('h1').innerText).contains('This is Login');
});


================================================
FILE: e2e/components/Example.svelte
================================================
<p data-test="import">Components can be lazy-loaded through <code>`() => import('...')`</code> calls.</p>


================================================
FILE: e2e/components/Import.svelte
================================================
<script>
  import { Router, Route } from 'yrv';

  function delay(promise) {
    return new Promise(ok => setTimeout(() => ok(promise), 500));
  }
</script>

<div data-test="container">
  <Router pending="Loading...">
    <Route fallback component={() => delay(import('./Example.svelte'))} />
  </Router>
</div>


================================================
FILE: e2e/components/Main.svelte
================================================
<script>
  import {
    Router, Route, Link, router,
  } from 'yrv';

  import Testing from './Testing.svelte';

  export let hashchange = null;

  router.hashchange = hashchange;

  let loggedIn;
  let myLink = '/';
  let count = 0;

  router.subscribe(info => {
    if (!info.initial) count += 1;
  });
</script>

<h1 id="top">
  <span>Example page</span>
  <small>{$router.path}</small>
</h1>

<p>This content is static, always shown.</p>

<Link exact href="/">Home</Link> | <Link href="/test">Test page</Link>
| <Link href="/sub">Anchor page</Link> | <Link href="/e">Error page</Link>

<p>
  Links can open windows, and they can set callbacks too:
  <Link open href="//google.com" on:close={() => /* eslint-disable no-alert */ alert('GREAT!')}>Open window</Link>
</p>

<Router path="/example">
  <Link exact href="/example/a">Link</Link> | <Link exact href="/example/a/b">Broken link</Link>

  <p data-test="example">
    <Route fallback>Not found?</Route>
    <Route exact>Hello World</Route>
    <Route exact path="/:name" let:router>Hello {router.params.name}</Route>
  </p>
</Router>

<p>
  Routes with exactly one segment are considered nested routes, e.g.

  <Link href="/top/foo/a">1</Link>
  | <Link href="/top/bar/b">2</Link>
  | <Link href="/top/bar/c">3</Link>
  | <Link href="/top">?</Link>
</p>

<p>
  Link's `href` can be changed as well, e.g.
  <Link data-test="customhref" href={myLink}>Change me!</Link>
  <input data-test="custominput" type="text" bind:value={myLink} />
</p>

<p data-test="nested">
  <Router path="/top">
    <Route exact fallback>?</Route>
    <Route exact path="/foo/a">a</Route>
    <Route exact path="/bar/b">b</Route>
    <Route exact path="/bar/c">c</Route>
  </Router>
</p>

<Router path="/test" nofallback>
  <Route>
    <h2>Testing features</h2>

    <p>This content is always mounted when the current URL starts-with <tt>/test</tt>.</p>

    <Link exact button go="-1" href="/test">Undo</Link> | <Link href="/test/props">Test props</Link>

    | <Link href="/test/static">Redirect</Link>
    | <Link href="/test/dynamic">Protected</Link>

    <p data-test="redirect">
      <Route path="/failed">Wrong!</Route>
      <Route path="/static" redirect="/test" />
      <Route path="/dynamic" redirect="/test/failed" condition={() => /* eslint-disable no-alert */ window.confirm('Are you sure?')}>Yay!</Route>
    </p>
  </Route>

  <Route path="/props" component={Testing} />

  <p data-test="routeless">Any <tt>Route</tt>-less content is always shown!</p>
</Router>

<div data-test="hashed">
  <Router path="/gist">
    <Route exact>GIST INFO</Route>
    <Router path="#:sha1" nofallback>
      <Route let:router>SHA1: {router.params.sha1 || 'N/A'}</Route>
      <Route exact path="/edit">(edit)</Route>
      <Route exact path="/save">(save)</Route>
    </Router>
  </Router>
</div>

<Link exact href="/gist">gists</Link>
| <Link href="/gist#x" exact>gshow</Link>
| <Link href="/gist#x/edit">gedit</Link>
| <Link href="/gist#x/save">gsave</Link>

<hr />

<div data-test="logged">
  <label>
    <input type="checkbox" bind:checked={loggedIn} /> on/off
  </label>

  <Link exact href="/auth">&rarr;</Link>
  | <Link href="/auth/login">Login</Link>
  | <Link href="/auth/not_found">Not found</Link>
  | <Link href="/auth/protected">Protected page</Link>

  <Router path="/auth">
    <Route path="/protected" condition={() => loggedIn} redirect="/auth/login">O.K.</Route>
    <Route path="/login">Log-in</Route>

    <Route condition={() => loggedIn} exact redirect="/auth/login" />
    <Route disabled={!loggedIn} exact>Welcome back.</Route>
  </Router>
</div>

<Router disabled={!loggedIn}>
  <p data-test="secret">
    <Route>Shhhh! Top-secret</Route>
  </p>
</Router>

<Router path="/sub">
  <Route>
    <Link exact href="/sub#">Root</Link> | <Link href="/sub#/about">About page</Link> | <Link href="/sub#broken">Broken anchor</Link>
  </Route>

  <p data-test="anchored">
    <Route exact path="#">HOME</Route>
    <Route exact path="#/about">ABOUT</Route>
  </p>
</Router>

<Router path="/e">
  <Route exact>
    <h2>It works!</h2>
  </Route>

  <Route fallback>
    <h2 data-test="fallback">NOT FOUND</h2>
  </Route>
</Router>

<div>
  <p>You can also hook into the router's state with <code>`router.subscribe(...)`, e.g.</code></p>
  <p data-test="counter">Times router-info has been changed: {count}</p>
</div>

<p data-test="unordered">
  <Router path="/page">
    <Route path="/:x/:y">I</Route>
    <Route path="/:x">II</Route>
    <Route path="/">III</Route>
  </Router>
</p>

<Link href="/page">List</Link>
<Link href="/page/1">Show</Link>
<Link href="/page/1/edit">Edit</Link>

<a style="margin-top:1000px;display:block" href="#top">&uarr;</a>


================================================
FILE: e2e/components/Testing.svelte
================================================
<script>
  import {
    Router, Route, Link, navigateTo,
  } from 'yrv';

  export let router = null;

  let newKey = '';
  let newValue = '';

  function overrideQueryParams(key, value) {
    if (key) {
      navigateTo(router.path, { replace: true, queryParams: { ...router.query, [key]: value } });
    }
  }

  function addNewValue() {
    overrideQueryParams(newKey, newValue);

    newKey = '';
    newValue = '';
  }

  function rmValue(key) {
    overrideQueryParams(key);
  }
</script>

<h3>Injected parameters</h3>

<fieldset data-test="parameters">
  <legend>router</legend>
  <ul>
    <li>key: {JSON.stringify(router.key)}</li>
    <li>matches: {JSON.stringify(router.matches)}</li>
    <li>params: {JSON.stringify(router.params)}</li>
    <li>route: {JSON.stringify(router.route)}</li>
    <li>query: {JSON.stringify(router.query)}</li>
    <li>path: {JSON.stringify(router.path)}</li>
  </ul>

  <table>
    <caption>QueryParams</caption>
    <tr>
      <th>key</th>
      <th>value</th>
    </tr>
    {#each Object.entries(router.query) as [key, value]}
      <tr>
        <td>{key}</td>
        <td>{value}</td>
        <td><button on:click={() => rmValue(key)}>rm</button></td>
      </tr>
    {/each}
    <tr>
      <td><input data-test="key" bind:value={newKey} /></td>
      <td><input data-test="value" bind:value={newValue} /></td>
      <td><button data-test="append" on:click={addNewValue}>add</td>
    </tr>
  </table>

  <Link on:click={() => overrideQueryParams('truth', 42)}>Do not click!</Link>
  | <Link href="/test/props/Hello%20World.">Hello World.</Link>
</fieldset>

<Router>
  <Route path="/:value" let:router>
    <p>Value: {router.params.value}</p>
  </Route>
</Router>


================================================
FILE: e2e/components/nested-routers/App.svelte
================================================
<script>
  import { Router, Route, Link } from 'yrv';

  import Home from './Home.svelte';
  import Players from './Players.svelte';
  import NotFound from './NotFound.svelte';
  import Login from './Login.svelte';

  let isLoggedIn = true;
</script>

<Link href="/">Home</Link> |
<Link href="/players">Players</Link> |
<Link href="/notfound">Not found</Link> |
<input type="checkbox" bind:checked={isLoggedIn}> isLoggedIn

<Router>
  {#if isLoggedIn}
    <Route key="home" exact component="{Home}" />
    <Route key="play" path="/players" component="{Players}" />
    <Route key="404" fallback component={NotFound} />
  {:else}
    <Route key="ask" fallback component={Login} />
  {/if}
</Router>


================================================
FILE: e2e/components/nested-routers/Home.svelte
================================================
<h1>This is Home</h1>


================================================
FILE: e2e/components/nested-routers/List.svelte
================================================
<h2>This is List</h2>


================================================
FILE: e2e/components/nested-routers/Login.svelte
================================================
<h1>This is Login</h1>


================================================
FILE: e2e/components/nested-routers/NewTeam.svelte
================================================
<h2>This is New Team</h2>


================================================
FILE: e2e/components/nested-routers/NotFound.svelte
================================================
<h1>This is NotFound</h1>


================================================
FILE: e2e/components/nested-routers/Players.svelte
================================================
<script>
  import { Router, Route, Link } from 'yrv';

  import List from './List.svelte';
  import NewTeam from './NewTeam.svelte';
</script>

<h1>This is Players</h1>

<Link exact href="/players">List</Link> |
<Link href="/players/team/new">New Team</Link>

<Router>
  <Route exact component="{List}" />
  <Route path="/team/new" exact component="{NewTeam}" />
</Router>


================================================
FILE: e2e/helpers.js
================================================
const prefix = process.env.BASE_URL || 'http://localhost:8080';

export function url(x, y) {
  if (!y && process.env.HASHCHANGE) {
    return `${prefix}#${x}`;
  }

  return prefix + x;
}

export function href(x) {
  if (process.env.HASHCHANGE) {
    return `#${x !== '/' ? x : ''}`;
  }
  return x;
}


================================================
FILE: package.json
================================================
{
  "name": "yrv",
  "version": "0.0.57",
  "description": "Your routing!",
  "types": "types/index.d.ts",
  "_main": "build/dist/index.js",
  "_module": "build/src/index.js",
  "svelte": "build/dist/index.js",
  "files": [
    "build/dev/**",
    "build/dist/**",
    "build/src/index.js",
    "types/*.*",
    "debug.js"
  ],
  "scripts": {
    "release": "standard-version",
    "lint": "eslint --ext js,svelte src e2e",
    "dev": "npm run build:test -- -w",
    "postbuild": "npm run copy:vendor && npm run copy:index",
    "prebuild": "rm -rf build",
    "copy:vendor": "cp build/src/vendor.js build/dev/ && cp build/src/vendor.js build/dist/",
    "copy:index": "cp src/index.js build/dev/ && cp src/index.js build/dist/",
    "mortero": "mortero -ayrv:./components USE_HASH_CHANGE=$HASHCHANGE DEBUG=true --platform browser",
    "build:module": "npm run mortero -- src -Xtest -Dbuild/src -Nsvelte -fyindex.js --format esm",
    "build:debug": "npm run mortero -- src/lib -Xtest -fKDbuild/dev --format esm",
    "build:test": "npm run mortero -- src e2e -X{lib,cases,components} -Xhelpers.js -X'**/_*'",
    "build:all": "mortero src/lib -fKXtest -Dbuild/dist --format esm --platform browser",
    "build": "npm run build:all && npm run build:debug && npm run build:module",
    "test:e2e": "testcafe ${BROWSER:-chrome:headless} --color -q",
    "test": "npm run build:test -- -fyjs && npm run test:e2e -- -a 'npm run dev' e2e/cases"
  },
  "mortero": {
    "bundle": [
      "**/app/*.js",
      "**/index.js",
      "**/vendor.js"
    ],
    "rename": [
      "**/test/**/!(index).html:{basedir}/{name}/index.html",
      "**/{src,e2e}/**:{filepath/1}",
      "**/test/**:{filepath/1}"
    ]
  },
  "bugs": {
    "url": "https://github.com/pateketrueke/yrv/issues"
  },
  "homepage": "https://github.com/pateketrueke/yrv",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/pateketrueke/yrv.git"
  },
  "author": "Alvaro Cabrera <pateketrueke@gmail.com>",
  "license": "MIT",
  "keywords": [
    "router",
    "svelte",
    "svelte3",
    "svelte-router",
    "svelte3-router"
  ],
  "peerDependencies": {
    "svelte": "3.x"
  },
  "devDependencies": {
    "abstract-nested-router": "^0.2.1",
    "eslint": "^7.27.0",
    "eslint-config-airbnb-base": "^14.0.0",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-svelte3": "^3.2.0",
    "eslint-utils": ">=1.4.1",
    "mortero": "^0.0.62",
    "pug": "^3.0.0",
    "query-string": "^6.8.3",
    "standard-version": "^9.0.0",
    "svelte": "^3.48.0",
    "testcafe": "1.18.6"
  }
}


================================================
FILE: src/index.js
================================================
export { default as Router } from './lib/Router.svelte';
export { default as Route } from './lib/Route.svelte';
export { default as Link } from './lib/Link.svelte';

export { navigateTo, router } from './lib/utils';


================================================
FILE: src/lib/Link.svelte
================================================
<script>
  import { createEventDispatcher } from 'svelte';

  import {
    ROOT_URL, fixedLocation, navigateTo, cleanPath, isActive, getProps, router,
  } from './utils';

  let ref;
  let active;
  let cssClass = '';
  let fixedHref = null;

  export let go = null;
  export let open = null;
  export let href = '';
  export let title = '';
  export let button = false;
  export let exact = false;
  export let reload = false;
  export let replace = false;
  export { cssClass as class };

  // replacement for `Object.keys(arguments[0].$$.props)`
  const thisProps = ['go', 'open', 'href', 'class', 'title', 'button', 'exact', 'reload', 'replace'];

  // rebase active URL
  $: if (!/^(\w+:)?\/\//.test(href)) {
    fixedHref = cleanPath(ROOT_URL, true) + cleanPath(router.hashchange ? `#${href}` : href);
  }

  $: if (ref && $router.path) {
    if (isActive(href, $router.path, exact)) {
      if (!active) {
        active = true;
        ref.setAttribute('aria-current', 'page');

        if (button) {
          ref.setAttribute('disabled', true);
        }
      }
    } else if (active) {
      active = false;
      ref.removeAttribute('disabled');
      ref.removeAttribute('aria-current');
    }
  }

  // extract additional props
  $: fixedProps = getProps($$props, thisProps);

  const dispatch = createEventDispatcher();

  // this will enable `<Link on:click={...} />` calls
  function handleOnClick(e) {
    e.preventDefault();

    if (typeof go === 'string' && window.history.length > 1) {
      if (go === 'back') window.history.back();
      else if (go === 'fwd') window.history.forward();
      else window.history.go(parseInt(go, 10));
      return;
    }

    if (!fixedHref && href !== '') {
      if (open) {
        let specs = typeof open === 'string' ? open : '';

        const wmatch = specs.match(/width=(\d+)/);
        const hmatch = specs.match(/height=(\d+)/);

        if (wmatch) specs += `,left=${(window.screen.width - wmatch[1]) / 2}`;
        if (hmatch) specs += `,top=${(window.screen.height - hmatch[1]) / 2}`;

        if (wmatch && !hmatch) {
          specs += `,height=${wmatch[1]},top=${(window.screen.height - wmatch[1]) / 2}`;
        }

        const w = window.open(href, '', specs);
        const t = setInterval(() => {
          if (w.closed) {
            dispatch('close');
            clearInterval(t);
          }
        }, 120);
      } else window.location.href = href;
      return;
    }

    fixedLocation(href, () => {
      navigateTo(fixedHref || '/', { reload, replace });
    }, () => dispatch('click', e));
  }

  function handleAnchorOnClick(e) {
    // user used a keyboard shortcut to force open link in a new tab
    if (e.metaKey || e.ctrlKey || e.button !== 0) {
      return;
    }
  
    handleOnClick(e);
  }
</script>

{#if button}
  <button {...fixedProps} bind:this={ref} class={cssClass} {title} on:click={handleOnClick}>
    <slot />
  </button>
{:else}
  <a {...fixedProps} href={cleanPath(fixedHref || href)} bind:this={ref} class={cssClass} {title} on:click={handleAnchorOnClick}>
    <slot />
  </a>
{/if}


================================================
FILE: src/lib/Route.svelte
================================================
<script context="module">
  import { writable } from 'svelte/store';
  import { routeInfo } from './router';
  import {
    CTX_ROUTER, CTX_ROUTE, router, getProps, isPromise, isSvelteComponent,
  } from './utils';
</script>

<script>
  import { onDestroy, getContext, setContext } from 'svelte';

  export let key = null;
  export let path = '/';
  export let exact = null;
  export let pending = null;
  export let disabled = false;
  export let fallback = null;
  export let component = null;
  export let condition = null;
  export let redirect = null;

  // replacement for `Object.keys(arguments[0].$$.props)`
  const thisProps = ['key', 'path', 'exact', 'pending', 'disabled', 'fallback', 'component', 'condition', 'redirect'];

  const routeContext = getContext(CTX_ROUTE);
  const routerContext = getContext(CTX_ROUTER);

  const { assignRoute, unassignRoute, pendingComponent } = routerContext || {};

  const routePath = routeContext ? routeContext.routePath : writable(path);

  let activeRouter = null;
  let activeProps = {};
  let fullpath;
  let hasLoaded;

  const fixedRoot = $routePath !== path && $routePath !== '/'
    ? `${$routePath}${path !== '/' ? path : ''}`
    : path;

  function resolve() {
    const fixedRoute = path !== fixedRoot && fixedRoot.substr(-1) !== '/'
      ? `${fixedRoot}/`
      : fixedRoot;

    [key, fullpath] = assignRoute(key, fixedRoute, {
      condition, redirect, fallback, exact,
    });
  }

  // IF DEBUG
  let failure;

  try {
    if (redirect !== null && !/^(?:\w+:\/\/|\/)/.test(redirect)) {
      throw new TypeError(`Expecting valid URL to redirect, given '${redirect}'`);
    }

    if (condition !== null && typeof condition !== 'function') {
      throw new TypeError(`Expecting condition to be a function, given '${condition}'`);
    }

    if (path.charAt() !== '#' && path.charAt() !== '/') {
      throw new TypeError(`Expecting a leading slash or hash, given '${path}'`);
    }

    if (!assignRoute) {
      throw new TypeError(`Missing top-level <Router>, given route: ${path}`);
    }

    resolve();
  } catch (e) {
    failure = e;
  }
  // ENDIF
  // IF_NOT DEBUG
  resolve();
  // ENDIF

  $: if (key) {
    activeRouter = !disabled && $routeInfo[key];
    activeProps = getProps($$props, thisProps);
    activeProps.router = activeRouter;
  }

  $: if (activeRouter) {
    for (const k in $router.params) {
      if (typeof activeRouter.params[k] === 'undefined') {
        activeRouter.params[k] = $router.params[k];
      }
    }

    if (!component) { // component passed as slot
      hasLoaded = true;
    } else if (isSvelteComponent(component)) { // component passed as Svelte component
      hasLoaded = true;
    } else if (isPromise(component)) { // component passed as import()
      component.then(module => {
        component = module.default;
        hasLoaded = true;
      });
    } else { // component passed as () => import()
      component().then(module => {
        component = module.default;
        hasLoaded = true;
      });
    }
  }

  onDestroy(() => {
    if (unassignRoute) {
      unassignRoute(fullpath);
    }
  });

  setContext(CTX_ROUTE, {
    routePath,
  });
</script>

<!--IF DEBUG-->
<style>
  [data-failure] {
    color: red;
  }
</style>

{#if failure}
  <p data-failure>{failure}</p>
{/if}
<!--ENDIF-->

{#if activeRouter}
<!--<fieldset><legend>{key} ({exact} | {fullpath})</legend>-->
  {#if !hasLoaded}
    {#if pending || pendingComponent}
      {#if isSvelteComponent(pending)}
        <svelte:component this={pending} {...activeProps} />
      {:else if isSvelteComponent(pendingComponent)}
        <svelte:component this={pendingComponent} {...activeProps} />
      {:else}
        {pending || pendingComponent}
      {/if}
    {/if}
  {:else}
    {#if component}
      <svelte:component this={component} {...activeProps} />
    {:else}
      <slot {...activeProps} />
    {/if}
  {/if}
<!--</fieldset>-->
{/if}


================================================
FILE: src/lib/Router.svelte
================================================
<script context="module">
  import { writable } from 'svelte/store';
  import { CTX_ROUTER, router } from './utils';
  import {
    baseRouter, addRouter, findRoutes, doFallback,
  } from './router';

  // const __CACHED_ROUTES__ = new Map();
</script>

<script>
  import {
    onMount, onDestroy, getContext, setContext,
  } from 'svelte';

  let cleanup;
  let failure;
  let fallback;

  export let key = '';
  export let path = '/';
  export let pending = null;
  export let disabled = false;
  export let condition = null;

  // IF DEBUG
  export let nofallback = false;
  // ENDIF

  const routerContext = getContext(CTX_ROUTER);
  const basePath = routerContext ? routerContext.basePath : writable(path);

  const fixedRoot = $basePath !== path && $basePath !== '/'
    ? `${$basePath}${path !== '/' ? path : ''}`
    : path;

  // IF DEBUG
  try {
    if (condition !== null && typeof condition !== 'function') {
      throw new TypeError(`Expecting condition to be a function, given '${condition}'`);
    }

    if (path.charAt() !== '#' && path.charAt() !== '/') {
      throw new TypeError(`Expecting a leading slash or hash, given '${path}'`);
    }
  } catch (e) {
    failure = e;
  }
  // ENDIF

  function assignRoute(_key, route, detail) {
    _key = _key || `route-${Math.random().toString(36).substr(2)}`;

    const $key = [key, _key].filter(Boolean).join('.');
    const handler = { key: $key, ...detail };

    let fullpath;
    baseRouter.mount(fixedRoot, () => {
      fullpath = baseRouter.add(route, handler);
      fallback = (handler.fallback && $key) || fallback;
    });

    findRoutes();

    return [$key, fullpath];
  }

  function unassignRoute(route) {
    try {
      baseRouter.rm(route);
    } catch (e) {
      // 🔥 this is fine...
    }
    findRoutes();
  }

  function onError(err) {
    failure = err;

    if (failure && fallback) {
      doFallback(failure, fallback);
    }
  }

  onMount(() => {
    cleanup = addRouter(fixedRoot, fallback, onError);
  });

  onDestroy(() => {
    if (cleanup) cleanup();
  });

  setContext(CTX_ROUTER, {
    basePath,
    assignRoute,
    unassignRoute,
    pendingComponent: pending,
  });

  $: if (condition) {
    disabled = !condition($router);
  }
</script>

{#if !disabled}
  <slot router={$router} />
{/if}

<!--IF DEBUG-->
<style>
  [data-failure] {
    border: 1px dashed silver;
  }
</style>

{#if failure && !fallback && !nofallback}
  <fieldset data-failure>
    <legend>Router failure: {path}</legend>
    <pre>{failure}</pre>
  </fieldset>
{/if}
<!--ENDIF-->


================================================
FILE: src/lib/router.js
================================================
import { writable } from 'svelte/store';
import { Router, parse } from '../vendor';

import {
  ROOT_URL, navigateTo, cleanPath, isActive, router,
} from './utils';

export const baseRouter = new Router();
export const routeInfo = writable({});

// private registries
const onError = {};
const shared = {};

let errors = [];
let routers = 0;
let interval;
let currentURL;

// take snapshot from current state...
router.subscribe(value => { shared.router = value; });
routeInfo.subscribe(value => { shared.routeInfo = value; });

export function doFallback(failure, fallback) {
  routeInfo.update(defaults => ({
    ...defaults,
    [fallback]: {
      ...shared.router,
      failure,
    },
  }));
}

export function handleRoutes(map, params, enforce) {
  map.some(x => {
    if (x.key && (enforce || (x.matches && !shared.routeInfo[x.key]))) {
      if (x.redirect && (x.condition === null || x.condition(shared.router) !== true)) {
        if (x.exact && shared.router.path !== x.path) return false;
        navigateTo(x.redirect);
        return true;
      }

      if (x.exact && x.path !== currentURL) {
        if (currentURL.replace(/[#/]$/, '') !== x.path) return false;
      }

      if (enforce && x.fallback) {
        return false;
      }

      Object.assign(params, x.params);

      // upgrade matching routes!
      routeInfo.update(defaults => ({
        ...defaults,
        [x.key]: {
          ...shared.router,
          ...x,
        },
      }));
    }

    return false;
  });
}

export function evtHandler() {
  let baseUri = !router.hashchange ? window.location.href.replace(window.location.origin, '') : window.location.hash || '/';
  let failure;

  // unprefix active URL
  if (ROOT_URL !== '/') {
    baseUri = baseUri.replace(cleanPath(ROOT_URL), '');
  }

  // skip given anchors if already exists on document, see #43
  if (
    /^#[\w-]+$/.test(window.location.hash)
    && document.querySelector(window.location.hash)
    && currentURL === baseUri.split('#')[0]
  ) return;

  // trailing slash is required to keep route-info on nested routes!
  // see: https://github.com/pateketrueke/abstract-nested-router/commit/0f338384bddcfbaee30f3ea2c4eb0c24cf5174cd
  const normalizedURL = baseUri.replace('/#', '#').replace(/^#\//, '/');
  const [path, qs] = normalizedURL.split('?');
  const fullpath = path.replace(/\/?$/, '/');
  const params = {};

  if (currentURL !== normalizedURL) {
    currentURL = normalizedURL;
    router.set({
      path: cleanPath(fullpath),
      query: parse(qs),
      params,
    });
  }

  routeInfo.set({});

  // load all matching routes...
  baseRouter.resolve(fullpath, (err, result) => {
    if (err) {
      failure = err;
      return;
    }

    handleRoutes(result, params);
  });

  if (!failure) {
    try {
      handleRoutes(baseRouter.find(fullpath), params, true);
    } catch (e) {
      // noop
    }
  }

  // it's fine to omit failures for '/' paths
  if (failure && failure.path !== '/') {
    console.debug(failure);
  } else {
    failure = null;
  }

  // clear previously failed handlers
  errors.forEach(cb => cb());
  errors = [];

  let fallback;

  // invoke error-handlers to clear out previous state!
  Object.keys(onError).forEach(root => {
    if (isActive(root, fullpath, false)) {
      const fn = onError[root].callback;

      fn(failure);
      errors.push(fn);
    }

    if (!fallback && onError[root].fallback) {
      fallback = onError[root].fallback;
    }
  });

  // handle unmatched fallbacks
  if (failure && fallback) {
    doFallback(failure, fallback);
  }
}

export function findRoutes() {
  clearTimeout(interval);
  interval = setTimeout(evtHandler);
}

export function addRouter(root, fallback, callback) {
  if (!routers) {
    window.addEventListener('popstate', findRoutes, false);
  }

  // register error-handlers
  if (!onError[root] || fallback) {
    onError[root] = { fallback, callback };
  }

  routers += 1;

  return () => {
    routers -= 1;

    if (!routers) {
      window.removeEventListener('popstate', findRoutes, false);
    }
  };
}


================================================
FILE: src/lib/utils.js
================================================
import { writable } from 'svelte/store';
import { Router, stringify } from '../vendor';

const cache = {};
const baseTag = document.getElementsByTagName('base');
const basePrefix = (baseTag[0] && baseTag[0].href) || '/';

export const ROOT_URL = basePrefix.replace(window.location.origin, '');

export const router = writable({
  path: '/',
  query: {},
  params: {},
  initial: true,
});

export const CTX_ROUTER = {};
export const CTX_ROUTE = {};

// use location.hash on embedded pages, e.g. Svelte REPL
let HASHCHANGE = window.location.origin === 'null';

export function hashchangeEnable(value) {
  if (typeof value === 'boolean') {
    HASHCHANGE = !!value;
  }

  return HASHCHANGE;
}

Object.defineProperty(router, 'hashchange', {
  set: value => hashchangeEnable(value),
  get: () => hashchangeEnable(),
  configurable: false,
  enumerable: false,
});

export function fixedLocation(path, callback, doFinally) {
  const baseUri = router.hashchange ? window.location.hash.replace('#', '') : window.location.pathname;

  // this will rebase anchors to avoid location changes
  if (path.charAt() !== '/') {
    path = baseUri + path;
  }

  const currentURL = baseUri + window.location.hash + window.location.search;

  // do not change location et all...
  if (currentURL !== path) {
    callback(path);
  }

  // invoke final guard regardless of previous result
  if (typeof doFinally === 'function') {
    doFinally();
  }
}

export function cleanPath(uri, fix) {
  return uri !== '/' || fix ? uri.replace(/\/$/, '') : uri;
}

export function navigateTo(path, options) {
  const {
    reload, replace,
    params, queryParams,
  } = options || {};

  // If path empty or no string, throws error
  if (!path || typeof path !== 'string' || (path[0] !== '/' && path[0] !== '#')) {
    throw new Error(`Expecting '/${path}' or '#${path}', given '${path}'`);
  }

  if (params) {
    path = path.replace(/:([a-zA-Z][a-zA-Z0-9_-]*)/g, (_, key) => params[key]);
  }

  if (queryParams) {
    const qs = stringify(queryParams);

    if (qs) {
      path += `?${qs}`;
    }
  }

  if (router.hashchange) {
    let fixedURL = path.replace(/^#|#$/g, '');

    if (ROOT_URL !== '/') {
      fixedURL = fixedURL.replace(cleanPath(ROOT_URL), '');
    }

    window.location.hash = fixedURL !== '/' ? fixedURL : '';
    return;
  }

  // If no History API support, fallbacks to URL redirect
  if (reload || !window.history.pushState || !window.dispatchEvent) {
    window.location.href = path;
    return;
  }

  // If has History API support, uses it
  fixedLocation(path, nextURL => {
    window.history[replace ? 'replaceState' : 'pushState'](null, '', nextURL);
    window.dispatchEvent(new Event('popstate'));
  });
}

export function getProps(given, required) {
  const { props: sub, ...others } = given;

  // prune all declared props from this component
  required.forEach(k => {
    delete others[k];
  });

  return {
    ...sub,
    ...others,
  };
}

export function isActive(uri, path, exact) {
  if (!cache[[uri, path, exact]]) {
    if (exact !== true && path.indexOf(uri) === 0) {
      cache[[uri, path, exact]] = /^[#/?]?$/.test(path.substr(uri.length, 1));
    } else if (uri.includes('*') || uri.includes(':')) {
      cache[[uri, path, exact]] = Router.matches(uri, path);
    } else {
      cache[[uri, path, exact]] = cleanPath(path) === uri;
    }
  }

  return cache[[uri, path, exact]];
}

export function isPromise(object) {
  return object && typeof object.then === 'function';
}

export function isSvelteComponent(object) {
  return object && object.prototype;
}


================================================
FILE: src/test/_layout.pug
================================================
doctype html
html(lang='en')
  head
    meta(charset='UTF-8')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    meta(http-equiv='X-UA-Compatible', content='ie=edge')
    block head
    style.
      a[aria-current]{font-weight:bold}
  body
    script.
      if (!!window.MSInputMethodContext && !!document.documentMode)
      document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Promise,Object.entries,Object.getOwnPropertyDescriptors"><\\/script>');
    block body


================================================
FILE: src/test/folder.pug
================================================
extends _layout

block head
  base(href='/folder/')
  title Your routing! (base-href)

block body
  script(src='../app/testing.js')


================================================
FILE: src/test/import.pug
================================================
extends _layout

block head
  base(href='/import/')
  title Your routing! (dynamic imports)

block body
  script(src='../app/import.esm.js' type='module')


================================================
FILE: src/test/index.pug
================================================
extends _layout

block head
  title Your routing!

block body
  script(src='/app/testing.js')


================================================
FILE: src/test/routers.pug
================================================
extends _layout

block head
  base(href='/routers/')
  title Your routing! (nested routers)

block body
  script(src='../app/routers.js')


================================================
FILE: src/vendor.js
================================================
export { parse, stringify } from 'query-string';
export { default as Router } from 'abstract-nested-router';


================================================
FILE: types/index.d.ts
================================================
import { SvelteComponentTyped, SvelteComponent } from "svelte";
import { Writable } from 'svelte/store';

interface RouterState {
  path: string;
  query: Record<string, any>;
  params: Record<string, any>;
  initial?: boolean;
}

interface RouterProps {
  /**
   * @description any segment to derive a fullpath from. 
   * @default /
   */
  path?: string;
  /**
   * @description A Svelte-component or string. Shown while we wait for a component to load.
   */
  pending?: SvelteComponent | string;
  /**
   * @description Similar to condition, but for bound props.
   */
  disabled?: boolean;
  /**
   * @description If given, render only if this function evaluates to true.
   */
  condition?: () => boolean;
}
interface RouterSlots {
  router: RouterState;
}
export class Router extends SvelteComponentTyped<RouterProps & {
  /**
   * @description if set, non-matched routes will never raise a failure.
   */
  nofallback?: boolean;
}, any, RouterSlots> { }

interface RouteProps extends RouterProps {
  /**
   * @description the route identity, not its path; defaults to a random psuedo-hash
   */
  key?: string;
  /**
   * @description if set, the route will only render if the route _exact_ matches the current route. 
   */
  exact?: boolean;
  /**
   * @description if set the route will render only if no more routes were matched.
   */
  fallback?: boolean;
  /**
   * @description accepts either a valid Svelte component, a promise that resolves to a component, or a dynamic import function.
   * 
   * _Note:_ supplying a dynamic import function will start the network call immediately when the route is mounted. If you would like to wait until the route is matched to start loading, use `() => import()` instead.
   */
  component?: SvelteComponent | Promise<SvelteComponent> | Promise<typeof import('*.svelte')> | (() => Promise<SvelteComponent>) | (() => Promise<typeof import('*.svelte')>);
  /**
   * @description alternate redirection location, only if the `condition` is true.
   */
  redirect?: string;
}

export class Route extends SvelteComponentTyped<RouteProps> { }

interface LinkProps {
  /**
   * @description history shortcut.
   */
  go?: 'back' | 'fwd' | number;
  /**
   * @description New location; defaults to `/`
   */
  href?: string;
  /** 
   * @description Same behavior as `<a target="_blank">`
   */
  open?: boolean;
  /**
   * @description HTML title-attribute value.
   */
  title?: string;
  /**
   * @description if set will use a button-tag instead of an anchor tag.
   */
  button?: boolean;
  /**
   * determine if the link should be matched exactly in order to be set as active.
   */
  exact?: boolean;
  /**
   * @description use `location.href` instead
   */
  reload?: boolean;
  /**
   * @description use `history.replaceState()` instead.
   */
  replace?: boolean;
  /**
   * @description custom class name for the mounted anchor.
   */
  class?: string;
}

export class Link extends SvelteComponentTyped<LinkProps> { }

interface NavigateToOps {
  /**
   * @description if true will use `location.href` instead.
   */
  reload?: boolean;
  /**
   * @description if true will use `history.replaceState()` instead.
   */
  replace?: boolean;
  /**
   * @description used to replace `:placeholders` in the `path` value.
   */
  params?: Record<string, any>;
  /**
   * @description additional search-parameters for new location.
   */
  queryParams?: string | Record<string, any>;
}
/**
 * 
 * @param path the path to change the URL to.
 * @param options changes behavior of function call.
 */
export function navigateTo(path: string, options?: NavigateToOps);
/**
 * @description a store with shared route information; similar to `let:router`
 * 
 * ```js
 * import { router } from 'yrv'
 * router.subscribe($router => {
 *  // code run on router update.
 * })
 * ```
 */
export const router: Writable<RouterState>

Download .txt
gitextract_601t9p7l/

├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── release.yml
│       └── testing.yml
├── .gitignore
├── CHANGELOG.md
├── Makefile
├── README.md
├── components.js
├── debug.js
├── e2e/
│   ├── app/
│   │   ├── import.esm.js
│   │   ├── routers.js
│   │   └── testing.js
│   ├── cases/
│   │   ├── main.test.js
│   │   └── routers.test.js
│   ├── components/
│   │   ├── Example.svelte
│   │   ├── Import.svelte
│   │   ├── Main.svelte
│   │   ├── Testing.svelte
│   │   └── nested-routers/
│   │       ├── App.svelte
│   │       ├── Home.svelte
│   │       ├── List.svelte
│   │       ├── Login.svelte
│   │       ├── NewTeam.svelte
│   │       ├── NotFound.svelte
│   │       └── Players.svelte
│   └── helpers.js
├── package.json
├── src/
│   ├── index.js
│   ├── lib/
│   │   ├── Link.svelte
│   │   ├── Route.svelte
│   │   ├── Router.svelte
│   │   ├── router.js
│   │   └── utils.js
│   ├── test/
│   │   ├── _layout.pug
│   │   ├── folder.pug
│   │   ├── import.pug
│   │   ├── index.pug
│   │   └── routers.pug
│   └── vendor.js
└── types/
    └── index.d.ts
Download .txt
SYMBOL INDEX (28 symbols across 4 files)

FILE: e2e/helpers.js
  function url (line 3) | function url(x, y) {
  function href (line 11) | function href(x) {

FILE: src/lib/router.js
  function doFallback (line 24) | function doFallback(failure, fallback) {
  function handleRoutes (line 34) | function handleRoutes(map, params, enforce) {
  function evtHandler (line 67) | function evtHandler() {
  function findRoutes (line 152) | function findRoutes() {
  function addRouter (line 157) | function addRouter(root, fallback, callback) {

FILE: src/lib/utils.js
  constant ROOT_URL (line 8) | const ROOT_URL = basePrefix.replace(window.location.origin, '');
  constant CTX_ROUTER (line 17) | const CTX_ROUTER = {};
  constant CTX_ROUTE (line 18) | const CTX_ROUTE = {};
  constant HASHCHANGE (line 21) | let HASHCHANGE = window.location.origin === 'null';
  function hashchangeEnable (line 23) | function hashchangeEnable(value) {
  function fixedLocation (line 38) | function fixedLocation(path, callback, doFinally) {
  function cleanPath (line 59) | function cleanPath(uri, fix) {
  function navigateTo (line 63) | function navigateTo(path, options) {
  function getProps (line 110) | function getProps(given, required) {
  function isActive (line 124) | function isActive(uri, path, exact) {
  function isPromise (line 138) | function isPromise(object) {
  function isSvelteComponent (line 142) | function isSvelteComponent(object) {

FILE: types/index.d.ts
  type RouterState (line 4) | interface RouterState {
  type RouterProps (line 11) | interface RouterProps {
  type RouterSlots (line 30) | interface RouterSlots {
  class Router (line 33) | class Router extends SvelteComponentTyped<RouterProps & {
  type RouteProps (line 40) | interface RouteProps extends RouterProps {
  class Route (line 65) | class Route extends SvelteComponentTyped<RouteProps> { }
  type LinkProps (line 67) | interface LinkProps {
  class Link (line 106) | class Link extends SvelteComponentTyped<LinkProps> { }
  type NavigateToOps (line 108) | interface NavigateToOps {
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".eslintignore",
    "chars": 47,
    "preview": "e2e/public/assets\ne2e/components/Import.svelte\n"
  },
  {
    "path": ".eslintrc",
    "chars": 1184,
    "preview": "{\n  \"env\": {\n    \"es6\": true,\n    \"browser\": true\n  },\n  \"extends\": \"airbnb-base\",\n  \"plugins\": [\n    \"svelte3\"\n  ],\n  \""
  },
  {
    "path": ".gitattributes",
    "chars": 37,
    "preview": "package-lock.json -diff\ndist/* -diff\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 900,
    "preview": "name: Release\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  Release:\n    runs-on: ubuntu-latest\n    steps:\n      - "
  },
  {
    "path": ".github/workflows/testing.yml",
    "chars": 262,
    "preview": "name: build\n\non:\n  pull_request:\n    branches:\n    - master\n\njobs:\n  build-test:\n    runs-on: ubuntu-latest\n    steps:\n "
  },
  {
    "path": ".gitignore",
    "chars": 88,
    "preview": ".DS_Store\n*.todo\ndist\nbuild\ncache.json\nnode_modules\ne2e/public/assets\npackage-lock.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 10508,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github."
  },
  {
    "path": "Makefile",
    "chars": 761,
    "preview": "ifneq ($(DEBUG),)\n\tE2E_FLAGS=--debug-on-fail\nendif\n\nhelp: Makefile\n\t@awk -F':.*?##' '/^[a-z0-9\\\\%!:-]+:.*##/{gsub(\"%\",\"*"
  },
  {
    "path": "README.md",
    "chars": 12399,
    "preview": "<div align=\"center\">\n\n![yrv](Japan_road_sign_201-D.svg)\n\n![Build status](https://github.com/pateketrueke/yrv/workflows/b"
  },
  {
    "path": "components.js",
    "chars": 232,
    "preview": "export { default as Router } from './src/lib/Router.svelte';\nexport { default as Route } from './src/lib/Route.svelte';\n"
  },
  {
    "path": "debug.js",
    "chars": 38,
    "preview": "export * from './build/dev/index.js';\n"
  },
  {
    "path": "e2e/app/import.esm.js",
    "chars": 107,
    "preview": "import App from '../components/Import.svelte';\n\nnew App({ target: document.body }); // eslint-disable-line\n"
  },
  {
    "path": "e2e/app/routers.js",
    "chars": 119,
    "preview": "import App from '../components/nested-routers/App.svelte';\n\nnew App({ target: document.body }); // eslint-disable-line\n"
  },
  {
    "path": "e2e/app/testing.js",
    "chars": 273,
    "preview": "import App from '../components/Main.svelte';\n\n/* global USE_HASH_CHANGE */\nlet hashchange;\nif (typeof USE_HASH_CHANGE !="
  },
  {
    "path": "e2e/cases/main.test.js",
    "chars": 15965,
    "preview": "import { Selector } from 'testcafe';\nimport { url, href } from '../helpers';\n\n/* global fixture, test */\n\nfixture('yrv ("
  },
  {
    "path": "e2e/cases/routers.test.js",
    "chars": 1820,
    "preview": "import { Selector } from 'testcafe';\nimport { url } from '../helpers';\n\n/* global fixture, test */\n\nfixture('yrv (nested"
  },
  {
    "path": "e2e/components/Example.svelte",
    "chars": 106,
    "preview": "<p data-test=\"import\">Components can be lazy-loaded through <code>`() => import('...')`</code> calls.</p>\n"
  },
  {
    "path": "e2e/components/Import.svelte",
    "chars": 312,
    "preview": "<script>\n  import { Router, Route } from 'yrv';\n\n  function delay(promise) {\n    return new Promise(ok => setTimeout(() "
  },
  {
    "path": "e2e/components/Main.svelte",
    "chars": 4702,
    "preview": "<script>\n  import {\n    Router, Route, Link, router,\n  } from 'yrv';\n\n  import Testing from './Testing.svelte';\n\n  expor"
  },
  {
    "path": "e2e/components/Testing.svelte",
    "chars": 1707,
    "preview": "<script>\n  import {\n    Router, Route, Link, navigateTo,\n  } from 'yrv';\n\n  export let router = null;\n\n  let newKey = ''"
  },
  {
    "path": "e2e/components/nested-routers/App.svelte",
    "chars": 698,
    "preview": "<script>\n  import { Router, Route, Link } from 'yrv';\n\n  import Home from './Home.svelte';\n  import Players from './Play"
  },
  {
    "path": "e2e/components/nested-routers/Home.svelte",
    "chars": 22,
    "preview": "<h1>This is Home</h1>\n"
  },
  {
    "path": "e2e/components/nested-routers/List.svelte",
    "chars": 22,
    "preview": "<h2>This is List</h2>\n"
  },
  {
    "path": "e2e/components/nested-routers/Login.svelte",
    "chars": 23,
    "preview": "<h1>This is Login</h1>\n"
  },
  {
    "path": "e2e/components/nested-routers/NewTeam.svelte",
    "chars": 26,
    "preview": "<h2>This is New Team</h2>\n"
  },
  {
    "path": "e2e/components/nested-routers/NotFound.svelte",
    "chars": 26,
    "preview": "<h1>This is NotFound</h1>\n"
  },
  {
    "path": "e2e/components/nested-routers/Players.svelte",
    "chars": 373,
    "preview": "<script>\n  import { Router, Route, Link } from 'yrv';\n\n  import List from './List.svelte';\n  import NewTeam from './NewT"
  },
  {
    "path": "e2e/helpers.js",
    "chars": 302,
    "preview": "const prefix = process.env.BASE_URL || 'http://localhost:8080';\n\nexport function url(x, y) {\n  if (!y && process.env.HAS"
  },
  {
    "path": "package.json",
    "chars": 2569,
    "preview": "{\n  \"name\": \"yrv\",\n  \"version\": \"0.0.57\",\n  \"description\": \"Your routing!\",\n  \"types\": \"types/index.d.ts\",\n  \"_main\": \"b"
  },
  {
    "path": "src/index.js",
    "chars": 216,
    "preview": "export { default as Router } from './lib/Router.svelte';\nexport { default as Route } from './lib/Route.svelte';\nexport {"
  },
  {
    "path": "src/lib/Link.svelte",
    "chars": 3098,
    "preview": "<script>\n  import { createEventDispatcher } from 'svelte';\n\n  import {\n    ROOT_URL, fixedLocation, navigateTo, cleanPat"
  },
  {
    "path": "src/lib/Route.svelte",
    "chars": 3943,
    "preview": "<script context=\"module\">\n  import { writable } from 'svelte/store';\n  import { routeInfo } from './router';\n  import {\n"
  },
  {
    "path": "src/lib/Router.svelte",
    "chars": 2558,
    "preview": "<script context=\"module\">\n  import { writable } from 'svelte/store';\n  import { CTX_ROUTER, router } from './utils';\n  i"
  },
  {
    "path": "src/lib/router.js",
    "chars": 4076,
    "preview": "import { writable } from 'svelte/store';\nimport { Router, parse } from '../vendor';\n\nimport {\n  ROOT_URL, navigateTo, cl"
  },
  {
    "path": "src/lib/utils.js",
    "chars": 3586,
    "preview": "import { writable } from 'svelte/store';\nimport { Router, stringify } from '../vendor';\n\nconst cache = {};\nconst baseTag"
  },
  {
    "path": "src/test/_layout.pug",
    "chars": 526,
    "preview": "doctype html\nhtml(lang='en')\n  head\n    meta(charset='UTF-8')\n    meta(name='viewport', content='width=device-width, ini"
  },
  {
    "path": "src/test/folder.pug",
    "chars": 132,
    "preview": "extends _layout\n\nblock head\n  base(href='/folder/')\n  title Your routing! (base-href)\n\nblock body\n  script(src='../app/t"
  },
  {
    "path": "src/test/import.pug",
    "chars": 155,
    "preview": "extends _layout\n\nblock head\n  base(href='/import/')\n  title Your routing! (dynamic imports)\n\nblock body\n  script(src='.."
  },
  {
    "path": "src/test/index.pug",
    "chars": 94,
    "preview": "extends _layout\n\nblock head\n  title Your routing!\n\nblock body\n  script(src='/app/testing.js')\n"
  },
  {
    "path": "src/test/routers.pug",
    "chars": 138,
    "preview": "extends _layout\n\nblock head\n  base(href='/routers/')\n  title Your routing! (nested routers)\n\nblock body\n  script(src='.."
  },
  {
    "path": "src/vendor.js",
    "chars": 109,
    "preview": "export { parse, stringify } from 'query-string';\nexport { default as Router } from 'abstract-nested-router';\n"
  },
  {
    "path": "types/index.d.ts",
    "chars": 3873,
    "preview": "import { SvelteComponentTyped, SvelteComponent } from \"svelte\";\nimport { Writable } from 'svelte/store';\n\ninterface Rout"
  }
]

About this extraction

This page contains the full source code of the pateketrueke/yrv GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (76.3 KB), approximately 23.7k tokens, and a symbol index with 28 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!