[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches: [main, dev]\n  pull_request:\n    branches: [dev]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [22.x, 24.x]\n        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/\n    steps:\n      - uses: actions/checkout@v4\n      - name: Local Unit Test ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm i\n      - run: npm test\n      - name: Generate Coverage\n        if: ${{ success() && github.event_name != 'pull_request' && matrix.node-version == '22.x' }}\n        run: npm run cov\n      - name: Code Quality\n        if: ${{ success() && github.event_name != 'pull_request' && matrix.node-version == '22.x' }}\n        uses: qltysh/qlty-action/coverage@v1\n        with:\n          token: ${{ secrets.QLTY_COVERAGE_TOKEN }}\n          files: coverage/lcov.info\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# editor files\n.idea\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\n# mac useless file\n.DS_Store\n\n# Generated files\nindex.js\nindex.umd.js\nindex.standalone.js\nindex.standalone.umd.js\ndemos/components/*.js\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "import config from '@riotjs/prettier-config'\nexport default config\n"
  },
  {
    "path": ".qlty/qlty.toml",
    "content": "exclude_patterns = [\"test/**\", \"demos/**\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) Gianluca Guarini\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Riot Router\n\n[![Route logo](https://raw.githubusercontent.com/riot/branding/main/route/route-horizontal.svg)](https://github.com/riot/route/)\n\n[![Build Status][ci-image]][ci-url] [![Code Quality][qlty-image]][qlty-url] [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![MIT License][license-image]][license-url] [![Coverage Status][coverage-image]][coverage-url]\n\n> Simple isomorphic router\n\nThe Riot.js Router is the minimal router implementation with such technologies:\n\n- compatible with the DOM pushState and history API\n- isomorphic functional API\n- [erre.js streams](https://github.com/GianlucaGuarini/erre) and javascript async generators\n- [rawth.js](https://github.com/GianlucaGuarini/rawth) urls parsing\n\nIt doesn't need Riot.js to work and can be used as standalone module.\n\n**For Riot.js 3 and the older route version please check the [v3 branch](https://github.com/riot/route/tree/v3)**\n\n## Table of Contents\n\n- [Install](#install)\n- [Documentation](#documentation)\n- [Demos](https://github.com/riot/examples)\n\n## Install\n\nWe have 2 editions:\n\n| edition                   | file                      |\n| :------------------------ | :------------------------ |\n| **ESM Module**            | `index.js`                |\n| **UMD Version**           | `index.umd.js`            |\n| **Standalone ESM Module** | `index.standalone.js`     |\n| **Standalone UMD Module** | `index.standalone.umd.js` |\n\n### Script injection\n\n```html\n<script src=\"https://unpkg.com/@riotjs/route@x.x.x/index.umd.js\"></script>\n```\n\n_Note_: change the part `x.x.x` to the version numbers what you want to use: ex. `4.5.0` or `4.7.0`.\n\n### ESM module\n\n```js\nimport { route } from 'https://unpkg.com/@riotjs/route/index.js'\n```\n\n### npm\n\n```bash\nnpm i -S @riotjs/route\n```\n\n### Download by yourself\n\n- [Standalone](https://unpkg.com/@riotjs/route/route.js)\n- [ESM](https://unpkg.com/@riotjs/route/route.esm.js)\n\n## Documentation\n\n### With Riot.js\n\nYou can import the `<router>` and `<route>` components in your application and use them as it follows:\n\n```html\n<app>\n  <router>\n    <!-- These links will trigger automatically HTML5 history events -->\n    <nav>\n      <a href=\"/home\">Home</a>\n      <a href=\"/about\">About</a>\n      <a href=\"/team/gianluca\">Gianluca</a>\n    </nav>\n\n    <!-- Your application routes will be rendered here -->\n    <route path=\"/home\"> Home page </route>\n    <route path=\"/about\"> About </route>\n    <route path=\"/team/:person\"> Hello dear { route.params.person } </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '@riotjs/route'\n\n    export default {\n      components { Router, Route }\n    }\n  </script>\n</app>\n```\n\nYou can also use the `riot.register` method to register them globally\n\n```js\nimport { Route, Router } from '@riotjs/route'\nimport { register } from 'riot'\n\n// now the Router and Route components are globally available\nregister('router', Router)\nregister('route', Route)\n```\n\n#### Router\n\nThe `<router>` component should wrap your application markup and will detect automatically all the clicks on links that should trigger a route event.\n\n```html\n<router>\n  <!-- this link will trigger a riot router event -->\n  <a href=\"/path/somewhere\">Link</a>\n</router>\n<!-- this link will work as normal link without triggering router events -->\n<a href=\"/path/to/a/page\">Link</a>\n```\n\nYou can also specify the base of your application via component attributes:\n\n```html\n<router base=\"/internal/path\">\n  <!-- this link is outside the base so it will work as a normal link -->\n  <a href=\"/somewhere\">Link<a>\n</router>\n```\n\nThe router component has also an `onStarted` callback that will be called asynchronously after the first route event will be called\n\n```html\n<router onStarted=\"{onRouterStarted}\"></router>\n```\n\n#### Route\n\nThe `<route>` component provides the `route` property to its children (it's simply a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object) allowing you to detect the url params and queries.\n\n```html\n<route path=\"/:some/:route/:param\"> {JSON.stringify(route.params)} </route>\n\n<route path=\"/search(.*)\">\n  <!-- Assuming the URL is \"/search?q=awesome\" -->\n\n  {route.searchParams.get('q')}\n</route>\n```\n\nEach `<route>` component has its own lifecycle attributes in order to let you know when it gets mounted or unmounted.\n\n```riot\n<app>\n  <router>\n    <route path=\"/home\"\n      on-before-mount={onBeforeHomeMount}\n      on-mounted={onHomeMounted}\n      on-before-update={onBeforeHomeUpdate}\n      on-updated={onHomeUpdated}\n      on-before-unmount={onBeforeHomeUnmount}\n      on-unmounted={onHomeUnmounted}\n    />\n  </router>\n</app>\n```\n\n### Standalone\n\nThis module was not only designed to be used with Riot.js but also as standalone module.\nWithout importing the Riot.js components in your application you can use the core methods exported to build and customize your own router compatible with any kind of frontend setup.\n\nDepending on your project setup you might import it as follows:\n\n```js\n// in a Riot.js application\nimport { route } from '@riotjs/route'\n\n// in a standalone context\nimport { route } from '@riotjs/route/standalone'\n```\n\n#### Fundamentals\n\nThis module works on node and on any modern browser, it exports the `router` and `router` property exposed by [rawth](https://github.com/GianlucaGuarini/rawth)\n\n```js\nimport { route, router, setBase } from '@riotjs/route'\n\n// required to set base first\nsetBase('/')\n\n// create a route stream\nconst aboutStream = route('/about')\n\naboutStream.on.value((url) => {\n  console.log(url) // URL object\n})\n\naboutStream.on.value(() => {\n  console.log('just log that the about route was triggered')\n})\n\n// triggered on each route event\nrouter.on.value((path) => {\n  // path is always a string in this function\n  console.log(path)\n})\n\n// trigger a route change manually\nrouter.push('/about')\n\n// end the stream\naboutStream.end()\n```\n\n#### Base path\n\nBefore using the router in your browser you will need to set your application base path.\nThis setting can be configured simply via `setBase` method:\n\n```js\nimport { setBase } from '@riotjs/route'\n\n// in case you want to use the HTML5 history navigation\nsetBase(`/`)\n\n// in case you use the hash navigation\nsetBase(`#`)\n```\n\nSetting the base path of your application route is mandatory and is the first you probably are going to do before creating your route listeners.\n\n#### DOM binding\n\nThe example above is not really practical in case you are working in a browser environment. In that case you might want to bind your router to the DOM listening all the click events that might trigger a route change event.\nWindow history `popstate` events should be also connected to the router.\nWith the `initDomListeners` method you can automatically achieve all the features above:\n\n```js\nimport { initDomListeners } from '@riotjs/route'\n\nconst unsubscribe = initDomListeners()\n// the router is connected to the page DOM\n\n// ...tear down and disconnect the router from the DOM\nunsubscribe()\n```\n\nThe `initDomListeners` will intercept any link click on your application. However it can also receive a HTMLElement or a list of HTMLElements as argument to scope the click listener only to a specific DOM region of your application\n\n```js\nimport { initDomListeners } from '@riotjs/route'\n\ninitDomListeners(document.querySelector('.main-navigation'))\n```\n\n[ci-image]: https://img.shields.io/github/actions/workflow/status/riot/route/test.yml?style=flat-square\n[ci-url]: https://github.com/riot/route/actions\n[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square\n[license-url]: LICENSE.txt\n[npm-version-image]: http://img.shields.io/npm/v/@riotjs/route.svg?style=flat-square\n[npm-downloads-image]: http://img.shields.io/npm/dm/@riotjs/route.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/@riotjs/route\n[coverage-image]: https://qlty.sh/gh/riot/projects/route/coverage.svg\n[coverage-url]: https://qlty.sh/gh/riot/projects/route\n[qlty-image]: https://qlty.sh/gh/riot/projects/route/maintainability.svg\n[qlty-url]: https://qlty.sh/gh/riot/projects/route\n"
  },
  {
    "path": "demos/components/app.riot",
    "content": "<app>\n  <router>\n    <nav>\n\n      <a href=\"/hello\">Hello</a>\n      <a href=\"/user\">User</a>\n      <a href=\"/user/gianluca\">Username</a>\n      <a href=\"/user/gianluca/#anchor\">Username with anchor</a>\n      <a href=\"/goodbye\">goodbye</a>\n    </nav>\n    <route path=\"(.*)\">\n      Every route :)\n    </route>\n    <route path=\"/hello\">hello</route>\n    <route path=\"/user\">user</route>\n    <route path=\"/user/:username/([?#].*)?\">\n      <user username={route.params.username}></user>\n    </route>\n    <route path=\"/goodbye\">goodbye</route>\n  </router>\n\n  <style>\n    nav {\n      display: flex;\n      gap: 0.6rem;\n      align-items: center;\n    }\n  </style>\n</app>\n"
  },
  {
    "path": "demos/components/user.riot",
    "content": "<user>\n  User {JSON.stringify(props)}\n\n  <p id=\"anchor\">i am an anchor</p>\n\n  <style>\n    p {\n      margin: 100vh 0;\n    }\n  </style>\n</user>\n"
  },
  {
    "path": "demos/riot-history.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <title>Riot.js history</title>\n    <script src=\"https://unpkg.com/riot/riot.js\"></script>\n    <script src=\"../index.umd.js\"></script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\">\n      import App from './components/app.js'\n      import User from './components/user.js'\n\n      riot.register('route', route.Route)\n      riot.register('router', route.Router)\n      riot.register('user', User)\n      riot.component(App)(app)\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/standalone-hash.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <title>Standalone Hash Demo</title>\n    <script src=\"../index.standalone.umd.js\"></script>\n  </head>\n  <body>\n    <nav>\n      <a href=\"#/hello\">Hello</a>\n      <a href=\"#/user\">User</a>\n      <a href=\"#/user/gianluca\">Username</a>\n      <a href=\"#/goodbye\">goodbye</a>\n    </nav>\n    <div id=\"root\"></div>\n    <script type=\"module\">\n      const { initDomListeners, setBase, router } = route\n\n      setBase('#')\n\n      const onRoute = (url) =>\n        (root.innerHTML = `${url} and params=${JSON.stringify(url.params)}`)\n\n      route.route('/hello').on.value(onRoute)\n      route.route('/user').on.value(onRoute)\n      route.route('/user/:username').on.value(onRoute)\n      route.route('/goodbye').on.value(onRoute)\n\n      router.push(window.location.hash.replace('#', ''))\n\n      initDomListeners()\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/standalone-history.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <title>Standalone History Demo</title>\n    <script src=\"../index.standalone.umd.js\"></script>\n  </head>\n  <body>\n    <nav>\n      <a href=\"/hello\">Hello</a>\n      <a href=\"/user\">User</a>\n      <a href=\"/user/gianluca\">Username</a>\n      <a href=\"/goodbye\">goodbye</a>\n    </nav>\n    <div id=\"root\"></div>\n    <script type=\"module\">\n      const { initDomListeners, setBase, router } = route\n\n      setBase('/')\n\n      const onRoute = (url) =>\n        (root.innerHTML = `${url} and params=${JSON.stringify(url.params)}`)\n\n      route.route('/hello').on.value(onRoute)\n      route.route('/user').on.value(onRoute)\n      route.route('/user/:username').on.value(onRoute)\n      route.route('/goodbye').on.value(onRoute)\n\n      initDomListeners()\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import { defineConfig } from 'eslint/config'\nimport riotEslintConfig from 'eslint-config-riot'\n\nexport default defineConfig([\n  { extends: [riotEslintConfig] },\n  {\n    rules: {\n      'fp/no-rest-parameters': 0,\n      'fp/no-mutating-methods': 0,\n    },\n  },\n])\n"
  },
  {
    "path": "index.d.ts",
    "content": "import { RiotComponentWrapper, RiotComponent } from 'riot'\nimport { URLWithParams } from 'rawth'\n\nexport * from 'rawth'\n\nexport declare const Route: RiotComponentWrapper<\n  RiotComponent<{\n    path: string\n    'on-before-mount'?: (path: URLWithParams) => void\n    'on-mounted'?: (path: URLWithParams) => void\n    'on-before-unmount'?: (path: URLWithParams) => void\n    'on-unmounted'?: (path: URLWithParams) => void\n  }>\n>\n\nexport declare const Router: RiotComponentWrapper<\n  RiotComponent<{\n    base?: string\n    'initial-route'?: string\n    'on-started'?: (route: string) => void\n  }>\n>\n\nexport declare function getCurrentRoute(): string\nexport declare function initDomListeners(): void\nexport declare function setBase(base: string): void\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@riotjs/route\",\n  \"version\": \"10.0.0\",\n  \"description\": \"Riot.js isomorphic router\",\n  \"type\": \"module\",\n  \"main\": \"index.umd.js\",\n  \"jsnext:main\": \"index.js\",\n  \"module\": \"index.js\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./index.d.ts\",\n      \"import\": \"./index.js\",\n      \"require\": \"./index.umd.js\",\n      \"browser\": \"./index.umd.js\"\n    },\n    \"./standalone\": {\n      \"types\": \"./index.d.ts\",\n      \"import\": \"./index.standalone.js\",\n      \"require\": \"./index.standalone.umd.js\",\n      \"browser\": \"./index.standalone.umd.js\"\n    }\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \"npm run build && npm test\",\n    \"lint\": \"eslint src test rollup.config.js && prettier -c .\",\n    \"build\": \"rollup -c && npm run build-demo\",\n    \"build-demo\": \"riot demos/components -o demos/components\",\n    \"demo\": \"npm run build && serve\",\n    \"cov\": \"c8 report --reporter=lcov\",\n    \"cov-html\": \"c8 report --reporter=html\",\n    \"test\": \"npm run lint && c8 mocha -r test/setup.js test/*.spec.js\"\n  },\n  \"files\": [\n    \"index.d.ts\",\n    \"index.js\",\n    \"index.umd.js\",\n    \"index.standalone.js\",\n    \"index.standalone.umd.js\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/riot/route.git\"\n  },\n  \"keywords\": [\n    \"riot\",\n    \"Riot.js\",\n    \"router\",\n    \"riot-route\",\n    \"route\"\n  ],\n  \"author\": \"Gianluca Guarini <gianluca.guarini@gmail.com> (https://gianlucaguarini.com)\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/riot/route/issues\"\n  },\n  \"homepage\": \"https://github.com/riot/route#readme\",\n  \"devDependencies\": {\n    \"@riotjs/cli\": \"10.0.0\",\n    \"@riotjs/compiler\": \"10.0.0\",\n    \"@riotjs/prettier-config\": \"^1.1.0\",\n    \"@riotjs/register\": \"10.0.0\",\n    \"@rollup/plugin-commonjs\": \"^28.0.6\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.1\",\n    \"@rollup/plugin-virtual\": \"^3.0.2\",\n    \"c8\": \"^10.1.3\",\n    \"chai\": \"^6.0.1\",\n    \"eslint\": \"^9.33.0\",\n    \"eslint-config-riot\": \"^5.0.2\",\n    \"globals\": \"^16.3.0\",\n    \"jsdom\": \"26.1.0\",\n    \"jsdom-global\": \"3.0.2\",\n    \"mocha\": \"^11.7.1\",\n    \"prettier\": \"^3.6.2\",\n    \"riot\": \"^10.0.0\",\n    \"rollup\": \"^4.47.1\",\n    \"rollup-plugin-riot\": \"10.0.0\",\n    \"serve\": \"^14.2.4\",\n    \"sinon\": \"^21.0.0\",\n    \"sinon-chai\": \"^4.0.1\"\n  },\n  \"peerDependency\": {\n    \"riot\": \"^6.0.0 || ^7.0.0 || ^9.0.0 || ^10.0.0\"\n  },\n  \"dependencies\": {\n    \"@riotjs/util\": \"^10.0.0\",\n    \"bianco.attr\": \"^1.1.1\",\n    \"bianco.events\": \"^1.1.1\",\n    \"bianco.query\": \"^1.1.4\",\n    \"cumpa\": \"^2.0.1\",\n    \"rawth\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import commonjs from '@rollup/plugin-commonjs'\nimport resolve from '@rollup/plugin-node-resolve'\nimport riot from 'rollup-plugin-riot'\nimport { resolve as nodeResolve } from 'node:path'\nimport virtual from '@rollup/plugin-virtual'\n\nconst standaloneExternal = [\n  nodeResolve('./src/components/route-hoc.riot'),\n  nodeResolve('./src/components/router-hoc.riot'),\n]\n\nconst defaultOptions = {\n  input: 'src/index.js',\n  plugins: [resolve(), commonjs(), riot()],\n  external: ['riot'],\n}\n\nconst standalonePlugins = [\n  virtual(\n    standaloneExternal.reduce(\n      (acc, path) => ({ ...acc, [path]: 'export default {}' }),\n      {},\n    ),\n  ),\n  ...defaultOptions.plugins,\n]\n\nexport default [\n  {\n    ...defaultOptions,\n    output: {\n      format: 'esm',\n      file: 'index.js',\n    },\n  },\n  {\n    ...defaultOptions,\n    output: {\n      format: 'umd',\n      name: 'route',\n      file: 'index.umd.js',\n    },\n  },\n  {\n    ...defaultOptions,\n    plugins: standalonePlugins,\n    output: {\n      format: 'esm',\n      file: 'index.standalone.js',\n    },\n  },\n  {\n    ...defaultOptions,\n    plugins: standalonePlugins,\n    output: {\n      format: 'umd',\n      name: 'route',\n      file: 'index.standalone.umd.js',\n    },\n  },\n]\n"
  },
  {
    "path": "src/components/route-hoc.js",
    "content": "import { PATH_ATTRIBUTE } from '../constants.js'\nimport {\n  route,\n  toRegexp,\n  match,\n  router,\n  createURLStreamPipe,\n} from '../index.js'\nimport $ from 'bianco.query'\nimport getCurrentRoute from '../get-current-route.js'\nimport { get as getAttr } from 'bianco.attr'\nimport {\n  createDefaultSlot,\n  getAttribute,\n  isValidQuerySelectorString,\n} from '../util.js'\nimport compose from 'cumpa'\n\nconst getInitialRouteValue = (pathToRegexp, path, options) => {\n  const route = compose(\n    ...createURLStreamPipe(pathToRegexp, options).reverse(),\n  )(path)\n\n  return route.params ? route : null\n}\n\nconst clearDOMBetweenNodes = (first, last, includeBoundaries) => {\n  const clear = (node) => {\n    if (!node || (node === last && !includeBoundaries)) return\n    const { nextSibling } = node\n    node.remove()\n    clear(nextSibling)\n  }\n\n  clear(includeBoundaries ? first : first.nextSibling)\n}\n\nexport const routeHoc = ({ slots, attributes }) => {\n  const placeholders = {\n    before: document.createTextNode(''),\n    after: document.createTextNode(''),\n  }\n\n  return {\n    mount(el, context) {\n      // create the component state\n      const currentRoute = getCurrentRoute()\n      const path =\n        getAttribute(attributes, PATH_ATTRIBUTE, context)?.evaluate(context) ||\n        getAttr(el, PATH_ATTRIBUTE)\n      const pathToRegexp = toRegexp(path, [])\n      const state = {\n        pathToRegexp,\n        route:\n          currentRoute && match(currentRoute, pathToRegexp)\n            ? getInitialRouteValue(pathToRegexp, currentRoute, {})\n            : null,\n      }\n      this.el = el\n      this.slot = createDefaultSlot([\n        {\n          isBoolean: false,\n          name: 'route',\n          evaluate: () => this.state.route,\n        },\n      ])\n      this.context = context\n      this.state = state\n      // set the route listeners\n      this.boundOnBeforeRoute = this.onBeforeRoute.bind(this)\n      this.boundOnRoute = this.onRoute.bind(this)\n      router.on.value(this.boundOnBeforeRoute)\n      this.stream = route(path).on.value(this.boundOnRoute)\n      // update the DOM\n      el.replaceWith(placeholders.before)\n      placeholders.before.parentNode.insertBefore(\n        placeholders.after,\n        placeholders.before.nextSibling,\n      )\n      if (state.route) this.mountSlot()\n    },\n    update(context) {\n      this.context = context\n      if (this.state.route) this.slot.update({}, context)\n    },\n    mountSlot() {\n      const { route } = this.state\n      // insert the route root element after the before placeholder\n      placeholders.before.parentNode.insertBefore(\n        this.el,\n        placeholders.before.nextSibling,\n      )\n      this.callLifecycleProperty('onBeforeMount', route)\n      this.slot.mount(\n        this.el,\n        {\n          slots,\n        },\n        this.context,\n      )\n      this.callLifecycleProperty('onMounted', route)\n    },\n    clearDOM(includeBoundaries) {\n      // remove all the DOM nodes between the placeholders\n      clearDOMBetweenNodes(\n        placeholders.before,\n        placeholders.after,\n        includeBoundaries,\n      )\n    },\n    unmount() {\n      router.off.value(this.boundOnBeforeRoute)\n      this.slot.unmount({}, this.context, true)\n      this.clearDOM(true)\n      this.stream.end()\n    },\n    onBeforeRoute(path) {\n      const { route } = this.state\n      // this component was not mounted or the current path matches\n      // we don't need to unmount this component\n      if (!route || match(path, this.state.pathToRegexp)) return\n\n      this.callLifecycleProperty('onBeforeUnmount', route)\n      this.slot.unmount({}, this.context, true)\n      this.clearDOM(false)\n      this.state.route = null\n      this.callLifecycleProperty('onUnmounted', route)\n    },\n    onRoute(route) {\n      const prevRoute = this.state.route\n      this.state.route = route\n\n      // if this route component was already mounted we need to update it\n      if (prevRoute) {\n        this.callLifecycleProperty('onBeforeUpdate', route)\n        this.slot.update({}, this.context)\n        this.callLifecycleProperty('onUpdated', route)\n      }\n      // this route component was never mounted, so we need to create its DOM\n      else this.mountSlot()\n\n      // emulate the default browser anchor links behaviour\n      if (route.hash && isValidQuerySelectorString(route.hash))\n        $(route.hash)?.[0].scrollIntoView()\n    },\n    callLifecycleProperty(method, ...params) {\n      const attr = getAttribute(attributes, method, this.context)\n\n      if (attr) attr.evaluate(this.context)(...params)\n    },\n  }\n}\n"
  },
  {
    "path": "src/components/route-hoc.riot",
    "content": "<route-hoc>\n  <script>\n    import { pure } from 'riot'\n    import { routeHoc } from './route-hoc.js'\n\n    export default pure(routeHoc)\n  </script>\n</route-hoc>\n"
  },
  {
    "path": "src/components/router-hoc.js",
    "content": "import { router } from '../index.js'\nimport { defer, cancelDefer, getAttribute, createDefaultSlot } from '../util.js'\nimport getCurrentRoute from '../get-current-route.js'\nimport setBase from '../set-base.js'\nimport { panic } from '@riotjs/util/misc'\nimport initDomListeners from '../dom.js'\n\nconst BASE_ATTRIBUTE_NAME = 'base'\nconst INITIAL_ROUTE = 'initialRoute'\nconst ON_STARTED_ATTRIBUTE_NAME = 'onStarted'\n\nexport const routerHoc = ({ slots, attributes, props }) => {\n  if (routerHoc.wasInitialized)\n    panic('Multiple <router> components are not supported')\n\n  return {\n    slot: null,\n    el: null,\n    teardown: null,\n    mount(el, context) {\n      const initialRouteAttr = getAttribute(attributes, INITIAL_ROUTE, context)\n      const initialRoute = initialRouteAttr\n        ? initialRouteAttr.evaluate(context)\n        : null\n      const currentRoute = getCurrentRoute()\n      const onFirstRoute = () => {\n        this.createSlot(context)\n        router.off.value(onFirstRoute)\n      }\n      routerHoc.wasInitialized = true\n\n      this.el = el\n      this.teardown = initDomListeners(this.root)\n\n      this.setBase(context)\n\n      // mount the slots only if the current route was defined\n      if (currentRoute && !initialRoute) {\n        this.createSlot(context)\n      } else {\n        router.on.value(onFirstRoute)\n        router.push(initialRoute || window.location.href)\n      }\n    },\n    createSlot(context) {\n      if (!slots || !slots.length) return\n      const onStartedAttr = getAttribute(\n        attributes,\n        ON_STARTED_ATTRIBUTE_NAME,\n        context,\n      )\n\n      this.slot = createDefaultSlot()\n\n      this.slot.mount(\n        this.el,\n        {\n          slots,\n        },\n        context,\n      )\n\n      if (onStartedAttr) {\n        onStartedAttr.evaluate(context)(getCurrentRoute())\n      }\n    },\n    update(context) {\n      this.setBase(context)\n\n      // defer the updates to avoid internal recursive update calls\n      // see https://github.com/riot/route/issues/148\n      if (this.slot) {\n        cancelDefer(this.deferred)\n\n        this.deferred = defer(() => {\n          this.slot.update({}, context)\n        })\n      }\n    },\n    unmount(...args) {\n      this.teardown()\n      routerHoc.wasInitialized = false\n\n      if (this.slot) {\n        this.slot.unmount(...args)\n      }\n    },\n    getBase(context) {\n      const baseAttr = getAttribute(attributes, BASE_ATTRIBUTE_NAME, context)\n\n      return baseAttr\n        ? baseAttr.evaluate(context)\n        : this.el.getAttribute(BASE_ATTRIBUTE_NAME) || '/'\n    },\n    setBase(context) {\n      setBase(props ? props.base : this.getBase(context))\n    },\n  }\n}\n\n// flag to avoid multiple router instances\nrouterHoc.wasInitialized = false\n"
  },
  {
    "path": "src/components/router-hoc.riot",
    "content": "<router-hoc>\n  <script>\n    import { pure } from 'riot'\n    import { routerHoc } from './router-hoc.js'\n    export default pure(routerHoc)\n  </script>\n</router-hoc>\n"
  },
  {
    "path": "src/constants.js",
    "content": "export const WINDOW_EVENTS = 'popstate'\nexport const CLICK_EVENT = 'click'\nexport const DOWNLOAD_LINK_ATTRIBUTE = 'download'\nexport const HREF_LINK_ATTRIBUTE = 'href'\nexport const TARGET_SELF_LINK_ATTRIBUTE = '_self'\nexport const LINK_TAG_NAME = 'A'\nexport const HASH = '#'\nexport const SLASH = '/'\nexport const PATH_ATTRIBUTE = 'path'\nexport const RE_ORIGIN = /^.+?\\/\\/+[^/]+/\n"
  },
  {
    "path": "src/dom.js",
    "content": "import {\n  CLICK_EVENT,\n  DOWNLOAD_LINK_ATTRIBUTE,\n  HREF_LINK_ATTRIBUTE,\n  LINK_TAG_NAME,\n  RE_ORIGIN,\n  TARGET_SELF_LINK_ATTRIBUTE,\n  WINDOW_EVENTS,\n} from './constants.js'\nimport { add, remove } from 'bianco.events'\nimport { defaults, router } from 'rawth'\nimport { getDocument, getHistory, getLocation, getWindow } from './util.js'\nimport { has } from 'bianco.attr'\n\nconst onWindowEvent = () =>\n  router.push(normalizePath(String(getLocation().href)))\nconst onRouterPush = (path) => {\n  const url = path.includes(defaults.base) ? path : defaults.base + path\n  const loc = getLocation()\n  const hist = getHistory()\n  const doc = getDocument()\n\n  // update the browser history only if it's necessary\n  if (hist && url !== loc.href) {\n    hist.pushState(null, doc.title, url)\n  }\n}\nconst getLinkElement = (node) =>\n  node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node\nconst isLinkNode = (node) => node.nodeName === LINK_TAG_NAME\nconst isCrossOriginLink = (path) =>\n  path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1\nconst isTargetSelfLink = (el) =>\n  el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE\nconst isEventForbidden = (event) =>\n  (event.which && event.which !== 1) || // not left click\n  event.metaKey ||\n  event.ctrlKey ||\n  event.shiftKey || // or meta keys\n  event.defaultPrevented // or default prevented\nconst isForbiddenLink = (el) =>\n  !el ||\n  !isLinkNode(el) || // not A tag\n  has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr\n  !has(el, HREF_LINK_ATTRIBUTE) || // has no href attr\n  isTargetSelfLink(el) ||\n  isCrossOriginLink(el.href)\nconst normalizePath = (path) => path.replace(defaults.base, '')\nconst isInBase = (path) => !defaults.base || path.includes(defaults.base)\n\n/**\n * Callback called anytime something will be clicked on the page\n * @param   {Event} event - click event\n * @returns {undefined} void method\n */\nconst onClick = (event) => {\n  if (isEventForbidden(event)) return\n\n  const el = getLinkElement(event.target)\n\n  if (isForbiddenLink(el) || !isInBase(el.href)) return\n\n  event.preventDefault()\n\n  router.push(normalizePath(el.href))\n}\n\n/**\n * Link the rawth router to the DOM events\n * @param { HTMLElement } container - DOM node where the links are located\n * @returns {Function} teardown function\n */\nexport default function initDomListeners(container) {\n  const win = getWindow()\n  const root = container || getDocument()\n\n  if (win) {\n    add(win, WINDOW_EVENTS, onWindowEvent)\n    add(root, CLICK_EVENT, onClick)\n  }\n\n  router.on.value(onRouterPush)\n\n  return () => {\n    if (win) {\n      remove(win, WINDOW_EVENTS, onWindowEvent)\n      remove(root, CLICK_EVENT, onClick)\n    }\n\n    router.off.value(onRouterPush)\n  }\n}\n"
  },
  {
    "path": "src/get-current-route.js",
    "content": "import { router } from 'rawth'\n\nconst getCurrentRoute = ((currentRoute) => {\n  // listen the route changes events to store the current route\n  router.on.value((r) => (currentRoute = r))\n\n  return () => {\n    return currentRoute\n  }\n})(null)\n\nexport default getCurrentRoute\n"
  },
  {
    "path": "src/set-base.js",
    "content": "import { HASH, SLASH } from './constants.js'\nimport { configure } from 'rawth'\nimport { getWindow } from './util.js'\n\nexport const normalizeInitialSlash = (str) =>\n  str[0] === SLASH ? str : `${SLASH}${str}`\nexport const removeTrailingSlash = (str) =>\n  str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str\n\nexport const normalizeBase = (base) => {\n  const win = getWindow()\n  const loc = win.location\n  const root = loc ? `${loc.protocol}//${loc.host}` : ''\n  const { pathname } = loc ? loc : {}\n\n  switch (true) {\n    // pure root url + pathname\n    case Boolean(base) === false:\n      return removeTrailingSlash(`${root}${pathname || ''}`)\n    // full path base\n    case /(www|http(s)?:)/.test(base):\n      return base\n    // hash navigation\n    case base[0] === HASH:\n      return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}`\n    // root url with trailing slash\n    case base === SLASH:\n      return removeTrailingSlash(root)\n    // custom pathname\n    default:\n      return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`)\n  }\n}\n\nexport default function setBase(base) {\n  configure({ base: normalizeBase(base) })\n}\n"
  },
  {
    "path": "src/util.js",
    "content": "import { dashToCamelCase } from '@riotjs/util/strings'\nimport { isNil } from '@riotjs/util/checks'\nimport { __ } from 'riot'\n\nexport const getGlobal = () => getWindow() || global\nexport const getWindow = () => (typeof window === 'undefined' ? null : window)\nexport const getDocument = () =>\n  typeof document === 'undefined' ? null : document\nexport const getHistory = () =>\n  typeof history === 'undefined' ? null : history\nexport const getLocation = () => {\n  const win = getWindow()\n  return win ? win.location : {}\n}\n\nexport const defer = (() => {\n  const globalScope = getGlobal()\n\n  return globalScope.requestAnimationFrame || globalScope.setTimeout\n})()\n\nexport const cancelDefer = (() => {\n  const globalScope = getGlobal()\n\n  return globalScope.cancelAnimationFrame || globalScope.clearTimeout\n})()\n\nexport const getAttribute = (attributes, name, context) => {\n  if (!attributes) return null\n\n  const normalizedAttributes = attributes.flatMap((attr) =>\n    isNil(attr.name)\n      ? // add support for spread attributes https://github.com/riot/route/issues/178\n        Object.entries(attr.evaluate(context)).map(([key, value]) => ({\n          // evaluate each value of the spread attribute and store it into the array\n          name: key,\n          // create a nested evaluate function pointing to the original value of the spread object\n          evaluate: () => value,\n        }))\n      : attr,\n  )\n\n  return normalizedAttributes.find((a) => dashToCamelCase(a.name) === name)\n}\n\nexport const createDefaultSlot = (attributes = []) => {\n  const { template, bindingTypes, expressionTypes } = __.DOMBindings\n\n  return template(null, [\n    {\n      type: bindingTypes.SLOT,\n      name: 'default',\n      attributes: attributes.map((attr) => ({\n        ...attr,\n        type: expressionTypes.ATTRIBUTE,\n      })),\n    },\n  ])\n}\n\n// True if the selector string is valid\nexport const isValidQuerySelectorString = (selector) =>\n  /^([a-zA-Z0-9-_*#.:[\\]\\s>+~()='\"]|\\\\.)+$/.test(selector)\n"
  },
  {
    "path": "test/components/computed-routes.riot",
    "content": "<computed-routes>\n  <router>\n    <route path={state.home}>\n      <p>{state.name}</p>\n    </route>\n  </router>\n  <script>\n    import { Router, Route } from '../../src/index.js'\n\n    export default {\n      components: {\n        Router,\n        Route,\n      },\n      state: {\n        home: '/home',\n        name: 'hello'\n      },\n    }\n  </script>\n</computed-routes>\n"
  },
  {
    "path": "test/components/history-router-app.riot",
    "content": "<history-router-app>\n  <h1>{state.message}</h1>\n  <router base={props.base} on-started={onStarted}>\n    <route path=\"/\">\n      <p>Hello</p>\n    </route>\n    <route path=\"/goodbye/:user\" on-mounted={() => this.update({\n      message: 'Title'\n    })}>\n      <user name={route.params.user}/>\n    </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '../../src/index.js'\n    import User from './user.riot'\n\n    export default {\n      components: {\n        Router,\n        Route,\n        User,\n      },\n      state: {\n        message: ''\n      },\n      onStarted(currentRoute) {\n        this.isRouterStarted = true\n        this.currentRoute = currentRoute\n      }\n    }\n  </script>\n</history-router-app>\n"
  },
  {
    "path": "test/components/nested-updates.riot",
    "content": "<nested-updates>\n  <router>\n    <user name={state.name} update-name={updateName}/>\n  </router>\n  <script>\n    import User from './user.riot'\n    import { Router, Route } from '../../src/index.js'\n\n    export default {\n      components: {\n        Router,\n        Route,\n        User\n      },\n      state: {\n        name: 'hello'\n      },\n      updateName() {\n        this.update({\n          name: 'goodbye'\n        })\n      }\n    }\n  </script>\n</nested-updates>\n"
  },
  {
    "path": "test/components/recursive-updates-bug-router.riot",
    "content": "<recursive-updates-bug-router>\n  <router base={props.base}>\n    <route path=\"/\">\n      <recursive-updates-bug148 message={state.message} callbacks={state.callbacks}>\n    </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '../../src/index.js'\n    import RecursiveUpdatesBug148 from './recursive-updates-bug148.riot'\n\n    export default {\n      components: {\n        Router,\n        Route,\n        RecursiveUpdatesBug148\n      },\n      state: {\n        callbacks: [],\n        message: '',\n      },\n      onBeforeMount() {\n        this.state.callbacks.push((message) => {\n          this.update({\n            message\n          })\n        })\n      }\n    }\n  </script>\n</recursive-updates-bug-router>\n"
  },
  {
    "path": "test/components/recursive-updates-bug148.riot",
    "content": "<recursive-updates-bug148>\n  <p>{props.message}</p>\n  <script>\n    export default {\n      onMounted(props) {\n        props.callbacks.forEach(fn => fn('hello'))\n      }\n    }\n  </script>\n</recursive-updates-bug148>"
  },
  {
    "path": "test/components/same-route-matches.riot",
    "content": "<same-route-matches>\n  <router>\n    <route path=\"(.*)\">\n      <p>{state.message}</p>\n    </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '../../src/index.js'\n\n    export default {\n      components: {\n        Router,\n        Route,\n      },\n      state: {\n        message: 'hello',\n      }\n    }\n  </script>\n</same-route-matches>\n"
  },
  {
    "path": "test/components/spred-props-router.riot",
    "content": "<spread-props-router>\n  <h1>{state.message}</h1>\n  <router {...props} on-started={onStarted}>\n    <route path=\"/\">\n      <p>Hello</p>\n    </route>\n    <route path=\"/goodbye/:user\" on-mounted={() => this.update({\n      message: 'Title'\n    })}>\n      <user name={route.params.user}/>\n    </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '../../src/index.js'\n    import User from './user.riot'\n\n    export default {\n      components: {\n        Router,\n        Route,\n        User,\n      },\n      state: {\n        message: ''\n      },\n      onStarted(currentRoute) {\n        this.isRouterStarted = true\n        this.currentRoute = currentRoute\n      }\n    }\n  </script>\n</spread-props-router>\n"
  },
  {
    "path": "test/components/static-base-path.riot",
    "content": "<static-base-path>\n  <router base=\"/app\">\n    <a href=\"/home\">\n      Home\n    </a>\n    <route path=\"(.*)\">\n        <p>{state.message}</p>\n    </route>\n  </router>\n\n  <script>\n    import { Router, Route } from '../../src/index.js'\n\n    export default {\n      components: {\n        Router,\n        Route,\n      },\n      state: {\n        message: 'hello',\n      }\n    }\n  </script>\n</static-base-path>\n"
  },
  {
    "path": "test/components/user.riot",
    "content": "<user>\n  <p>{props.name}</p>\n\n  <script>\n    export default {\n      onMounted() {\n        if (this.props.updateName) {\n          this.props.updateName()\n        }\n      }\n    }\n  </script>\n</user>"
  },
  {
    "path": "test/components.spec.js",
    "content": "import { base, sleep } from './util.js'\nimport HistoryRouterApp from './components/history-router-app.riot'\nimport SpreadPropsRouter from './components/spred-props-router.riot'\nimport NestedUpdates from './components/nested-updates.riot'\nimport RecursiveUpdatesBugRouter from './components/recursive-updates-bug-router.riot'\nimport StaticBasePath from './components/static-base-path.riot'\nimport SameRouteMatches from './components/same-route-matches.riot'\nimport ComputedRoutes from './components/computed-routes.riot'\nimport { component } from 'riot'\nimport { expect } from 'chai'\nimport { router, defaults } from '../src/index.js'\n\ndescribe('components', function () {\n  beforeEach(async function () {\n    router.push('/')\n  })\n\n  it('The router contents get properly rendered', async function () {\n    const el = document.createElement('div')\n\n    const comp = component(HistoryRouterApp)(el, {\n      base,\n    })\n\n    await sleep()\n\n    expect(comp.$('p')).to.be.ok\n    expect(comp.isRouterStarted).to.be.ok\n    expect(comp.currentRoute).to.be.ok\n\n    router.push('/goodbye/gianluca')\n\n    await sleep()\n\n    expect(comp.$('user p').innerHTML).to.be.equal('gianluca')\n    expect(comp.$('h1').innerHTML).to.be.equal('Title')\n\n    comp.unmount()\n  })\n\n  it('The Router component accepts spread props', async function () {\n    const el = document.createElement('div')\n\n    const comp = component(SpreadPropsRouter)(el, {\n      base,\n    })\n\n    await sleep()\n\n    expect(comp.$('p')).to.be.ok\n    expect(comp.isRouterStarted).to.be.ok\n    expect(comp.currentRoute).to.be.ok\n\n    router.push('/goodbye/gianluca')\n\n    await sleep()\n\n    expect(comp.$('user p').innerHTML).to.be.equal('gianluca')\n    expect(comp.$('h1').innerHTML).to.be.equal('Title')\n\n    comp.unmount()\n  })\n\n  it('The Route Context gets properly updated', async function () {\n    const el = document.createElement('div')\n\n    const comp = component(NestedUpdates)(el, {\n      base,\n    })\n\n    await sleep()\n\n    expect(comp.$('p')).to.be.ok\n\n    await sleep()\n\n    expect(comp.$('user p').innerHTML).to.be.equal('goodbye')\n\n    comp.unmount()\n  })\n\n  it('Recursive onMounted callbacks (bug 148) ', async function () {\n    const el = document.createElement('div')\n\n    const comp = component(RecursiveUpdatesBugRouter)(el, {\n      base,\n    })\n\n    await sleep()\n\n    expect(comp.$('p').innerHTML).to.be.equal('hello')\n\n    await sleep()\n\n    router.push('/')\n\n    await sleep()\n\n    expect(comp.$('p').innerHTML).to.be.equal('hello')\n\n    comp.unmount()\n  })\n\n  it('Static base path attributes are supported (bug 172) ', async function () {\n    const el = document.createElement('div')\n    const comp = component(StaticBasePath)(el)\n\n    expect(defaults.base).to.be.equal('https://riot.rocks/app')\n\n    comp.unmount()\n  })\n\n  it('Routes matched multiple times do not render twice (bug 173) ', async function () {\n    const el = document.createElement('div')\n    const comp = component(SameRouteMatches)(el)\n\n    expect(comp.$$('p')).to.have.length(1)\n\n    router.push('/foo')\n\n    await sleep()\n\n    expect(comp.$$('p')).to.have.length(1)\n\n    router.push('/')\n\n    await sleep()\n\n    expect(comp.$$('p')).to.have.length(1)\n\n    comp.unmount()\n  })\n\n  it('Computed routes get properly rendered', async function () {\n    const el = document.createElement('div')\n    const comp = component(ComputedRoutes)(el)\n\n    expect(comp.$('p')).to.be.not.ok\n\n    router.push('/home')\n\n    await sleep()\n\n    expect(comp.$('p')).to.be.ok\n\n    await sleep()\n\n    router.push('/')\n\n    await sleep()\n\n    expect(comp.$('p')).to.be.not.ok\n\n    comp.unmount()\n  })\n})\n"
  },
  {
    "path": "test/misc.spec.js",
    "content": "import { base, sleep } from './util.js'\nimport { getCurrentRoute, router, setBase } from '../src/index.js'\nimport { expect } from 'chai'\nimport { normalizeBase } from '../src/set-base.js'\n\ndescribe('misc methods', function () {\n  beforeEach(() => {\n    setBase(`${base}#`)\n  })\n\n  it('getCurrentRoute returns properly the current router value', async function () {\n    router.push(`${base}#/hello`)\n\n    await sleep()\n\n    expect(getCurrentRoute()).to.be.equal(`${base}#/hello`)\n  })\n\n  it('normalizeBase returns the expected paths', async function () {\n    expect(normalizeBase('#')).to.be.equal(`${base}#`)\n    expect(normalizeBase('/')).to.be.equal(`${base}`)\n    expect(normalizeBase('')).to.be.equal(`${base}`)\n    expect(normalizeBase('/hello')).to.be.equal(`${base}/hello`)\n    expect(normalizeBase('hello')).to.be.equal(`${base}/hello`)\n    expect(normalizeBase('http://google.com')).to.be.equal('http://google.com')\n    expect(normalizeBase('/page#anchor')).to.be.equal(`${base}/page#anchor`)\n  })\n})\n"
  },
  {
    "path": "test/setup.js",
    "content": "import { base } from './util.js'\nimport { register } from 'node:module'\nimport { pathToFileURL } from 'node:url'\nimport jsdomGlobal from 'jsdom-global'\nimport sinonChai from 'sinon-chai'\nimport { use } from 'chai'\n\nregister('@riotjs/register', pathToFileURL('./'))\n\njsdomGlobal(null, {\n  url: base,\n})\n\nuse(sinonChai)\n"
  },
  {
    "path": "test/standalone-hash-dom.spec.js",
    "content": "import { base, sleep } from './util.js'\nimport { route, router, setBase } from '../src/index.js'\nimport { expect } from 'chai'\nimport { spy } from 'sinon'\n\ndescribe('standalone hash', function () {\n  beforeEach(() => {\n    setBase('#')\n  })\n\n  afterEach(() => {\n    window.history.replaceState(null, '', '/')\n  })\n\n  it('hash links dispatch events', async function () {\n    const onRoute = spy()\n    const hello = route('/hello').on.value(onRoute)\n\n    router.push(`${base}#/hello`)\n\n    await sleep()\n\n    expect(onRoute).to.have.been.called\n    hello.end()\n  })\n\n  it('hash links receive parameters', (done) => {\n    const user = route('/user/:username').on.value((url) => {\n      user.end()\n      expect(url.params).to.be.deep.equal({ username: 'gianluca' })\n      done()\n    })\n\n    router.push(`${base}#/user/gianluca`)\n  })\n})\n"
  },
  {
    "path": "test/standalone-history-dom.spec.js",
    "content": "import { fireEvent, sleep } from './util.js'\nimport { initDomListeners, route, setBase } from '../src/index.js'\nimport $ from 'bianco.query'\nimport { expect } from 'chai'\nimport { spy } from 'sinon'\n\ndescribe('standalone history', function () {\n  let teardown // eslint-disable-line\n\n  beforeEach(() => {\n    setBase('/')\n\n    document.body.innerHTML = `\n    <nav>\n      <a href=\"/hello\">Hello</a>\n      <a href=\"/user\">User</a>\n      <a href=\"/goodbye\">goodbye</a>\n      <a href=\"/user/gianluca\">Username</a>\n      <a href=\"/hello#anchor\">Anchor</a>\n    </nav>\n  `\n    teardown = initDomListeners($('nav')[0])\n  })\n\n  afterEach(() => {\n    document.body.innerHTML = ''\n    window.history.replaceState(null, '', '/')\n    teardown()\n  })\n\n  it('html5 history links dispatch events', async function () {\n    const onRoute = spy()\n    const hello = route('/hello').on.value(onRoute)\n\n    const [a] = $('nav > a:first-of-type')\n\n    fireEvent(a, 'click')\n\n    await sleep()\n\n    expect(window.location.pathname).to.be.equal('/hello')\n    expect(onRoute).to.have.been.called\n\n    hello.end()\n  })\n\n  it('html5 history links receive parameters', (done) => {\n    const user = route('/user/:username').on.value((url) => {\n      user.end()\n      expect(url.params).to.be.deep.equal({ username: 'gianluca' })\n      done()\n    })\n\n    const [a] = $('nav > a:nth-child(4)')\n\n    fireEvent(a, 'click')\n  })\n\n  it('hash links are supported', async () => {\n    const onRoute = spy()\n    const hello = route('/hello(/?[?#].*)?').on.value(onRoute)\n\n    const [a] = $('nav > a:nth-child(5)')\n\n    fireEvent(a, 'click')\n\n    await sleep()\n\n    expect(onRoute).to.have.been.called\n    expect(window.location.pathname).to.be.equal('/hello')\n    expect(window.location.hash).to.be.equal('#anchor')\n\n    hello.end()\n  })\n})\n"
  },
  {
    "path": "test/util.js",
    "content": "export function fireEvent(el, name) {\n  const e = new Event(name, { bubbles: true, cancelable: false })\n\n  el.dispatchEvent(e)\n}\n\nexport const sleep = (timeout) => new Promise((r) => setTimeout(r, timeout))\n\nexport const base = 'https://riot.rocks'\n"
  }
]