diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index e3b2bf2..b4eb278 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -3,11 +3,101 @@ "lockfileVersion": 3, "requires": true, "packages": { + "node_modules/@riotjs/route": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@riotjs/route/-/route-10.0.0.tgz", + "integrity": "sha512-NQ9JfIzq/itFthEbCS7GqaVEJ+yjgMqIEZcP2QbiHALcMjyeaR1CacGMMd44e9Gi+Y+N+kOebpTVw2QczTAiqg==", + "license": "MIT", + "dependencies": { + "@riotjs/util": "^10.0.0", + "bianco.attr": "^1.1.1", + "bianco.events": "^1.1.1", + "bianco.query": "^1.1.4", + "cumpa": "^2.0.1", + "rawth": "^3.0.0" + } + }, + "node_modules/@riotjs/util": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@riotjs/util/-/util-10.1.2.tgz", + "integrity": "sha512-K85suj+5YItWHB5N6LO1uMJNH6ZMBl8FxGH2xDb6dl8V3EBlLuQaPQGVzp3SOKKqeqcxJH3o5UHHIgjc2I8YrA==", + "license": "MIT" + }, + "node_modules/bianco.attr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bianco.attr/-/bianco.attr-1.1.1.tgz", + "integrity": "sha512-fTjfPnnGYiCVbe5UltC/LsDRtJE+MjmadtL749CMIfCwjl18sdbCkaQ7cgtSao6iC9ZJC8Pzw0rjMdIuA6mK1g==", + "license": "MIT", + "dependencies": { + "bianco.dom-to-array": "^1.1.0" + } + }, + "node_modules/bianco.dom-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bianco.dom-to-array/-/bianco.dom-to-array-1.1.0.tgz", + "integrity": "sha512-IWUgplQRhJSZh+7PgD/my5+X27PXNUFdcHPosOYz39a/iFF8Wl9d0N/mOArdR7Zgr3J0Q9pKVk7nO6W+7XZwBg==", + "license": "MIT" + }, + "node_modules/bianco.events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bianco.events/-/bianco.events-1.1.1.tgz", + "integrity": "sha512-Ja7oY4xThYgsmfS+JltOnzdAvqP90DVXjbXab0lwrygJdCVRoL0Q4SkEKVMnN1VqNfDtxIUKNlubEUVNp00H7A==", + "license": "MIT", + "dependencies": { + "bianco.dom-to-array": "^1.1.0" + } + }, + "node_modules/bianco.query": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bianco.query/-/bianco.query-1.1.4.tgz", + "integrity": "sha512-jUu8l484ckacCBmxN0gYLZ4Ge5aMfReL+aYNiC81s37s8+l0+rn9pnQayEgQtMHGlnL8ejd+x5U2PKpo0rvQzw==", + "license": "MIT", + "dependencies": { + "bianco.dom-to-array": "^1.1.0" + } + }, + "node_modules/cumpa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cumpa/-/cumpa-2.0.1.tgz", + "integrity": "sha512-8oBF1cSWkgYq0ZsLP9iiLLZDicIh1eYM8WLicRhaSLMrdtjEf9A3DgMYMxB/i/xgVUZGxPVD/hrwVzx/pjdmmw==", + "license": "MIT" + }, + "node_modules/erre": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/erre/-/erre-3.0.1.tgz", + "integrity": "sha512-NoexRasUiWU1CcBMh997iybzdKRw4RPhjjiVjPwh1h+aK0PglsR6+7A3osXP5829hXNnarn9Yr1Zi9ThwwV4aA==", + "license": "MIT", + "dependencies": { + "ruit": "^1.0.4" + } + }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "license": "BSD-2-Clause" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/rawth": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rawth/-/rawth-3.0.0.tgz", + "integrity": "sha512-712WxtKAVEhtFm/4keDUefyjxAZR/jKrFR8ownCdjnFUxJstyigv3LBo3Shtd5VPC/p6r5gd6jY0bnAfPSol9Q==", + "license": "MIT", + "dependencies": { + "erre": "^3.0.1", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/ruit": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ruit/-/ruit-1.0.4.tgz", + "integrity": "sha512-eiHVb18DQ24Of/fdJmZCysw6X21IIyed5c87eAW95KQY5TvTfh6SR9pCkAowciyvhW1Bhm3RXuRX6eILKl+49w==", + "license": "MIT" } } } diff --git a/node_modules/@riotjs/route/LICENSE b/node_modules/@riotjs/route/LICENSE new file mode 100644 index 0000000..74e6791 --- /dev/null +++ b/node_modules/@riotjs/route/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Gianluca Guarini + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/@riotjs/route/README.md b/node_modules/@riotjs/route/README.md new file mode 100644 index 0000000..69b0931 --- /dev/null +++ b/node_modules/@riotjs/route/README.md @@ -0,0 +1,264 @@ +# Riot Router + +[![Route logo](https://raw.githubusercontent.com/riot/branding/main/route/route-horizontal.svg)](https://github.com/riot/route/) + +[![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] + +> Simple isomorphic router + +The Riot.js Router is the minimal router implementation with such technologies: + +- compatible with the DOM pushState and history API +- isomorphic functional API +- [erre.js streams](https://github.com/GianlucaGuarini/erre) and javascript async generators +- [rawth.js](https://github.com/GianlucaGuarini/rawth) urls parsing + +It doesn't need Riot.js to work and can be used as standalone module. + +**For Riot.js 3 and the older route version please check the [v3 branch](https://github.com/riot/route/tree/v3)** + +## Table of Contents + +- [Install](#install) +- [Documentation](#documentation) +- [Demos](https://github.com/riot/examples) + +## Install + +We have 2 editions: + +| edition | file | +| :------------------------ | :------------------------ | +| **ESM Module** | `index.js` | +| **UMD Version** | `index.umd.js` | +| **Standalone ESM Module** | `index.standalone.js` | +| **Standalone UMD Module** | `index.standalone.umd.js` | + +### Script injection + +```html + +``` + +_Note_: change the part `x.x.x` to the version numbers what you want to use: ex. `4.5.0` or `4.7.0`. + +### ESM module + +```js +import { route } from 'https://unpkg.com/@riotjs/route/index.js' +``` + +### npm + +```bash +npm i -S @riotjs/route +``` + +### Download by yourself + +- [Standalone](https://unpkg.com/@riotjs/route/route.js) +- [ESM](https://unpkg.com/@riotjs/route/route.esm.js) + +## Documentation + +### With Riot.js + +You can import the `` and `` components in your application and use them as it follows: + +```html + + + + + + + Home page + About + Hello dear { route.params.person } + + + + +``` + +You can also use the `riot.register` method to register them globally + +```js +import { Route, Router } from '@riotjs/route' +import { register } from 'riot' + +// now the Router and Route components are globally available +register('router', Router) +register('route', Route) +``` + +#### Router + +The `` component should wrap your application markup and will detect automatically all the clicks on links that should trigger a route event. + +```html + + + Link + + +Link +``` + +You can also specify the base of your application via component attributes: + +```html + + + Link + +``` + +The router component has also an `onStarted` callback that will be called asynchronously after the first route event will be called + +```html + +``` + +#### Route + +The `` 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. + +```html + {JSON.stringify(route.params)} + + + + + {route.searchParams.get('q')} + +``` + +Each `` component has its own lifecycle attributes in order to let you know when it gets mounted or unmounted. + +```riot + + + + + +``` + +### Standalone + +This module was not only designed to be used with Riot.js but also as standalone module. +Without 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. + +Depending on your project setup you might import it as follows: + +```js +// in a Riot.js application +import { route } from '@riotjs/route' + +// in a standalone context +import { route } from '@riotjs/route/standalone' +``` + +#### Fundamentals + +This module works on node and on any modern browser, it exports the `router` and `router` property exposed by [rawth](https://github.com/GianlucaGuarini/rawth) + +```js +import { route, router, setBase } from '@riotjs/route' + +// required to set base first +setBase('/') + +// create a route stream +const aboutStream = route('/about') + +aboutStream.on.value((url) => { + console.log(url) // URL object +}) + +aboutStream.on.value(() => { + console.log('just log that the about route was triggered') +}) + +// triggered on each route event +router.on.value((path) => { + // path is always a string in this function + console.log(path) +}) + +// trigger a route change manually +router.push('/about') + +// end the stream +aboutStream.end() +``` + +#### Base path + +Before using the router in your browser you will need to set your application base path. +This setting can be configured simply via `setBase` method: + +```js +import { setBase } from '@riotjs/route' + +// in case you want to use the HTML5 history navigation +setBase(`/`) + +// in case you use the hash navigation +setBase(`#`) +``` + +Setting the base path of your application route is mandatory and is the first you probably are going to do before creating your route listeners. + +#### DOM binding + +The 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. +Window history `popstate` events should be also connected to the router. +With the `initDomListeners` method you can automatically achieve all the features above: + +```js +import { initDomListeners } from '@riotjs/route' + +const unsubscribe = initDomListeners() +// the router is connected to the page DOM + +// ...tear down and disconnect the router from the DOM +unsubscribe() +``` + +The `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 + +```js +import { initDomListeners } from '@riotjs/route' + +initDomListeners(document.querySelector('.main-navigation')) +``` + +[ci-image]: https://img.shields.io/github/actions/workflow/status/riot/route/test.yml?style=flat-square +[ci-url]: https://github.com/riot/route/actions +[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square +[license-url]: LICENSE.txt +[npm-version-image]: http://img.shields.io/npm/v/@riotjs/route.svg?style=flat-square +[npm-downloads-image]: http://img.shields.io/npm/dm/@riotjs/route.svg?style=flat-square +[npm-url]: https://npmjs.org/package/@riotjs/route +[coverage-image]: https://qlty.sh/gh/riot/projects/route/coverage.svg +[coverage-url]: https://qlty.sh/gh/riot/projects/route +[qlty-image]: https://qlty.sh/gh/riot/projects/route/maintainability.svg +[qlty-url]: https://qlty.sh/gh/riot/projects/route diff --git a/node_modules/@riotjs/route/index.d.ts b/node_modules/@riotjs/route/index.d.ts new file mode 100644 index 0000000..a39ba31 --- /dev/null +++ b/node_modules/@riotjs/route/index.d.ts @@ -0,0 +1,26 @@ +import { RiotComponentWrapper, RiotComponent } from 'riot' +import { URLWithParams } from 'rawth' + +export * from 'rawth' + +export declare const Route: RiotComponentWrapper< + RiotComponent<{ + path: string + 'on-before-mount'?: (path: URLWithParams) => void + 'on-mounted'?: (path: URLWithParams) => void + 'on-before-unmount'?: (path: URLWithParams) => void + 'on-unmounted'?: (path: URLWithParams) => void + }> +> + +export declare const Router: RiotComponentWrapper< + RiotComponent<{ + base?: string + 'initial-route'?: string + 'on-started'?: (route: string) => void + }> +> + +export declare function getCurrentRoute(): string +export declare function initDomListeners(): void +export declare function setBase(base: string): void diff --git a/node_modules/@riotjs/route/index.js b/node_modules/@riotjs/route/index.js new file mode 100644 index 0000000..399c092 --- /dev/null +++ b/node_modules/@riotjs/route/index.js @@ -0,0 +1,1416 @@ +import { __, pure } from 'riot'; + +/** + * Tokenize input string. + */ +function lexer(str) { + var tokens = []; + var i = 0; + while (i < str.length) { + var char = str[i]; + if (char === "*" || char === "+" || char === "?") { + tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); + continue; + } + if (char === "\\") { + tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); + continue; + } + if (char === "{") { + tokens.push({ type: "OPEN", index: i, value: str[i++] }); + continue; + } + if (char === "}") { + tokens.push({ type: "CLOSE", index: i, value: str[i++] }); + continue; + } + if (char === ":") { + var name = ""; + var j = i + 1; + while (j < str.length) { + var code = str.charCodeAt(j); + if ( + // `0-9` + (code >= 48 && code <= 57) || + // `A-Z` + (code >= 65 && code <= 90) || + // `a-z` + (code >= 97 && code <= 122) || + // `_` + code === 95) { + name += str[j++]; + continue; + } + break; + } + if (!name) + throw new TypeError("Missing parameter name at ".concat(i)); + tokens.push({ type: "NAME", index: i, value: name }); + i = j; + continue; + } + if (char === "(") { + var count = 1; + var pattern = ""; + var j = i + 1; + if (str[j] === "?") { + throw new TypeError("Pattern cannot start with \"?\" at ".concat(j)); + } + while (j < str.length) { + if (str[j] === "\\") { + pattern += str[j++] + str[j++]; + continue; + } + if (str[j] === ")") { + count--; + if (count === 0) { + j++; + break; + } + } + else if (str[j] === "(") { + count++; + if (str[j + 1] !== "?") { + throw new TypeError("Capturing groups are not allowed at ".concat(j)); + } + } + pattern += str[j++]; + } + if (count) + throw new TypeError("Unbalanced pattern at ".concat(i)); + if (!pattern) + throw new TypeError("Missing pattern at ".concat(i)); + tokens.push({ type: "PATTERN", index: i, value: pattern }); + i = j; + continue; + } + tokens.push({ type: "CHAR", index: i, value: str[i++] }); + } + tokens.push({ type: "END", index: i, value: "" }); + return tokens; +} +/** + * Parse a string for the raw tokens. + */ +function parse(str, options) { + if (options === void 0) { options = {}; } + var tokens = lexer(str); + var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; + var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?"); + var result = []; + var key = 0; + var i = 0; + var path = ""; + var tryConsume = function (type) { + if (i < tokens.length && tokens[i].type === type) + return tokens[i++].value; + }; + var mustConsume = function (type) { + var value = tryConsume(type); + if (value !== undefined) + return value; + var _a = tokens[i], nextType = _a.type, index = _a.index; + throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type)); + }; + var consumeText = function () { + var result = ""; + var value; + while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { + result += value; + } + return result; + }; + while (i < tokens.length) { + var char = tryConsume("CHAR"); + var name = tryConsume("NAME"); + var pattern = tryConsume("PATTERN"); + if (name || pattern) { + var prefix = char || ""; + if (prefixes.indexOf(prefix) === -1) { + path += prefix; + prefix = ""; + } + if (path) { + result.push(path); + path = ""; + } + result.push({ + name: name || key++, + prefix: prefix, + suffix: "", + pattern: pattern || defaultPattern, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + var value = char || tryConsume("ESCAPED_CHAR"); + if (value) { + path += value; + continue; + } + if (path) { + result.push(path); + path = ""; + } + var open = tryConsume("OPEN"); + if (open) { + var prefix = consumeText(); + var name_1 = tryConsume("NAME") || ""; + var pattern_1 = tryConsume("PATTERN") || ""; + var suffix = consumeText(); + mustConsume("CLOSE"); + result.push({ + name: name_1 || (pattern_1 ? key++ : ""), + pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, + prefix: prefix, + suffix: suffix, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + mustConsume("END"); + } + return result; +} +/** + * Compile a string to a template function for the path. + */ +function compile(str, options) { + return tokensToFunction(parse(str, options), options); +} +/** + * Expose a method for transforming tokens into the path function. + */ +function tokensToFunction(tokens, options) { + if (options === void 0) { options = {}; } + var reFlags = flags(options); + var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b; + // Compile all the tokens into regexps. + var matches = tokens.map(function (token) { + if (typeof token === "object") { + return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags); + } + }); + return function (data) { + var path = ""; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token === "string") { + path += token; + continue; + } + var value = data ? data[token.name] : undefined; + var optional = token.modifier === "?" || token.modifier === "*"; + var repeat = token.modifier === "*" || token.modifier === "+"; + if (Array.isArray(value)) { + if (!repeat) { + throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array")); + } + if (value.length === 0) { + if (optional) + continue; + throw new TypeError("Expected \"".concat(token.name, "\" to not be empty")); + } + for (var j = 0; j < value.length; j++) { + var segment = encode(value[j], token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + } + continue; + } + if (typeof value === "string" || typeof value === "number") { + var segment = encode(String(value), token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + continue; + } + if (optional) + continue; + var typeOfMessage = repeat ? "an array" : "a string"; + throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage)); + } + return path; + }; +} +/** + * Escape a regular expression string. + */ +function escapeString(str) { + return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); +} +/** + * Get the flags for a regexp from the options. + */ +function flags(options) { + return options && options.sensitive ? "" : "i"; +} +/** + * Pull out keys from a regexp. + */ +function regexpToRegexp(path, keys) { + if (!keys) + return path; + var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g; + var index = 0; + var execResult = groupsRegex.exec(path.source); + while (execResult) { + keys.push({ + // Use parenthesized substring match if available, index otherwise + name: execResult[1] || index++, + prefix: "", + suffix: "", + modifier: "", + pattern: "", + }); + execResult = groupsRegex.exec(path.source); + } + return path; +} +/** + * Transform an array into a regexp. + */ +function arrayToRegexp(paths, keys, options) { + var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); + return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options)); +} +/** + * Create a path regexp from string input. + */ +function stringToRegexp(path, keys, options) { + return tokensToRegexp(parse(path, options), keys, options); +} +/** + * Expose a function for taking tokens and returning a RegExp. + */ +function tokensToRegexp(tokens, keys, options) { + if (options === void 0) { options = {}; } + var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f; + var endsWithRe = "[".concat(escapeString(endsWith), "]|$"); + var delimiterRe = "[".concat(escapeString(delimiter), "]"); + var route = start ? "^" : ""; + // Iterate over the tokens and create our regexp string. + for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { + var token = tokens_1[_i]; + if (typeof token === "string") { + route += escapeString(encode(token)); + } + else { + var prefix = escapeString(encode(token.prefix)); + var suffix = escapeString(encode(token.suffix)); + if (token.pattern) { + if (keys) + keys.push(token); + if (prefix || suffix) { + if (token.modifier === "+" || token.modifier === "*") { + var mod = token.modifier === "*" ? "?" : ""; + route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod); + } + else { + route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier); + } + } + else { + if (token.modifier === "+" || token.modifier === "*") { + route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")"); + } + else { + route += "(".concat(token.pattern, ")").concat(token.modifier); + } + } + } + else { + route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier); + } + } + } + if (end) { + if (!strict) + route += "".concat(delimiterRe, "?"); + route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")"); + } + else { + var endToken = tokens[tokens.length - 1]; + var isEndDelimited = typeof endToken === "string" + ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 + : endToken === undefined; + if (!strict) { + route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?"); + } + if (!isEndDelimited) { + route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")"); + } + } + return new RegExp(route, flags(options)); +} +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ +function pathToRegexp(path, keys, options) { + if (path instanceof RegExp) + return regexpToRegexp(path, keys); + if (Array.isArray(path)) + return arrayToRegexp(path, keys, options); + return stringToRegexp(path, keys, options); +} + +/** + * Cancel token + * @private + * @type { Symbol } + */ +const CANCEL = Symbol(); + +/** + * Helper that can be returned by ruit function to cancel the tasks chain + * @returns { Symbol } internal private constant + * @example + * + * ruit( + * 100, + * num => Math.random() * num + * num => num > 50 ? ruit.cancel() : num + * num => num - 2 + * ).then(result => { + * console.log(result) // here we will get only number lower than 50 + * }) + * + */ +ruit.cancel = () => CANCEL; + +/** + * The same as ruit() but with the arguments inverted from right to left + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from right to left: 1 => 1 + 1 => 2 * 2 + * ruit.compose(squareAsync, addOne, 1).then(result => console.log(result)) // 4 + */ +ruit.compose = (...tasks) => ruit(...tasks.reverse()); + +/** + * Serialize a list of sync and async tasks from left to right + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from left to right: 1 => 1 + 1 => 2 * 2 + * ruit(1, addOne, squareAsync).then(result => console.log(result)) // 4 + */ +function ruit(...tasks) { + return new Promise((resolve, reject) => { + return (function run(queue, result) { + if (!queue.length) return resolve(result) + + const [task, ...rest] = queue; + const value = typeof task === 'function' ? task(result) : task; + const done = v => run(rest, v); + + // check against nil values + if (value != null) { + if (value === CANCEL) return + if (value.then) return value.then(done, reject) + } + + return Promise.resolve(done(value)) + })(tasks) + }) +} + +// Store the erre the API methods to handle the plugins installation +const API_METHODS = new Set(); +const UNSUBSCRIBE_SYMBOL = Symbol(); +const UNSUBSCRIBE_METHOD = 'off'; +const CANCEL_METHOD = 'cancel'; + +/** + * Factory function to create the stream generator + * @private + * @param {Set} modifiers - stream input modifiers + * @returns {Generator} the stream generator + */ +function createStream(modifiers) { + const stream = (function *stream() { + while (true) { + // get the initial stream value + const input = yield; + + // run the input sequence + yield ruit(input, ...modifiers); + } + })(); + + // start the stream + stream.next(); + + return stream +} + +/** + * Dispatch a value to several listeners + * @private + * @param {Set} callbacks - callbacks collection + * @param {*} value - anything + * @returns {Set} the callbacks received + */ +function dispatch(callbacks, value) { + callbacks.forEach(f => { + // unsubscribe the callback if erre.unsubscribe() will be returned + if (f(value) === UNSUBSCRIBE_SYMBOL) callbacks.delete(f); + }); + + return callbacks +} + +/** + * Throw a panic error + * @param {string} message - error message + * @returns {Error} an error object + */ +function panic$1(message) { + throw new Error(message) +} + +/** + * Install an erre plugin adding it to the API + * @param {string} name - plugin name + * @param {Function} fn - new erre API method + * @returns {Function} return the erre function + */ +erre.install = function(name, fn) { + if (!name || typeof name !== 'string') + panic$1('Please provide a name (as string) for your erre plugin'); + if (!fn || typeof fn !== 'function') + panic$1('Please provide a function for your erre plugin'); + + if (API_METHODS.has(name)) { + panic$1(`The ${name} is already part of the erre API, please provide a different name`); + } else { + erre[name] = fn; + API_METHODS.add(name); + } + + return erre +}; + +// alias for ruit canel to stop a stream chain +erre.install(CANCEL_METHOD, ruit.cancel); + +// unsubscribe helper +erre.install(UNSUBSCRIBE_METHOD, () => UNSUBSCRIBE_SYMBOL); + +/** + * Stream constuction function + * @param {...Function} fns - stream modifiers + * @returns {Object} erre instance + */ +function erre(...fns) { + const + [success, error, end, modifiers] = [new Set(), new Set(), new Set(), new Set(fns)], + generator = createStream(modifiers), + stream = Object.create(generator), + addToCollection = (collection) => (fn) => collection.add(fn) && stream, + deleteFromCollection = (collection) => (fn) => collection.delete(fn) ? stream + : panic$1('Couldn\'t remove handler passed by reference'); + + return Object.assign(stream, { + on: Object.freeze({ + value: addToCollection(success), + error: addToCollection(error), + end: addToCollection(end) + }), + off: Object.freeze({ + value: deleteFromCollection(success), + error: deleteFromCollection(error), + end: deleteFromCollection(end) + }), + connect: addToCollection(modifiers), + push(input) { + const { value, done } = stream.next(input); + + // dispatch the stream events + if (!done) { + value.then( + res => dispatch(success, res), + err => dispatch(error, err) + ); + } + + return stream + }, + end() { + // kill the stream + generator.return(); + // dispatch the end event + dispatch(end) + // clean up all the collections + ;[success, error, end, modifiers].forEach(el => el.clear()); + + return stream + }, + fork() { + return erre(...modifiers) + }, + next(input) { + // get the input and run eventually the promise + const result = generator.next(input); + + // pause to the next iteration + generator.next(); + + return result + } + }) +} + +const isString = str => typeof str === 'string'; +const parseURL = (...args) => new URL(...args); + +/** + * Replace the base path from a path + * @param {string} path - router path string + * @returns {string} path cleaned up without the base + */ +const replaceBase = path => path.replace(defaults.base, ''); + +/** + * Try to match the current path or skip it + * @param {RegExp} pathRegExp - target path transformed by pathToRegexp + * @returns {string|Symbol} if the path match we return it otherwise we cancel the stream + */ +const matchOrSkip = pathRegExp => path => match(path, pathRegExp) ? path : erre.cancel(); + +/** + * Combine 2 streams connecting the events of dispatcherStream to the receiverStream + * @param {Stream} dispatcherStream - main stream dispatching events + * @param {Stream} receiverStream - sub stream receiving events from the dispatcher + * @returns {Stream} receiverStream + */ +const joinStreams = (dispatcherStream, receiverStream) => { + dispatcherStream.on.value(receiverStream.push); + + receiverStream.on.end(() => { + dispatcherStream.off.value(receiverStream.push); + }); + + return receiverStream +}; + +/** + * Error handling function + * @param {Error} error - error to catch + * @returns {void} + */ +/* c8 ignore start */ +const panic$2 = error => { + if (defaults.silentErrors) return + + throw new Error(error) +}; +/* c8 ignore stop */ + +// make sure that the router will always receive strings params +const filterStrings = str => isString(str) ? str : erre.cancel(); + +// create the streaming router +const router = erre(filterStrings).on.error(panic$2); // cast the values of this stream always to string + +/** + * Merge the user options with the defaults + * @param {Object} options - custom user options + * @returns {Object} options object merged with defaults + */ +const mergeOptions = options => ({...defaults, ...options}); + +/* @type {object} general configuration object */ +const defaults = { + base: 'https://localhost', + silentErrors: false, + // pathToRegexp options + sensitive: false, + strict: false, + end: true, + start: true, + delimiter: '/#?', + encode: undefined, + endsWith: undefined, + prefixes: './' +}; + +/** + * Configure the router options overriding the defaults + * @param {Object} options - custom user options to override + * @returns {Object} new defaults + */ +const configure = (options) => { + Object.entries(options).forEach(([key, value]) => { + if (Object.hasOwn(defaults, key)) defaults[key] = value; + }); + + return defaults +}; + + + +/* {@link https://github.com/pillarjs/path-to-regexp#usage} */ +const toRegexp = (path, keys, options) => pathToRegexp(path, keys, mergeOptions(options)); + +/** + * Convert a router entry to a real path computing the url parameters + * @param {string} path - router path string + * @param {Object} params - named matched parameters + * @param {Object} options - pathToRegexp options object + * @returns {string} computed url string + */ +const toPath = (path, params, options) => compile(path, mergeOptions(options))(params); + +/** + * Parse a string path generating an object containing + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - object containing the base path + * @returns {URL} url object enhanced with the `match` attribute + */ +const toURL = (path, pathRegExp, options = {}) => { + const {base} = mergeOptions(options); + const [, ...params] = pathRegExp.exec(path); + const url = parseURL(path, base); + + // extend the url object adding the matched params + url.params = params.reduce((acc, param, index) => { + const key = options.keys && options.keys[index]; + if (key) acc[key.name] = param ? decodeURIComponent(param) : param; + return acc + }, {}); + + return url +}; + +/** + * Return true if a path will be matched + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @returns {boolean} true if the path matches the regexp + */ +const match = (path, pathRegExp) => pathRegExp.test(path); + +/** + * Factory function to create an sequence of functions to pass to erre.js + * This function will be used in the erre stream + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - pathToRegexp options object + * @returns {Array} a functions array that will be used as stream pipe for erre.js + */ +const createURLStreamPipe = (pathRegExp, options) => [ + decodeURI, + replaceBase, + matchOrSkip(pathRegExp), + path => toURL(path, pathRegExp, options) +]; + +/** + * Create a fork of the main router stream + * @param {string} path - route to match + * @param {Object} options - pathToRegexp options object + * @returns {Stream} new route stream + */ +function createRoute(path, options) { + const keys = []; + const pathRegExp = pathToRegexp(path, keys, options); + const URLStream = erre(...createURLStreamPipe(pathRegExp, { + ...options, + keys + })); + + return joinStreams(router, URLStream).on.error(panic$2) +} + +const WINDOW_EVENTS = 'popstate'; +const CLICK_EVENT = 'click'; +const DOWNLOAD_LINK_ATTRIBUTE = 'download'; +const HREF_LINK_ATTRIBUTE = 'href'; +const TARGET_SELF_LINK_ATTRIBUTE = '_self'; +const LINK_TAG_NAME = 'A'; +const HASH = '#'; +const SLASH = '/'; +const PATH_ATTRIBUTE = 'path'; +const RE_ORIGIN = /^.+?\/\/+[^/]+/; + +/** + * Converts any DOM node/s to a loopable array + * @param { HTMLElement|NodeList } els - single html element or a node list + * @returns { Array } always a loopable object + */ +function domToArray(els) { + // can this object be already looped? + if (!Array.isArray(els)) { + // is it a node list? + if ( + /^\[object (HTMLCollection|NodeList|Object)\]$/ + .test(Object.prototype.toString.call(els)) + && typeof els.length === 'number' + ) + return Array.from(els) + else + // if it's a single node + // it will be returned as "array" with one single entry + return [els] + } + // this object could be looped out of the box + return els +} + +/** + * Simple helper to find DOM nodes returning them as array like loopable object + * @param { string|DOMNodeList } selector - either the query or the DOM nodes to arraify + * @param { HTMLElement } scope - context defining where the query will search for the DOM nodes + * @returns { Array } DOM nodes found as array + */ +function $(selector, scope) { + return domToArray(typeof selector === 'string' ? + (document).querySelectorAll(selector) : + selector + ) +} + +const getCurrentRoute = ((currentRoute) => { + // listen the route changes events to store the current route + router.on.value((r) => (currentRoute = r)); + + return () => { + return currentRoute + } +})(null); + +/** + * Normalize the return values, in case of a single value we avoid to return an array + * @param { Array } values - list of values we want to return + * @returns { Array|string|boolean } either the whole list of values or the single one found + * @private + */ +const normalize = values => values.length === 1 ? values[0] : values; + +/** + * Parse all the nodes received to get/remove/check their attributes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes + * @param { string } method - method that will be used to parse the attributes + * @returns { Array|string } result of the parsing in a list or a single value + * @private + */ +function parseNodes(els, name, method) { + const names = typeof name === 'string' ? [name] : name; + return normalize(domToArray(els).map(el => { + return normalize(names.map(n => el[method](n))) + })) +} + +/** + * Get any attribute from a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to get + * @returns { Array|string } list of the attributes found + * + * @example + * + * import { get } from 'bianco.attr' + * + * const img = document.createElement('img') + * + * get(img, 'width') // => '200' + * + * // or also + * get(img, ['width', 'height']) // => ['200', '300'] + * + * // or also + * get([img1, img2], ['width', 'height']) // => [['200', '300'], ['500', '200']] + */ +function get(els, name) { + return parseNodes(els, name, 'getAttribute') +} + +/** + * Set any attribute on a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to detect + * @returns { boolean|Array } true or false or an array of boolean values + * @example + * + * import { has } from 'bianco.attr' + * + * has(img, 'width') // false + * + * // or also + * has(img, ['width', 'height']) // => [false, false] + * + * // or also + * has([img1, img2], ['width', 'height']) // => [[false, false], [false, false]] + */ +function has(els, name) { + return parseNodes(els, name, 'hasAttribute') +} + +/** + * Convert a string from camel case to dash-case + * @param {string} string - probably a component tag name + * @returns {string} component name normalized + */ + +/** + * Convert a string containing dashes to camel case + * @param {string} string - input string + * @returns {string} my-string -> myString + */ +function dashToCamelCase(string) { + return string.replace(/-(\w)/g, (_, c) => c.toUpperCase()) +} + +/** + * Check if a value is null or undefined + * @param {*} value - anything + * @returns {boolean} true only for the 'undefined' and 'null' types + */ +function isNil(value) { + return value === null || value === undefined +} + +const getGlobal = () => getWindow() || global; +const getWindow = () => (typeof window === 'undefined' ? null : window); +const getDocument = () => + typeof document === 'undefined' ? null : document; +const getHistory = () => + typeof history === 'undefined' ? null : history; +const getLocation = () => { + const win = getWindow(); + return win ? win.location : {} +}; + +const defer = (() => { + const globalScope = getGlobal(); + + return globalScope.requestAnimationFrame || globalScope.setTimeout +})(); + +const cancelDefer = (() => { + const globalScope = getGlobal(); + + return globalScope.cancelAnimationFrame || globalScope.clearTimeout +})(); + +const getAttribute = (attributes, name, context) => { + if (!attributes) return null + + const normalizedAttributes = attributes.flatMap((attr) => + isNil(attr.name) + ? // add support for spread attributes https://github.com/riot/route/issues/178 + Object.entries(attr.evaluate(context)).map(([key, value]) => ({ + // evaluate each value of the spread attribute and store it into the array + name: key, + // create a nested evaluate function pointing to the original value of the spread object + evaluate: () => value, + })) + : attr, + ); + + return normalizedAttributes.find((a) => dashToCamelCase(a.name) === name) +}; + +const createDefaultSlot = (attributes = []) => { + const { template, bindingTypes, expressionTypes } = __.DOMBindings; + + return template(null, [ + { + type: bindingTypes.SLOT, + name: 'default', + attributes: attributes.map((attr) => ({ + ...attr, + type: expressionTypes.ATTRIBUTE, + })), + }, + ]) +}; + +// True if the selector string is valid +const isValidQuerySelectorString = (selector) => + /^([a-zA-Z0-9-_*#.:[\]\s>+~()='"]|\\.)+$/.test(selector); + +/** + * Similar to compose but performs from left-to-right function composition.
+ * {@link https://30secondsofcode.org/function#composeright see also} + * @param {...[function]} fns) - list of unary function + * @returns {*} result of the computation + */ + +/** + * Performs right-to-left function composition.
+ * Use Array.prototype.reduce() to perform right-to-left function composition.
+ * The last (rightmost) function can accept one or more arguments; the remaining functions must be unary.
+ * {@link https://30secondsofcode.org/function#compose original source code} + * @param {...[function]} fns) - list of unary function + * @returns {*} result of the computation + */ +function compose(...fns) { + return fns.reduce((f, g) => (...args) => f(g(...args))) +} + +const getInitialRouteValue = (pathToRegexp, path, options) => { + const route = compose( + ...createURLStreamPipe(pathToRegexp, options).reverse(), + )(path); + + return route.params ? route : null +}; + +const clearDOMBetweenNodes = (first, last, includeBoundaries) => { + const clear = (node) => { + if (!node || (node === last && !includeBoundaries)) return + const { nextSibling } = node; + node.remove(); + clear(nextSibling); + }; + + clear(includeBoundaries ? first : first.nextSibling); +}; + +const routeHoc$1 = ({ slots, attributes }) => { + const placeholders = { + before: document.createTextNode(''), + after: document.createTextNode(''), + }; + + return { + mount(el, context) { + // create the component state + const currentRoute = getCurrentRoute(); + const path = + getAttribute(attributes, PATH_ATTRIBUTE, context)?.evaluate(context) || + get(el, PATH_ATTRIBUTE); + const pathToRegexp = toRegexp(path, []); + const state = { + pathToRegexp, + route: + currentRoute && match(currentRoute, pathToRegexp) + ? getInitialRouteValue(pathToRegexp, currentRoute, {}) + : null, + }; + this.el = el; + this.slot = createDefaultSlot([ + { + isBoolean: false, + name: 'route', + evaluate: () => this.state.route, + }, + ]); + this.context = context; + this.state = state; + // set the route listeners + this.boundOnBeforeRoute = this.onBeforeRoute.bind(this); + this.boundOnRoute = this.onRoute.bind(this); + router.on.value(this.boundOnBeforeRoute); + this.stream = createRoute(path).on.value(this.boundOnRoute); + // update the DOM + el.replaceWith(placeholders.before); + placeholders.before.parentNode.insertBefore( + placeholders.after, + placeholders.before.nextSibling, + ); + if (state.route) this.mountSlot(); + }, + update(context) { + this.context = context; + if (this.state.route) this.slot.update({}, context); + }, + mountSlot() { + const { route } = this.state; + // insert the route root element after the before placeholder + placeholders.before.parentNode.insertBefore( + this.el, + placeholders.before.nextSibling, + ); + this.callLifecycleProperty('onBeforeMount', route); + this.slot.mount( + this.el, + { + slots, + }, + this.context, + ); + this.callLifecycleProperty('onMounted', route); + }, + clearDOM(includeBoundaries) { + // remove all the DOM nodes between the placeholders + clearDOMBetweenNodes( + placeholders.before, + placeholders.after, + includeBoundaries, + ); + }, + unmount() { + router.off.value(this.boundOnBeforeRoute); + this.slot.unmount({}, this.context, true); + this.clearDOM(true); + this.stream.end(); + }, + onBeforeRoute(path) { + const { route } = this.state; + // this component was not mounted or the current path matches + // we don't need to unmount this component + if (!route || match(path, this.state.pathToRegexp)) return + + this.callLifecycleProperty('onBeforeUnmount', route); + this.slot.unmount({}, this.context, true); + this.clearDOM(false); + this.state.route = null; + this.callLifecycleProperty('onUnmounted', route); + }, + onRoute(route) { + const prevRoute = this.state.route; + this.state.route = route; + + // if this route component was already mounted we need to update it + if (prevRoute) { + this.callLifecycleProperty('onBeforeUpdate', route); + this.slot.update({}, this.context); + this.callLifecycleProperty('onUpdated', route); + } + // this route component was never mounted, so we need to create its DOM + else this.mountSlot(); + + // emulate the default browser anchor links behaviour + if (route.hash && isValidQuerySelectorString(route.hash)) + $(route.hash)?.[0].scrollIntoView(); + }, + callLifecycleProperty(method, ...params) { + const attr = getAttribute(attributes, method, this.context); + + if (attr) attr.evaluate(this.context)(...params); + }, + } +}; + +var routeHoc = { + css: null, + + exports: pure( + routeHoc$1 + ), + + template: null, + name: 'route-hoc' +}; + +const normalizeInitialSlash = (str) => + str[0] === SLASH ? str : `${SLASH}${str}`; +const removeTrailingSlash = (str) => + str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str; + +const normalizeBase = (base) => { + const win = getWindow(); + const loc = win.location; + const root = loc ? `${loc.protocol}//${loc.host}` : ''; + const { pathname } = loc ? loc : {}; + + switch (true) { + // pure root url + pathname + case Boolean(base) === false: + return removeTrailingSlash(`${root}${pathname || ''}`) + // full path base + case /(www|http(s)?:)/.test(base): + return base + // hash navigation + case base[0] === HASH: + return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}` + // root url with trailing slash + case base === SLASH: + return removeTrailingSlash(root) + // custom pathname + default: + return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`) + } +}; + +function setBase(base) { + configure({ base: normalizeBase(base) }); +} + +/** + * Throw an error with a descriptive message + * @param { string } message - error message + * @param { string } cause - optional error cause object + * @returns { undefined } hoppla... at this point the program should stop working + */ +function panic(message, cause) { + throw new Error(message, { cause }) +} + +/** + * Split a string into several items separed by spaces + * @param { string } l - events list + * @returns { Array } all the events detected + * @private + */ +const split = l => l.split(/\s/); + +/** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|NodeList|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind or unbind space separated + * @param { Function } cb - listeners callback + * @param { string } method - either 'addEventListener' or 'removeEventListener' + * @param { Object } options - event options (capture, once and passive) + * @returns { undefined } + * @private + */ +function manageEvents(els, evList, cb, method, options) { + els = domToArray(els); + + split(evList).forEach((e) => { + els.forEach(el => el[method](e, cb, options || false)); + }); +} + +/** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ +function add(els, evList, cb, options) { + manageEvents(els, evList, cb, 'addEventListener', options); + return els +} + +/** + * Remove all the listeners for the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the events will be unbind + * @param { string } evList - list of events we want unbind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ +function remove(els, evList, cb, options) { + manageEvents(els, evList, cb, 'removeEventListener', options); + return els +} + +const onWindowEvent = () => + router.push(normalizePath(String(getLocation().href))); +const onRouterPush = (path) => { + const url = path.includes(defaults.base) ? path : defaults.base + path; + const loc = getLocation(); + const hist = getHistory(); + const doc = getDocument(); + + // update the browser history only if it's necessary + if (hist && url !== loc.href) { + hist.pushState(null, doc.title, url); + } +}; +const getLinkElement = (node) => + node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node; +const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME; +const isCrossOriginLink = (path) => + path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1; +const isTargetSelfLink = (el) => + el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE; +const isEventForbidden = (event) => + (event.which && event.which !== 1) || // not left click + event.metaKey || + event.ctrlKey || + event.shiftKey || // or meta keys + event.defaultPrevented; // or default prevented +const isForbiddenLink = (el) => + !el || + !isLinkNode(el) || // not A tag + has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr + !has(el, HREF_LINK_ATTRIBUTE) || // has no href attr + isTargetSelfLink(el) || + isCrossOriginLink(el.href); +const normalizePath = (path) => path.replace(defaults.base, ''); +const isInBase = (path) => !defaults.base || path.includes(defaults.base); + +/** + * Callback called anytime something will be clicked on the page + * @param {Event} event - click event + * @returns {undefined} void method + */ +const onClick = (event) => { + if (isEventForbidden(event)) return + + const el = getLinkElement(event.target); + + if (isForbiddenLink(el) || !isInBase(el.href)) return + + event.preventDefault(); + + router.push(normalizePath(el.href)); +}; + +/** + * Link the rawth router to the DOM events + * @param { HTMLElement } container - DOM node where the links are located + * @returns {Function} teardown function + */ +function initDomListeners(container) { + const win = getWindow(); + const root = container || getDocument(); + + if (win) { + add(win, WINDOW_EVENTS, onWindowEvent); + add(root, CLICK_EVENT, onClick); + } + + router.on.value(onRouterPush); + + return () => { + if (win) { + remove(win, WINDOW_EVENTS, onWindowEvent); + remove(root, CLICK_EVENT, onClick); + } + + router.off.value(onRouterPush); + } +} + +const BASE_ATTRIBUTE_NAME = 'base'; +const INITIAL_ROUTE = 'initialRoute'; +const ON_STARTED_ATTRIBUTE_NAME = 'onStarted'; + +const routerHoc$1 = ({ slots, attributes, props }) => { + if (routerHoc$1.wasInitialized) + panic('Multiple components are not supported'); + + return { + slot: null, + el: null, + teardown: null, + mount(el, context) { + const initialRouteAttr = getAttribute(attributes, INITIAL_ROUTE, context); + const initialRoute = initialRouteAttr + ? initialRouteAttr.evaluate(context) + : null; + const currentRoute = getCurrentRoute(); + const onFirstRoute = () => { + this.createSlot(context); + router.off.value(onFirstRoute); + }; + routerHoc$1.wasInitialized = true; + + this.el = el; + this.teardown = initDomListeners(this.root); + + this.setBase(context); + + // mount the slots only if the current route was defined + if (currentRoute && !initialRoute) { + this.createSlot(context); + } else { + router.on.value(onFirstRoute); + router.push(initialRoute || window.location.href); + } + }, + createSlot(context) { + if (!slots || !slots.length) return + const onStartedAttr = getAttribute( + attributes, + ON_STARTED_ATTRIBUTE_NAME, + context, + ); + + this.slot = createDefaultSlot(); + + this.slot.mount( + this.el, + { + slots, + }, + context, + ); + + if (onStartedAttr) { + onStartedAttr.evaluate(context)(getCurrentRoute()); + } + }, + update(context) { + this.setBase(context); + + // defer the updates to avoid internal recursive update calls + // see https://github.com/riot/route/issues/148 + if (this.slot) { + cancelDefer(this.deferred); + + this.deferred = defer(() => { + this.slot.update({}, context); + }); + } + }, + unmount(...args) { + this.teardown(); + routerHoc$1.wasInitialized = false; + + if (this.slot) { + this.slot.unmount(...args); + } + }, + getBase(context) { + const baseAttr = getAttribute(attributes, BASE_ATTRIBUTE_NAME, context); + + return baseAttr + ? baseAttr.evaluate(context) + : this.el.getAttribute(BASE_ATTRIBUTE_NAME) || '/' + }, + setBase(context) { + setBase(props ? props.base : this.getBase(context)); + }, + } +}; + +// flag to avoid multiple router instances +routerHoc$1.wasInitialized = false; + +var routerHoc = { + css: null, + + exports: pure( + routerHoc$1 + ), + + template: null, + name: 'router-hoc' +}; + +export { routeHoc as Route, routerHoc as Router, configure, createURLStreamPipe, defaults, getCurrentRoute, initDomListeners, match, createRoute as route, router, setBase, toPath, toRegexp, toURL }; diff --git a/node_modules/@riotjs/route/index.standalone.js b/node_modules/@riotjs/route/index.standalone.js new file mode 100644 index 0000000..6f371df --- /dev/null +++ b/node_modules/@riotjs/route/index.standalone.js @@ -0,0 +1,1039 @@ +import 'riot'; + +/** + * Tokenize input string. + */ +function lexer(str) { + var tokens = []; + var i = 0; + while (i < str.length) { + var char = str[i]; + if (char === "*" || char === "+" || char === "?") { + tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); + continue; + } + if (char === "\\") { + tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); + continue; + } + if (char === "{") { + tokens.push({ type: "OPEN", index: i, value: str[i++] }); + continue; + } + if (char === "}") { + tokens.push({ type: "CLOSE", index: i, value: str[i++] }); + continue; + } + if (char === ":") { + var name = ""; + var j = i + 1; + while (j < str.length) { + var code = str.charCodeAt(j); + if ( + // `0-9` + (code >= 48 && code <= 57) || + // `A-Z` + (code >= 65 && code <= 90) || + // `a-z` + (code >= 97 && code <= 122) || + // `_` + code === 95) { + name += str[j++]; + continue; + } + break; + } + if (!name) + throw new TypeError("Missing parameter name at ".concat(i)); + tokens.push({ type: "NAME", index: i, value: name }); + i = j; + continue; + } + if (char === "(") { + var count = 1; + var pattern = ""; + var j = i + 1; + if (str[j] === "?") { + throw new TypeError("Pattern cannot start with \"?\" at ".concat(j)); + } + while (j < str.length) { + if (str[j] === "\\") { + pattern += str[j++] + str[j++]; + continue; + } + if (str[j] === ")") { + count--; + if (count === 0) { + j++; + break; + } + } + else if (str[j] === "(") { + count++; + if (str[j + 1] !== "?") { + throw new TypeError("Capturing groups are not allowed at ".concat(j)); + } + } + pattern += str[j++]; + } + if (count) + throw new TypeError("Unbalanced pattern at ".concat(i)); + if (!pattern) + throw new TypeError("Missing pattern at ".concat(i)); + tokens.push({ type: "PATTERN", index: i, value: pattern }); + i = j; + continue; + } + tokens.push({ type: "CHAR", index: i, value: str[i++] }); + } + tokens.push({ type: "END", index: i, value: "" }); + return tokens; +} +/** + * Parse a string for the raw tokens. + */ +function parse(str, options) { + if (options === void 0) { options = {}; } + var tokens = lexer(str); + var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; + var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?"); + var result = []; + var key = 0; + var i = 0; + var path = ""; + var tryConsume = function (type) { + if (i < tokens.length && tokens[i].type === type) + return tokens[i++].value; + }; + var mustConsume = function (type) { + var value = tryConsume(type); + if (value !== undefined) + return value; + var _a = tokens[i], nextType = _a.type, index = _a.index; + throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type)); + }; + var consumeText = function () { + var result = ""; + var value; + while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { + result += value; + } + return result; + }; + while (i < tokens.length) { + var char = tryConsume("CHAR"); + var name = tryConsume("NAME"); + var pattern = tryConsume("PATTERN"); + if (name || pattern) { + var prefix = char || ""; + if (prefixes.indexOf(prefix) === -1) { + path += prefix; + prefix = ""; + } + if (path) { + result.push(path); + path = ""; + } + result.push({ + name: name || key++, + prefix: prefix, + suffix: "", + pattern: pattern || defaultPattern, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + var value = char || tryConsume("ESCAPED_CHAR"); + if (value) { + path += value; + continue; + } + if (path) { + result.push(path); + path = ""; + } + var open = tryConsume("OPEN"); + if (open) { + var prefix = consumeText(); + var name_1 = tryConsume("NAME") || ""; + var pattern_1 = tryConsume("PATTERN") || ""; + var suffix = consumeText(); + mustConsume("CLOSE"); + result.push({ + name: name_1 || (pattern_1 ? key++ : ""), + pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, + prefix: prefix, + suffix: suffix, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + mustConsume("END"); + } + return result; +} +/** + * Compile a string to a template function for the path. + */ +function compile(str, options) { + return tokensToFunction(parse(str, options), options); +} +/** + * Expose a method for transforming tokens into the path function. + */ +function tokensToFunction(tokens, options) { + if (options === void 0) { options = {}; } + var reFlags = flags(options); + var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b; + // Compile all the tokens into regexps. + var matches = tokens.map(function (token) { + if (typeof token === "object") { + return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags); + } + }); + return function (data) { + var path = ""; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token === "string") { + path += token; + continue; + } + var value = data ? data[token.name] : undefined; + var optional = token.modifier === "?" || token.modifier === "*"; + var repeat = token.modifier === "*" || token.modifier === "+"; + if (Array.isArray(value)) { + if (!repeat) { + throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array")); + } + if (value.length === 0) { + if (optional) + continue; + throw new TypeError("Expected \"".concat(token.name, "\" to not be empty")); + } + for (var j = 0; j < value.length; j++) { + var segment = encode(value[j], token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + } + continue; + } + if (typeof value === "string" || typeof value === "number") { + var segment = encode(String(value), token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + continue; + } + if (optional) + continue; + var typeOfMessage = repeat ? "an array" : "a string"; + throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage)); + } + return path; + }; +} +/** + * Escape a regular expression string. + */ +function escapeString(str) { + return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); +} +/** + * Get the flags for a regexp from the options. + */ +function flags(options) { + return options && options.sensitive ? "" : "i"; +} +/** + * Pull out keys from a regexp. + */ +function regexpToRegexp(path, keys) { + if (!keys) + return path; + var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g; + var index = 0; + var execResult = groupsRegex.exec(path.source); + while (execResult) { + keys.push({ + // Use parenthesized substring match if available, index otherwise + name: execResult[1] || index++, + prefix: "", + suffix: "", + modifier: "", + pattern: "", + }); + execResult = groupsRegex.exec(path.source); + } + return path; +} +/** + * Transform an array into a regexp. + */ +function arrayToRegexp(paths, keys, options) { + var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); + return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options)); +} +/** + * Create a path regexp from string input. + */ +function stringToRegexp(path, keys, options) { + return tokensToRegexp(parse(path, options), keys, options); +} +/** + * Expose a function for taking tokens and returning a RegExp. + */ +function tokensToRegexp(tokens, keys, options) { + if (options === void 0) { options = {}; } + var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f; + var endsWithRe = "[".concat(escapeString(endsWith), "]|$"); + var delimiterRe = "[".concat(escapeString(delimiter), "]"); + var route = start ? "^" : ""; + // Iterate over the tokens and create our regexp string. + for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { + var token = tokens_1[_i]; + if (typeof token === "string") { + route += escapeString(encode(token)); + } + else { + var prefix = escapeString(encode(token.prefix)); + var suffix = escapeString(encode(token.suffix)); + if (token.pattern) { + if (keys) + keys.push(token); + if (prefix || suffix) { + if (token.modifier === "+" || token.modifier === "*") { + var mod = token.modifier === "*" ? "?" : ""; + route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod); + } + else { + route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier); + } + } + else { + if (token.modifier === "+" || token.modifier === "*") { + route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")"); + } + else { + route += "(".concat(token.pattern, ")").concat(token.modifier); + } + } + } + else { + route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier); + } + } + } + if (end) { + if (!strict) + route += "".concat(delimiterRe, "?"); + route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")"); + } + else { + var endToken = tokens[tokens.length - 1]; + var isEndDelimited = typeof endToken === "string" + ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 + : endToken === undefined; + if (!strict) { + route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?"); + } + if (!isEndDelimited) { + route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")"); + } + } + return new RegExp(route, flags(options)); +} +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ +function pathToRegexp(path, keys, options) { + if (path instanceof RegExp) + return regexpToRegexp(path, keys); + if (Array.isArray(path)) + return arrayToRegexp(path, keys, options); + return stringToRegexp(path, keys, options); +} + +/** + * Cancel token + * @private + * @type { Symbol } + */ +const CANCEL = Symbol(); + +/** + * Helper that can be returned by ruit function to cancel the tasks chain + * @returns { Symbol } internal private constant + * @example + * + * ruit( + * 100, + * num => Math.random() * num + * num => num > 50 ? ruit.cancel() : num + * num => num - 2 + * ).then(result => { + * console.log(result) // here we will get only number lower than 50 + * }) + * + */ +ruit.cancel = () => CANCEL; + +/** + * The same as ruit() but with the arguments inverted from right to left + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from right to left: 1 => 1 + 1 => 2 * 2 + * ruit.compose(squareAsync, addOne, 1).then(result => console.log(result)) // 4 + */ +ruit.compose = (...tasks) => ruit(...tasks.reverse()); + +/** + * Serialize a list of sync and async tasks from left to right + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from left to right: 1 => 1 + 1 => 2 * 2 + * ruit(1, addOne, squareAsync).then(result => console.log(result)) // 4 + */ +function ruit(...tasks) { + return new Promise((resolve, reject) => { + return (function run(queue, result) { + if (!queue.length) return resolve(result) + + const [task, ...rest] = queue; + const value = typeof task === 'function' ? task(result) : task; + const done = v => run(rest, v); + + // check against nil values + if (value != null) { + if (value === CANCEL) return + if (value.then) return value.then(done, reject) + } + + return Promise.resolve(done(value)) + })(tasks) + }) +} + +// Store the erre the API methods to handle the plugins installation +const API_METHODS = new Set(); +const UNSUBSCRIBE_SYMBOL = Symbol(); +const UNSUBSCRIBE_METHOD = 'off'; +const CANCEL_METHOD = 'cancel'; + +/** + * Factory function to create the stream generator + * @private + * @param {Set} modifiers - stream input modifiers + * @returns {Generator} the stream generator + */ +function createStream(modifiers) { + const stream = (function *stream() { + while (true) { + // get the initial stream value + const input = yield; + + // run the input sequence + yield ruit(input, ...modifiers); + } + })(); + + // start the stream + stream.next(); + + return stream +} + +/** + * Dispatch a value to several listeners + * @private + * @param {Set} callbacks - callbacks collection + * @param {*} value - anything + * @returns {Set} the callbacks received + */ +function dispatch(callbacks, value) { + callbacks.forEach(f => { + // unsubscribe the callback if erre.unsubscribe() will be returned + if (f(value) === UNSUBSCRIBE_SYMBOL) callbacks.delete(f); + }); + + return callbacks +} + +/** + * Throw a panic error + * @param {string} message - error message + * @returns {Error} an error object + */ +function panic$1(message) { + throw new Error(message) +} + +/** + * Install an erre plugin adding it to the API + * @param {string} name - plugin name + * @param {Function} fn - new erre API method + * @returns {Function} return the erre function + */ +erre.install = function(name, fn) { + if (!name || typeof name !== 'string') + panic$1('Please provide a name (as string) for your erre plugin'); + if (!fn || typeof fn !== 'function') + panic$1('Please provide a function for your erre plugin'); + + if (API_METHODS.has(name)) { + panic$1(`The ${name} is already part of the erre API, please provide a different name`); + } else { + erre[name] = fn; + API_METHODS.add(name); + } + + return erre +}; + +// alias for ruit canel to stop a stream chain +erre.install(CANCEL_METHOD, ruit.cancel); + +// unsubscribe helper +erre.install(UNSUBSCRIBE_METHOD, () => UNSUBSCRIBE_SYMBOL); + +/** + * Stream constuction function + * @param {...Function} fns - stream modifiers + * @returns {Object} erre instance + */ +function erre(...fns) { + const + [success, error, end, modifiers] = [new Set(), new Set(), new Set(), new Set(fns)], + generator = createStream(modifiers), + stream = Object.create(generator), + addToCollection = (collection) => (fn) => collection.add(fn) && stream, + deleteFromCollection = (collection) => (fn) => collection.delete(fn) ? stream + : panic$1('Couldn\'t remove handler passed by reference'); + + return Object.assign(stream, { + on: Object.freeze({ + value: addToCollection(success), + error: addToCollection(error), + end: addToCollection(end) + }), + off: Object.freeze({ + value: deleteFromCollection(success), + error: deleteFromCollection(error), + end: deleteFromCollection(end) + }), + connect: addToCollection(modifiers), + push(input) { + const { value, done } = stream.next(input); + + // dispatch the stream events + if (!done) { + value.then( + res => dispatch(success, res), + err => dispatch(error, err) + ); + } + + return stream + }, + end() { + // kill the stream + generator.return(); + // dispatch the end event + dispatch(end) + // clean up all the collections + ;[success, error, end, modifiers].forEach(el => el.clear()); + + return stream + }, + fork() { + return erre(...modifiers) + }, + next(input) { + // get the input and run eventually the promise + const result = generator.next(input); + + // pause to the next iteration + generator.next(); + + return result + } + }) +} + +const isString = str => typeof str === 'string'; +const parseURL = (...args) => new URL(...args); + +/** + * Replace the base path from a path + * @param {string} path - router path string + * @returns {string} path cleaned up without the base + */ +const replaceBase = path => path.replace(defaults.base, ''); + +/** + * Try to match the current path or skip it + * @param {RegExp} pathRegExp - target path transformed by pathToRegexp + * @returns {string|Symbol} if the path match we return it otherwise we cancel the stream + */ +const matchOrSkip = pathRegExp => path => match(path, pathRegExp) ? path : erre.cancel(); + +/** + * Combine 2 streams connecting the events of dispatcherStream to the receiverStream + * @param {Stream} dispatcherStream - main stream dispatching events + * @param {Stream} receiverStream - sub stream receiving events from the dispatcher + * @returns {Stream} receiverStream + */ +const joinStreams = (dispatcherStream, receiverStream) => { + dispatcherStream.on.value(receiverStream.push); + + receiverStream.on.end(() => { + dispatcherStream.off.value(receiverStream.push); + }); + + return receiverStream +}; + +/** + * Error handling function + * @param {Error} error - error to catch + * @returns {void} + */ +/* c8 ignore start */ +const panic = error => { + if (defaults.silentErrors) return + + throw new Error(error) +}; +/* c8 ignore stop */ + +// make sure that the router will always receive strings params +const filterStrings = str => isString(str) ? str : erre.cancel(); + +// create the streaming router +const router = erre(filterStrings).on.error(panic); // cast the values of this stream always to string + +/** + * Merge the user options with the defaults + * @param {Object} options - custom user options + * @returns {Object} options object merged with defaults + */ +const mergeOptions = options => ({...defaults, ...options}); + +/* @type {object} general configuration object */ +const defaults = { + base: 'https://localhost', + silentErrors: false, + // pathToRegexp options + sensitive: false, + strict: false, + end: true, + start: true, + delimiter: '/#?', + encode: undefined, + endsWith: undefined, + prefixes: './' +}; + +/** + * Configure the router options overriding the defaults + * @param {Object} options - custom user options to override + * @returns {Object} new defaults + */ +const configure = (options) => { + Object.entries(options).forEach(([key, value]) => { + if (Object.hasOwn(defaults, key)) defaults[key] = value; + }); + + return defaults +}; + + + +/* {@link https://github.com/pillarjs/path-to-regexp#usage} */ +const toRegexp = (path, keys, options) => pathToRegexp(path, keys, mergeOptions(options)); + +/** + * Convert a router entry to a real path computing the url parameters + * @param {string} path - router path string + * @param {Object} params - named matched parameters + * @param {Object} options - pathToRegexp options object + * @returns {string} computed url string + */ +const toPath = (path, params, options) => compile(path, mergeOptions(options))(params); + +/** + * Parse a string path generating an object containing + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - object containing the base path + * @returns {URL} url object enhanced with the `match` attribute + */ +const toURL = (path, pathRegExp, options = {}) => { + const {base} = mergeOptions(options); + const [, ...params] = pathRegExp.exec(path); + const url = parseURL(path, base); + + // extend the url object adding the matched params + url.params = params.reduce((acc, param, index) => { + const key = options.keys && options.keys[index]; + if (key) acc[key.name] = param ? decodeURIComponent(param) : param; + return acc + }, {}); + + return url +}; + +/** + * Return true if a path will be matched + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @returns {boolean} true if the path matches the regexp + */ +const match = (path, pathRegExp) => pathRegExp.test(path); + +/** + * Factory function to create an sequence of functions to pass to erre.js + * This function will be used in the erre stream + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - pathToRegexp options object + * @returns {Array} a functions array that will be used as stream pipe for erre.js + */ +const createURLStreamPipe = (pathRegExp, options) => [ + decodeURI, + replaceBase, + matchOrSkip(pathRegExp), + path => toURL(path, pathRegExp, options) +]; + +/** + * Create a fork of the main router stream + * @param {string} path - route to match + * @param {Object} options - pathToRegexp options object + * @returns {Stream} new route stream + */ +function createRoute(path, options) { + const keys = []; + const pathRegExp = pathToRegexp(path, keys, options); + const URLStream = erre(...createURLStreamPipe(pathRegExp, { + ...options, + keys + })); + + return joinStreams(router, URLStream).on.error(panic) +} + +var routeHoc = {}; + +var routerHoc = {}; + +const getCurrentRoute = ((currentRoute) => { + // listen the route changes events to store the current route + router.on.value((r) => (currentRoute = r)); + + return () => { + return currentRoute + } +})(null); + +const WINDOW_EVENTS = 'popstate'; +const CLICK_EVENT = 'click'; +const DOWNLOAD_LINK_ATTRIBUTE = 'download'; +const HREF_LINK_ATTRIBUTE = 'href'; +const TARGET_SELF_LINK_ATTRIBUTE = '_self'; +const LINK_TAG_NAME = 'A'; +const HASH = '#'; +const SLASH = '/'; +const RE_ORIGIN = /^.+?\/\/+[^/]+/; + +/** + * Converts any DOM node/s to a loopable array + * @param { HTMLElement|NodeList } els - single html element or a node list + * @returns { Array } always a loopable object + */ +function domToArray(els) { + // can this object be already looped? + if (!Array.isArray(els)) { + // is it a node list? + if ( + /^\[object (HTMLCollection|NodeList|Object)\]$/ + .test(Object.prototype.toString.call(els)) + && typeof els.length === 'number' + ) + return Array.from(els) + else + // if it's a single node + // it will be returned as "array" with one single entry + return [els] + } + // this object could be looped out of the box + return els +} + +/** + * Split a string into several items separed by spaces + * @param { string } l - events list + * @returns { Array } all the events detected + * @private + */ +const split = l => l.split(/\s/); + +/** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|NodeList|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind or unbind space separated + * @param { Function } cb - listeners callback + * @param { string } method - either 'addEventListener' or 'removeEventListener' + * @param { Object } options - event options (capture, once and passive) + * @returns { undefined } + * @private + */ +function manageEvents(els, evList, cb, method, options) { + els = domToArray(els); + + split(evList).forEach((e) => { + els.forEach(el => el[method](e, cb, options || false)); + }); +} + +/** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ +function add(els, evList, cb, options) { + manageEvents(els, evList, cb, 'addEventListener', options); + return els +} + +/** + * Remove all the listeners for the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the events will be unbind + * @param { string } evList - list of events we want unbind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ +function remove(els, evList, cb, options) { + manageEvents(els, evList, cb, 'removeEventListener', options); + return els +} + +const getGlobal = () => getWindow() || global; +const getWindow = () => (typeof window === 'undefined' ? null : window); +const getDocument = () => + typeof document === 'undefined' ? null : document; +const getHistory = () => + typeof history === 'undefined' ? null : history; +const getLocation = () => { + const win = getWindow(); + return win ? win.location : {} +}; + +(() => { + const globalScope = getGlobal(); + + return globalScope.requestAnimationFrame || globalScope.setTimeout +})(); + +(() => { + const globalScope = getGlobal(); + + return globalScope.cancelAnimationFrame || globalScope.clearTimeout +})(); + +/** + * Normalize the return values, in case of a single value we avoid to return an array + * @param { Array } values - list of values we want to return + * @returns { Array|string|boolean } either the whole list of values or the single one found + * @private + */ +const normalize = values => values.length === 1 ? values[0] : values; + +/** + * Parse all the nodes received to get/remove/check their attributes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes + * @param { string } method - method that will be used to parse the attributes + * @returns { Array|string } result of the parsing in a list or a single value + * @private + */ +function parseNodes(els, name, method) { + const names = typeof name === 'string' ? [name] : name; + return normalize(domToArray(els).map(el => { + return normalize(names.map(n => el[method](n))) + })) +} + +/** + * Set any attribute on a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to detect + * @returns { boolean|Array } true or false or an array of boolean values + * @example + * + * import { has } from 'bianco.attr' + * + * has(img, 'width') // false + * + * // or also + * has(img, ['width', 'height']) // => [false, false] + * + * // or also + * has([img1, img2], ['width', 'height']) // => [[false, false], [false, false]] + */ +function has(els, name) { + return parseNodes(els, name, 'hasAttribute') +} + +const onWindowEvent = () => + router.push(normalizePath(String(getLocation().href))); +const onRouterPush = (path) => { + const url = path.includes(defaults.base) ? path : defaults.base + path; + const loc = getLocation(); + const hist = getHistory(); + const doc = getDocument(); + + // update the browser history only if it's necessary + if (hist && url !== loc.href) { + hist.pushState(null, doc.title, url); + } +}; +const getLinkElement = (node) => + node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node; +const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME; +const isCrossOriginLink = (path) => + path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1; +const isTargetSelfLink = (el) => + el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE; +const isEventForbidden = (event) => + (event.which && event.which !== 1) || // not left click + event.metaKey || + event.ctrlKey || + event.shiftKey || // or meta keys + event.defaultPrevented; // or default prevented +const isForbiddenLink = (el) => + !el || + !isLinkNode(el) || // not A tag + has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr + !has(el, HREF_LINK_ATTRIBUTE) || // has no href attr + isTargetSelfLink(el) || + isCrossOriginLink(el.href); +const normalizePath = (path) => path.replace(defaults.base, ''); +const isInBase = (path) => !defaults.base || path.includes(defaults.base); + +/** + * Callback called anytime something will be clicked on the page + * @param {Event} event - click event + * @returns {undefined} void method + */ +const onClick = (event) => { + if (isEventForbidden(event)) return + + const el = getLinkElement(event.target); + + if (isForbiddenLink(el) || !isInBase(el.href)) return + + event.preventDefault(); + + router.push(normalizePath(el.href)); +}; + +/** + * Link the rawth router to the DOM events + * @param { HTMLElement } container - DOM node where the links are located + * @returns {Function} teardown function + */ +function initDomListeners(container) { + const win = getWindow(); + const root = container || getDocument(); + + if (win) { + add(win, WINDOW_EVENTS, onWindowEvent); + add(root, CLICK_EVENT, onClick); + } + + router.on.value(onRouterPush); + + return () => { + if (win) { + remove(win, WINDOW_EVENTS, onWindowEvent); + remove(root, CLICK_EVENT, onClick); + } + + router.off.value(onRouterPush); + } +} + +const normalizeInitialSlash = (str) => + str[0] === SLASH ? str : `${SLASH}${str}`; +const removeTrailingSlash = (str) => + str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str; + +const normalizeBase = (base) => { + const win = getWindow(); + const loc = win.location; + const root = loc ? `${loc.protocol}//${loc.host}` : ''; + const { pathname } = loc ? loc : {}; + + switch (true) { + // pure root url + pathname + case Boolean(base) === false: + return removeTrailingSlash(`${root}${pathname || ''}`) + // full path base + case /(www|http(s)?:)/.test(base): + return base + // hash navigation + case base[0] === HASH: + return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}` + // root url with trailing slash + case base === SLASH: + return removeTrailingSlash(root) + // custom pathname + default: + return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`) + } +}; + +function setBase(base) { + configure({ base: normalizeBase(base) }); +} + +export { routeHoc as Route, routerHoc as Router, configure, createURLStreamPipe, defaults, getCurrentRoute, initDomListeners, match, createRoute as route, router, setBase, toPath, toRegexp, toURL }; diff --git a/node_modules/@riotjs/route/index.standalone.umd.js b/node_modules/@riotjs/route/index.standalone.umd.js new file mode 100644 index 0000000..bb481be --- /dev/null +++ b/node_modules/@riotjs/route/index.standalone.umd.js @@ -0,0 +1,1058 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('riot')) : + typeof define === 'function' && define.amd ? define(['exports', 'riot'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.route = {})); +})(this, (function (exports) { 'use strict'; + + /** + * Tokenize input string. + */ + function lexer(str) { + var tokens = []; + var i = 0; + while (i < str.length) { + var char = str[i]; + if (char === "*" || char === "+" || char === "?") { + tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); + continue; + } + if (char === "\\") { + tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); + continue; + } + if (char === "{") { + tokens.push({ type: "OPEN", index: i, value: str[i++] }); + continue; + } + if (char === "}") { + tokens.push({ type: "CLOSE", index: i, value: str[i++] }); + continue; + } + if (char === ":") { + var name = ""; + var j = i + 1; + while (j < str.length) { + var code = str.charCodeAt(j); + if ( + // `0-9` + (code >= 48 && code <= 57) || + // `A-Z` + (code >= 65 && code <= 90) || + // `a-z` + (code >= 97 && code <= 122) || + // `_` + code === 95) { + name += str[j++]; + continue; + } + break; + } + if (!name) + throw new TypeError("Missing parameter name at ".concat(i)); + tokens.push({ type: "NAME", index: i, value: name }); + i = j; + continue; + } + if (char === "(") { + var count = 1; + var pattern = ""; + var j = i + 1; + if (str[j] === "?") { + throw new TypeError("Pattern cannot start with \"?\" at ".concat(j)); + } + while (j < str.length) { + if (str[j] === "\\") { + pattern += str[j++] + str[j++]; + continue; + } + if (str[j] === ")") { + count--; + if (count === 0) { + j++; + break; + } + } + else if (str[j] === "(") { + count++; + if (str[j + 1] !== "?") { + throw new TypeError("Capturing groups are not allowed at ".concat(j)); + } + } + pattern += str[j++]; + } + if (count) + throw new TypeError("Unbalanced pattern at ".concat(i)); + if (!pattern) + throw new TypeError("Missing pattern at ".concat(i)); + tokens.push({ type: "PATTERN", index: i, value: pattern }); + i = j; + continue; + } + tokens.push({ type: "CHAR", index: i, value: str[i++] }); + } + tokens.push({ type: "END", index: i, value: "" }); + return tokens; + } + /** + * Parse a string for the raw tokens. + */ + function parse(str, options) { + if (options === void 0) { options = {}; } + var tokens = lexer(str); + var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; + var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?"); + var result = []; + var key = 0; + var i = 0; + var path = ""; + var tryConsume = function (type) { + if (i < tokens.length && tokens[i].type === type) + return tokens[i++].value; + }; + var mustConsume = function (type) { + var value = tryConsume(type); + if (value !== undefined) + return value; + var _a = tokens[i], nextType = _a.type, index = _a.index; + throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type)); + }; + var consumeText = function () { + var result = ""; + var value; + while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { + result += value; + } + return result; + }; + while (i < tokens.length) { + var char = tryConsume("CHAR"); + var name = tryConsume("NAME"); + var pattern = tryConsume("PATTERN"); + if (name || pattern) { + var prefix = char || ""; + if (prefixes.indexOf(prefix) === -1) { + path += prefix; + prefix = ""; + } + if (path) { + result.push(path); + path = ""; + } + result.push({ + name: name || key++, + prefix: prefix, + suffix: "", + pattern: pattern || defaultPattern, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + var value = char || tryConsume("ESCAPED_CHAR"); + if (value) { + path += value; + continue; + } + if (path) { + result.push(path); + path = ""; + } + var open = tryConsume("OPEN"); + if (open) { + var prefix = consumeText(); + var name_1 = tryConsume("NAME") || ""; + var pattern_1 = tryConsume("PATTERN") || ""; + var suffix = consumeText(); + mustConsume("CLOSE"); + result.push({ + name: name_1 || (pattern_1 ? key++ : ""), + pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, + prefix: prefix, + suffix: suffix, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + mustConsume("END"); + } + return result; + } + /** + * Compile a string to a template function for the path. + */ + function compile(str, options) { + return tokensToFunction(parse(str, options), options); + } + /** + * Expose a method for transforming tokens into the path function. + */ + function tokensToFunction(tokens, options) { + if (options === void 0) { options = {}; } + var reFlags = flags(options); + var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b; + // Compile all the tokens into regexps. + var matches = tokens.map(function (token) { + if (typeof token === "object") { + return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags); + } + }); + return function (data) { + var path = ""; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token === "string") { + path += token; + continue; + } + var value = data ? data[token.name] : undefined; + var optional = token.modifier === "?" || token.modifier === "*"; + var repeat = token.modifier === "*" || token.modifier === "+"; + if (Array.isArray(value)) { + if (!repeat) { + throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array")); + } + if (value.length === 0) { + if (optional) + continue; + throw new TypeError("Expected \"".concat(token.name, "\" to not be empty")); + } + for (var j = 0; j < value.length; j++) { + var segment = encode(value[j], token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + } + continue; + } + if (typeof value === "string" || typeof value === "number") { + var segment = encode(String(value), token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + continue; + } + if (optional) + continue; + var typeOfMessage = repeat ? "an array" : "a string"; + throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage)); + } + return path; + }; + } + /** + * Escape a regular expression string. + */ + function escapeString(str) { + return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); + } + /** + * Get the flags for a regexp from the options. + */ + function flags(options) { + return options && options.sensitive ? "" : "i"; + } + /** + * Pull out keys from a regexp. + */ + function regexpToRegexp(path, keys) { + if (!keys) + return path; + var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g; + var index = 0; + var execResult = groupsRegex.exec(path.source); + while (execResult) { + keys.push({ + // Use parenthesized substring match if available, index otherwise + name: execResult[1] || index++, + prefix: "", + suffix: "", + modifier: "", + pattern: "", + }); + execResult = groupsRegex.exec(path.source); + } + return path; + } + /** + * Transform an array into a regexp. + */ + function arrayToRegexp(paths, keys, options) { + var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); + return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options)); + } + /** + * Create a path regexp from string input. + */ + function stringToRegexp(path, keys, options) { + return tokensToRegexp(parse(path, options), keys, options); + } + /** + * Expose a function for taking tokens and returning a RegExp. + */ + function tokensToRegexp(tokens, keys, options) { + if (options === void 0) { options = {}; } + var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f; + var endsWithRe = "[".concat(escapeString(endsWith), "]|$"); + var delimiterRe = "[".concat(escapeString(delimiter), "]"); + var route = start ? "^" : ""; + // Iterate over the tokens and create our regexp string. + for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { + var token = tokens_1[_i]; + if (typeof token === "string") { + route += escapeString(encode(token)); + } + else { + var prefix = escapeString(encode(token.prefix)); + var suffix = escapeString(encode(token.suffix)); + if (token.pattern) { + if (keys) + keys.push(token); + if (prefix || suffix) { + if (token.modifier === "+" || token.modifier === "*") { + var mod = token.modifier === "*" ? "?" : ""; + route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod); + } + else { + route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier); + } + } + else { + if (token.modifier === "+" || token.modifier === "*") { + route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")"); + } + else { + route += "(".concat(token.pattern, ")").concat(token.modifier); + } + } + } + else { + route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier); + } + } + } + if (end) { + if (!strict) + route += "".concat(delimiterRe, "?"); + route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")"); + } + else { + var endToken = tokens[tokens.length - 1]; + var isEndDelimited = typeof endToken === "string" + ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 + : endToken === undefined; + if (!strict) { + route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?"); + } + if (!isEndDelimited) { + route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")"); + } + } + return new RegExp(route, flags(options)); + } + /** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ + function pathToRegexp(path, keys, options) { + if (path instanceof RegExp) + return regexpToRegexp(path, keys); + if (Array.isArray(path)) + return arrayToRegexp(path, keys, options); + return stringToRegexp(path, keys, options); + } + + /** + * Cancel token + * @private + * @type { Symbol } + */ + const CANCEL = Symbol(); + + /** + * Helper that can be returned by ruit function to cancel the tasks chain + * @returns { Symbol } internal private constant + * @example + * + * ruit( + * 100, + * num => Math.random() * num + * num => num > 50 ? ruit.cancel() : num + * num => num - 2 + * ).then(result => { + * console.log(result) // here we will get only number lower than 50 + * }) + * + */ + ruit.cancel = () => CANCEL; + + /** + * The same as ruit() but with the arguments inverted from right to left + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from right to left: 1 => 1 + 1 => 2 * 2 + * ruit.compose(squareAsync, addOne, 1).then(result => console.log(result)) // 4 + */ + ruit.compose = (...tasks) => ruit(...tasks.reverse()); + + /** + * Serialize a list of sync and async tasks from left to right + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from left to right: 1 => 1 + 1 => 2 * 2 + * ruit(1, addOne, squareAsync).then(result => console.log(result)) // 4 + */ + function ruit(...tasks) { + return new Promise((resolve, reject) => { + return (function run(queue, result) { + if (!queue.length) return resolve(result) + + const [task, ...rest] = queue; + const value = typeof task === 'function' ? task(result) : task; + const done = v => run(rest, v); + + // check against nil values + if (value != null) { + if (value === CANCEL) return + if (value.then) return value.then(done, reject) + } + + return Promise.resolve(done(value)) + })(tasks) + }) + } + + // Store the erre the API methods to handle the plugins installation + const API_METHODS = new Set(); + const UNSUBSCRIBE_SYMBOL = Symbol(); + const UNSUBSCRIBE_METHOD = 'off'; + const CANCEL_METHOD = 'cancel'; + + /** + * Factory function to create the stream generator + * @private + * @param {Set} modifiers - stream input modifiers + * @returns {Generator} the stream generator + */ + function createStream(modifiers) { + const stream = (function *stream() { + while (true) { + // get the initial stream value + const input = yield; + + // run the input sequence + yield ruit(input, ...modifiers); + } + })(); + + // start the stream + stream.next(); + + return stream + } + + /** + * Dispatch a value to several listeners + * @private + * @param {Set} callbacks - callbacks collection + * @param {*} value - anything + * @returns {Set} the callbacks received + */ + function dispatch(callbacks, value) { + callbacks.forEach(f => { + // unsubscribe the callback if erre.unsubscribe() will be returned + if (f(value) === UNSUBSCRIBE_SYMBOL) callbacks.delete(f); + }); + + return callbacks + } + + /** + * Throw a panic error + * @param {string} message - error message + * @returns {Error} an error object + */ + function panic$1(message) { + throw new Error(message) + } + + /** + * Install an erre plugin adding it to the API + * @param {string} name - plugin name + * @param {Function} fn - new erre API method + * @returns {Function} return the erre function + */ + erre.install = function(name, fn) { + if (!name || typeof name !== 'string') + panic$1('Please provide a name (as string) for your erre plugin'); + if (!fn || typeof fn !== 'function') + panic$1('Please provide a function for your erre plugin'); + + if (API_METHODS.has(name)) { + panic$1(`The ${name} is already part of the erre API, please provide a different name`); + } else { + erre[name] = fn; + API_METHODS.add(name); + } + + return erre + }; + + // alias for ruit canel to stop a stream chain + erre.install(CANCEL_METHOD, ruit.cancel); + + // unsubscribe helper + erre.install(UNSUBSCRIBE_METHOD, () => UNSUBSCRIBE_SYMBOL); + + /** + * Stream constuction function + * @param {...Function} fns - stream modifiers + * @returns {Object} erre instance + */ + function erre(...fns) { + const + [success, error, end, modifiers] = [new Set(), new Set(), new Set(), new Set(fns)], + generator = createStream(modifiers), + stream = Object.create(generator), + addToCollection = (collection) => (fn) => collection.add(fn) && stream, + deleteFromCollection = (collection) => (fn) => collection.delete(fn) ? stream + : panic$1('Couldn\'t remove handler passed by reference'); + + return Object.assign(stream, { + on: Object.freeze({ + value: addToCollection(success), + error: addToCollection(error), + end: addToCollection(end) + }), + off: Object.freeze({ + value: deleteFromCollection(success), + error: deleteFromCollection(error), + end: deleteFromCollection(end) + }), + connect: addToCollection(modifiers), + push(input) { + const { value, done } = stream.next(input); + + // dispatch the stream events + if (!done) { + value.then( + res => dispatch(success, res), + err => dispatch(error, err) + ); + } + + return stream + }, + end() { + // kill the stream + generator.return(); + // dispatch the end event + dispatch(end) + // clean up all the collections + ;[success, error, end, modifiers].forEach(el => el.clear()); + + return stream + }, + fork() { + return erre(...modifiers) + }, + next(input) { + // get the input and run eventually the promise + const result = generator.next(input); + + // pause to the next iteration + generator.next(); + + return result + } + }) + } + + const isString = str => typeof str === 'string'; + const parseURL = (...args) => new URL(...args); + + /** + * Replace the base path from a path + * @param {string} path - router path string + * @returns {string} path cleaned up without the base + */ + const replaceBase = path => path.replace(defaults.base, ''); + + /** + * Try to match the current path or skip it + * @param {RegExp} pathRegExp - target path transformed by pathToRegexp + * @returns {string|Symbol} if the path match we return it otherwise we cancel the stream + */ + const matchOrSkip = pathRegExp => path => match(path, pathRegExp) ? path : erre.cancel(); + + /** + * Combine 2 streams connecting the events of dispatcherStream to the receiverStream + * @param {Stream} dispatcherStream - main stream dispatching events + * @param {Stream} receiverStream - sub stream receiving events from the dispatcher + * @returns {Stream} receiverStream + */ + const joinStreams = (dispatcherStream, receiverStream) => { + dispatcherStream.on.value(receiverStream.push); + + receiverStream.on.end(() => { + dispatcherStream.off.value(receiverStream.push); + }); + + return receiverStream + }; + + /** + * Error handling function + * @param {Error} error - error to catch + * @returns {void} + */ + /* c8 ignore start */ + const panic = error => { + if (defaults.silentErrors) return + + throw new Error(error) + }; + /* c8 ignore stop */ + + // make sure that the router will always receive strings params + const filterStrings = str => isString(str) ? str : erre.cancel(); + + // create the streaming router + const router = erre(filterStrings).on.error(panic); // cast the values of this stream always to string + + /** + * Merge the user options with the defaults + * @param {Object} options - custom user options + * @returns {Object} options object merged with defaults + */ + const mergeOptions = options => ({...defaults, ...options}); + + /* @type {object} general configuration object */ + const defaults = { + base: 'https://localhost', + silentErrors: false, + // pathToRegexp options + sensitive: false, + strict: false, + end: true, + start: true, + delimiter: '/#?', + encode: undefined, + endsWith: undefined, + prefixes: './' + }; + + /** + * Configure the router options overriding the defaults + * @param {Object} options - custom user options to override + * @returns {Object} new defaults + */ + const configure = (options) => { + Object.entries(options).forEach(([key, value]) => { + if (Object.hasOwn(defaults, key)) defaults[key] = value; + }); + + return defaults + }; + + + + /* {@link https://github.com/pillarjs/path-to-regexp#usage} */ + const toRegexp = (path, keys, options) => pathToRegexp(path, keys, mergeOptions(options)); + + /** + * Convert a router entry to a real path computing the url parameters + * @param {string} path - router path string + * @param {Object} params - named matched parameters + * @param {Object} options - pathToRegexp options object + * @returns {string} computed url string + */ + const toPath = (path, params, options) => compile(path, mergeOptions(options))(params); + + /** + * Parse a string path generating an object containing + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - object containing the base path + * @returns {URL} url object enhanced with the `match` attribute + */ + const toURL = (path, pathRegExp, options = {}) => { + const {base} = mergeOptions(options); + const [, ...params] = pathRegExp.exec(path); + const url = parseURL(path, base); + + // extend the url object adding the matched params + url.params = params.reduce((acc, param, index) => { + const key = options.keys && options.keys[index]; + if (key) acc[key.name] = param ? decodeURIComponent(param) : param; + return acc + }, {}); + + return url + }; + + /** + * Return true if a path will be matched + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @returns {boolean} true if the path matches the regexp + */ + const match = (path, pathRegExp) => pathRegExp.test(path); + + /** + * Factory function to create an sequence of functions to pass to erre.js + * This function will be used in the erre stream + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - pathToRegexp options object + * @returns {Array} a functions array that will be used as stream pipe for erre.js + */ + const createURLStreamPipe = (pathRegExp, options) => [ + decodeURI, + replaceBase, + matchOrSkip(pathRegExp), + path => toURL(path, pathRegExp, options) + ]; + + /** + * Create a fork of the main router stream + * @param {string} path - route to match + * @param {Object} options - pathToRegexp options object + * @returns {Stream} new route stream + */ + function createRoute(path, options) { + const keys = []; + const pathRegExp = pathToRegexp(path, keys, options); + const URLStream = erre(...createURLStreamPipe(pathRegExp, { + ...options, + keys + })); + + return joinStreams(router, URLStream).on.error(panic) + } + + var routeHoc = {}; + + var routerHoc = {}; + + const getCurrentRoute = ((currentRoute) => { + // listen the route changes events to store the current route + router.on.value((r) => (currentRoute = r)); + + return () => { + return currentRoute + } + })(null); + + const WINDOW_EVENTS = 'popstate'; + const CLICK_EVENT = 'click'; + const DOWNLOAD_LINK_ATTRIBUTE = 'download'; + const HREF_LINK_ATTRIBUTE = 'href'; + const TARGET_SELF_LINK_ATTRIBUTE = '_self'; + const LINK_TAG_NAME = 'A'; + const HASH = '#'; + const SLASH = '/'; + const RE_ORIGIN = /^.+?\/\/+[^/]+/; + + /** + * Converts any DOM node/s to a loopable array + * @param { HTMLElement|NodeList } els - single html element or a node list + * @returns { Array } always a loopable object + */ + function domToArray(els) { + // can this object be already looped? + if (!Array.isArray(els)) { + // is it a node list? + if ( + /^\[object (HTMLCollection|NodeList|Object)\]$/ + .test(Object.prototype.toString.call(els)) + && typeof els.length === 'number' + ) + return Array.from(els) + else + // if it's a single node + // it will be returned as "array" with one single entry + return [els] + } + // this object could be looped out of the box + return els + } + + /** + * Split a string into several items separed by spaces + * @param { string } l - events list + * @returns { Array } all the events detected + * @private + */ + const split = l => l.split(/\s/); + + /** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|NodeList|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind or unbind space separated + * @param { Function } cb - listeners callback + * @param { string } method - either 'addEventListener' or 'removeEventListener' + * @param { Object } options - event options (capture, once and passive) + * @returns { undefined } + * @private + */ + function manageEvents(els, evList, cb, method, options) { + els = domToArray(els); + + split(evList).forEach((e) => { + els.forEach(el => el[method](e, cb, options || false)); + }); + } + + /** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ + function add(els, evList, cb, options) { + manageEvents(els, evList, cb, 'addEventListener', options); + return els + } + + /** + * Remove all the listeners for the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the events will be unbind + * @param { string } evList - list of events we want unbind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ + function remove(els, evList, cb, options) { + manageEvents(els, evList, cb, 'removeEventListener', options); + return els + } + + const getGlobal = () => getWindow() || global; + const getWindow = () => (typeof window === 'undefined' ? null : window); + const getDocument = () => + typeof document === 'undefined' ? null : document; + const getHistory = () => + typeof history === 'undefined' ? null : history; + const getLocation = () => { + const win = getWindow(); + return win ? win.location : {} + }; + + (() => { + const globalScope = getGlobal(); + + return globalScope.requestAnimationFrame || globalScope.setTimeout + })(); + + (() => { + const globalScope = getGlobal(); + + return globalScope.cancelAnimationFrame || globalScope.clearTimeout + })(); + + /** + * Normalize the return values, in case of a single value we avoid to return an array + * @param { Array } values - list of values we want to return + * @returns { Array|string|boolean } either the whole list of values or the single one found + * @private + */ + const normalize = values => values.length === 1 ? values[0] : values; + + /** + * Parse all the nodes received to get/remove/check their attributes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes + * @param { string } method - method that will be used to parse the attributes + * @returns { Array|string } result of the parsing in a list or a single value + * @private + */ + function parseNodes(els, name, method) { + const names = typeof name === 'string' ? [name] : name; + return normalize(domToArray(els).map(el => { + return normalize(names.map(n => el[method](n))) + })) + } + + /** + * Set any attribute on a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to detect + * @returns { boolean|Array } true or false or an array of boolean values + * @example + * + * import { has } from 'bianco.attr' + * + * has(img, 'width') // false + * + * // or also + * has(img, ['width', 'height']) // => [false, false] + * + * // or also + * has([img1, img2], ['width', 'height']) // => [[false, false], [false, false]] + */ + function has(els, name) { + return parseNodes(els, name, 'hasAttribute') + } + + const onWindowEvent = () => + router.push(normalizePath(String(getLocation().href))); + const onRouterPush = (path) => { + const url = path.includes(defaults.base) ? path : defaults.base + path; + const loc = getLocation(); + const hist = getHistory(); + const doc = getDocument(); + + // update the browser history only if it's necessary + if (hist && url !== loc.href) { + hist.pushState(null, doc.title, url); + } + }; + const getLinkElement = (node) => + node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node; + const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME; + const isCrossOriginLink = (path) => + path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1; + const isTargetSelfLink = (el) => + el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE; + const isEventForbidden = (event) => + (event.which && event.which !== 1) || // not left click + event.metaKey || + event.ctrlKey || + event.shiftKey || // or meta keys + event.defaultPrevented; // or default prevented + const isForbiddenLink = (el) => + !el || + !isLinkNode(el) || // not A tag + has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr + !has(el, HREF_LINK_ATTRIBUTE) || // has no href attr + isTargetSelfLink(el) || + isCrossOriginLink(el.href); + const normalizePath = (path) => path.replace(defaults.base, ''); + const isInBase = (path) => !defaults.base || path.includes(defaults.base); + + /** + * Callback called anytime something will be clicked on the page + * @param {Event} event - click event + * @returns {undefined} void method + */ + const onClick = (event) => { + if (isEventForbidden(event)) return + + const el = getLinkElement(event.target); + + if (isForbiddenLink(el) || !isInBase(el.href)) return + + event.preventDefault(); + + router.push(normalizePath(el.href)); + }; + + /** + * Link the rawth router to the DOM events + * @param { HTMLElement } container - DOM node where the links are located + * @returns {Function} teardown function + */ + function initDomListeners(container) { + const win = getWindow(); + const root = container || getDocument(); + + if (win) { + add(win, WINDOW_EVENTS, onWindowEvent); + add(root, CLICK_EVENT, onClick); + } + + router.on.value(onRouterPush); + + return () => { + if (win) { + remove(win, WINDOW_EVENTS, onWindowEvent); + remove(root, CLICK_EVENT, onClick); + } + + router.off.value(onRouterPush); + } + } + + const normalizeInitialSlash = (str) => + str[0] === SLASH ? str : `${SLASH}${str}`; + const removeTrailingSlash = (str) => + str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str; + + const normalizeBase = (base) => { + const win = getWindow(); + const loc = win.location; + const root = loc ? `${loc.protocol}//${loc.host}` : ''; + const { pathname } = loc ? loc : {}; + + switch (true) { + // pure root url + pathname + case Boolean(base) === false: + return removeTrailingSlash(`${root}${pathname || ''}`) + // full path base + case /(www|http(s)?:)/.test(base): + return base + // hash navigation + case base[0] === HASH: + return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}` + // root url with trailing slash + case base === SLASH: + return removeTrailingSlash(root) + // custom pathname + default: + return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`) + } + }; + + function setBase(base) { + configure({ base: normalizeBase(base) }); + } + + exports.Route = routeHoc; + exports.Router = routerHoc; + exports.configure = configure; + exports.createURLStreamPipe = createURLStreamPipe; + exports.defaults = defaults; + exports.getCurrentRoute = getCurrentRoute; + exports.initDomListeners = initDomListeners; + exports.match = match; + exports.route = createRoute; + exports.router = router; + exports.setBase = setBase; + exports.toPath = toPath; + exports.toRegexp = toRegexp; + exports.toURL = toURL; + +})); diff --git a/node_modules/@riotjs/route/index.umd.js b/node_modules/@riotjs/route/index.umd.js new file mode 100644 index 0000000..9cb9192 --- /dev/null +++ b/node_modules/@riotjs/route/index.umd.js @@ -0,0 +1,1435 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('riot')) : + typeof define === 'function' && define.amd ? define(['exports', 'riot'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.route = {}, global.riot)); +})(this, (function (exports, riot) { 'use strict'; + + /** + * Tokenize input string. + */ + function lexer(str) { + var tokens = []; + var i = 0; + while (i < str.length) { + var char = str[i]; + if (char === "*" || char === "+" || char === "?") { + tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); + continue; + } + if (char === "\\") { + tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); + continue; + } + if (char === "{") { + tokens.push({ type: "OPEN", index: i, value: str[i++] }); + continue; + } + if (char === "}") { + tokens.push({ type: "CLOSE", index: i, value: str[i++] }); + continue; + } + if (char === ":") { + var name = ""; + var j = i + 1; + while (j < str.length) { + var code = str.charCodeAt(j); + if ( + // `0-9` + (code >= 48 && code <= 57) || + // `A-Z` + (code >= 65 && code <= 90) || + // `a-z` + (code >= 97 && code <= 122) || + // `_` + code === 95) { + name += str[j++]; + continue; + } + break; + } + if (!name) + throw new TypeError("Missing parameter name at ".concat(i)); + tokens.push({ type: "NAME", index: i, value: name }); + i = j; + continue; + } + if (char === "(") { + var count = 1; + var pattern = ""; + var j = i + 1; + if (str[j] === "?") { + throw new TypeError("Pattern cannot start with \"?\" at ".concat(j)); + } + while (j < str.length) { + if (str[j] === "\\") { + pattern += str[j++] + str[j++]; + continue; + } + if (str[j] === ")") { + count--; + if (count === 0) { + j++; + break; + } + } + else if (str[j] === "(") { + count++; + if (str[j + 1] !== "?") { + throw new TypeError("Capturing groups are not allowed at ".concat(j)); + } + } + pattern += str[j++]; + } + if (count) + throw new TypeError("Unbalanced pattern at ".concat(i)); + if (!pattern) + throw new TypeError("Missing pattern at ".concat(i)); + tokens.push({ type: "PATTERN", index: i, value: pattern }); + i = j; + continue; + } + tokens.push({ type: "CHAR", index: i, value: str[i++] }); + } + tokens.push({ type: "END", index: i, value: "" }); + return tokens; + } + /** + * Parse a string for the raw tokens. + */ + function parse(str, options) { + if (options === void 0) { options = {}; } + var tokens = lexer(str); + var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; + var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?"); + var result = []; + var key = 0; + var i = 0; + var path = ""; + var tryConsume = function (type) { + if (i < tokens.length && tokens[i].type === type) + return tokens[i++].value; + }; + var mustConsume = function (type) { + var value = tryConsume(type); + if (value !== undefined) + return value; + var _a = tokens[i], nextType = _a.type, index = _a.index; + throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type)); + }; + var consumeText = function () { + var result = ""; + var value; + while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { + result += value; + } + return result; + }; + while (i < tokens.length) { + var char = tryConsume("CHAR"); + var name = tryConsume("NAME"); + var pattern = tryConsume("PATTERN"); + if (name || pattern) { + var prefix = char || ""; + if (prefixes.indexOf(prefix) === -1) { + path += prefix; + prefix = ""; + } + if (path) { + result.push(path); + path = ""; + } + result.push({ + name: name || key++, + prefix: prefix, + suffix: "", + pattern: pattern || defaultPattern, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + var value = char || tryConsume("ESCAPED_CHAR"); + if (value) { + path += value; + continue; + } + if (path) { + result.push(path); + path = ""; + } + var open = tryConsume("OPEN"); + if (open) { + var prefix = consumeText(); + var name_1 = tryConsume("NAME") || ""; + var pattern_1 = tryConsume("PATTERN") || ""; + var suffix = consumeText(); + mustConsume("CLOSE"); + result.push({ + name: name_1 || (pattern_1 ? key++ : ""), + pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, + prefix: prefix, + suffix: suffix, + modifier: tryConsume("MODIFIER") || "", + }); + continue; + } + mustConsume("END"); + } + return result; + } + /** + * Compile a string to a template function for the path. + */ + function compile(str, options) { + return tokensToFunction(parse(str, options), options); + } + /** + * Expose a method for transforming tokens into the path function. + */ + function tokensToFunction(tokens, options) { + if (options === void 0) { options = {}; } + var reFlags = flags(options); + var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b; + // Compile all the tokens into regexps. + var matches = tokens.map(function (token) { + if (typeof token === "object") { + return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags); + } + }); + return function (data) { + var path = ""; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token === "string") { + path += token; + continue; + } + var value = data ? data[token.name] : undefined; + var optional = token.modifier === "?" || token.modifier === "*"; + var repeat = token.modifier === "*" || token.modifier === "+"; + if (Array.isArray(value)) { + if (!repeat) { + throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array")); + } + if (value.length === 0) { + if (optional) + continue; + throw new TypeError("Expected \"".concat(token.name, "\" to not be empty")); + } + for (var j = 0; j < value.length; j++) { + var segment = encode(value[j], token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + } + continue; + } + if (typeof value === "string" || typeof value === "number") { + var segment = encode(String(value), token); + if (validate && !matches[i].test(segment)) { + throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\"")); + } + path += token.prefix + segment + token.suffix; + continue; + } + if (optional) + continue; + var typeOfMessage = repeat ? "an array" : "a string"; + throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage)); + } + return path; + }; + } + /** + * Escape a regular expression string. + */ + function escapeString(str) { + return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); + } + /** + * Get the flags for a regexp from the options. + */ + function flags(options) { + return options && options.sensitive ? "" : "i"; + } + /** + * Pull out keys from a regexp. + */ + function regexpToRegexp(path, keys) { + if (!keys) + return path; + var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g; + var index = 0; + var execResult = groupsRegex.exec(path.source); + while (execResult) { + keys.push({ + // Use parenthesized substring match if available, index otherwise + name: execResult[1] || index++, + prefix: "", + suffix: "", + modifier: "", + pattern: "", + }); + execResult = groupsRegex.exec(path.source); + } + return path; + } + /** + * Transform an array into a regexp. + */ + function arrayToRegexp(paths, keys, options) { + var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); + return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options)); + } + /** + * Create a path regexp from string input. + */ + function stringToRegexp(path, keys, options) { + return tokensToRegexp(parse(path, options), keys, options); + } + /** + * Expose a function for taking tokens and returning a RegExp. + */ + function tokensToRegexp(tokens, keys, options) { + if (options === void 0) { options = {}; } + var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f; + var endsWithRe = "[".concat(escapeString(endsWith), "]|$"); + var delimiterRe = "[".concat(escapeString(delimiter), "]"); + var route = start ? "^" : ""; + // Iterate over the tokens and create our regexp string. + for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { + var token = tokens_1[_i]; + if (typeof token === "string") { + route += escapeString(encode(token)); + } + else { + var prefix = escapeString(encode(token.prefix)); + var suffix = escapeString(encode(token.suffix)); + if (token.pattern) { + if (keys) + keys.push(token); + if (prefix || suffix) { + if (token.modifier === "+" || token.modifier === "*") { + var mod = token.modifier === "*" ? "?" : ""; + route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod); + } + else { + route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier); + } + } + else { + if (token.modifier === "+" || token.modifier === "*") { + route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")"); + } + else { + route += "(".concat(token.pattern, ")").concat(token.modifier); + } + } + } + else { + route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier); + } + } + } + if (end) { + if (!strict) + route += "".concat(delimiterRe, "?"); + route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")"); + } + else { + var endToken = tokens[tokens.length - 1]; + var isEndDelimited = typeof endToken === "string" + ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 + : endToken === undefined; + if (!strict) { + route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?"); + } + if (!isEndDelimited) { + route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")"); + } + } + return new RegExp(route, flags(options)); + } + /** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ + function pathToRegexp(path, keys, options) { + if (path instanceof RegExp) + return regexpToRegexp(path, keys); + if (Array.isArray(path)) + return arrayToRegexp(path, keys, options); + return stringToRegexp(path, keys, options); + } + + /** + * Cancel token + * @private + * @type { Symbol } + */ + const CANCEL = Symbol(); + + /** + * Helper that can be returned by ruit function to cancel the tasks chain + * @returns { Symbol } internal private constant + * @example + * + * ruit( + * 100, + * num => Math.random() * num + * num => num > 50 ? ruit.cancel() : num + * num => num - 2 + * ).then(result => { + * console.log(result) // here we will get only number lower than 50 + * }) + * + */ + ruit.cancel = () => CANCEL; + + /** + * The same as ruit() but with the arguments inverted from right to left + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from right to left: 1 => 1 + 1 => 2 * 2 + * ruit.compose(squareAsync, addOne, 1).then(result => console.log(result)) // 4 + */ + ruit.compose = (...tasks) => ruit(...tasks.reverse()); + + /** + * Serialize a list of sync and async tasks from left to right + * @param { * } tasks - list of tasks to process sequentially + * @returns { Promise } a promise containing the result of the whole chain + * @example + * + * const curry = f => a => b => f(a, b) + * const add = (a, b) => a + b + * + * const addOne = curry(add)(1) + * + * const squareAsync = (num) => { + * return new Promise(r => { + * setTimeout(r, 500, num * 2) + * }) + * } + * + * // a -> a + a -> a * 2 + * // basically from left to right: 1 => 1 + 1 => 2 * 2 + * ruit(1, addOne, squareAsync).then(result => console.log(result)) // 4 + */ + function ruit(...tasks) { + return new Promise((resolve, reject) => { + return (function run(queue, result) { + if (!queue.length) return resolve(result) + + const [task, ...rest] = queue; + const value = typeof task === 'function' ? task(result) : task; + const done = v => run(rest, v); + + // check against nil values + if (value != null) { + if (value === CANCEL) return + if (value.then) return value.then(done, reject) + } + + return Promise.resolve(done(value)) + })(tasks) + }) + } + + // Store the erre the API methods to handle the plugins installation + const API_METHODS = new Set(); + const UNSUBSCRIBE_SYMBOL = Symbol(); + const UNSUBSCRIBE_METHOD = 'off'; + const CANCEL_METHOD = 'cancel'; + + /** + * Factory function to create the stream generator + * @private + * @param {Set} modifiers - stream input modifiers + * @returns {Generator} the stream generator + */ + function createStream(modifiers) { + const stream = (function *stream() { + while (true) { + // get the initial stream value + const input = yield; + + // run the input sequence + yield ruit(input, ...modifiers); + } + })(); + + // start the stream + stream.next(); + + return stream + } + + /** + * Dispatch a value to several listeners + * @private + * @param {Set} callbacks - callbacks collection + * @param {*} value - anything + * @returns {Set} the callbacks received + */ + function dispatch(callbacks, value) { + callbacks.forEach(f => { + // unsubscribe the callback if erre.unsubscribe() will be returned + if (f(value) === UNSUBSCRIBE_SYMBOL) callbacks.delete(f); + }); + + return callbacks + } + + /** + * Throw a panic error + * @param {string} message - error message + * @returns {Error} an error object + */ + function panic$1(message) { + throw new Error(message) + } + + /** + * Install an erre plugin adding it to the API + * @param {string} name - plugin name + * @param {Function} fn - new erre API method + * @returns {Function} return the erre function + */ + erre.install = function(name, fn) { + if (!name || typeof name !== 'string') + panic$1('Please provide a name (as string) for your erre plugin'); + if (!fn || typeof fn !== 'function') + panic$1('Please provide a function for your erre plugin'); + + if (API_METHODS.has(name)) { + panic$1(`The ${name} is already part of the erre API, please provide a different name`); + } else { + erre[name] = fn; + API_METHODS.add(name); + } + + return erre + }; + + // alias for ruit canel to stop a stream chain + erre.install(CANCEL_METHOD, ruit.cancel); + + // unsubscribe helper + erre.install(UNSUBSCRIBE_METHOD, () => UNSUBSCRIBE_SYMBOL); + + /** + * Stream constuction function + * @param {...Function} fns - stream modifiers + * @returns {Object} erre instance + */ + function erre(...fns) { + const + [success, error, end, modifiers] = [new Set(), new Set(), new Set(), new Set(fns)], + generator = createStream(modifiers), + stream = Object.create(generator), + addToCollection = (collection) => (fn) => collection.add(fn) && stream, + deleteFromCollection = (collection) => (fn) => collection.delete(fn) ? stream + : panic$1('Couldn\'t remove handler passed by reference'); + + return Object.assign(stream, { + on: Object.freeze({ + value: addToCollection(success), + error: addToCollection(error), + end: addToCollection(end) + }), + off: Object.freeze({ + value: deleteFromCollection(success), + error: deleteFromCollection(error), + end: deleteFromCollection(end) + }), + connect: addToCollection(modifiers), + push(input) { + const { value, done } = stream.next(input); + + // dispatch the stream events + if (!done) { + value.then( + res => dispatch(success, res), + err => dispatch(error, err) + ); + } + + return stream + }, + end() { + // kill the stream + generator.return(); + // dispatch the end event + dispatch(end) + // clean up all the collections + ;[success, error, end, modifiers].forEach(el => el.clear()); + + return stream + }, + fork() { + return erre(...modifiers) + }, + next(input) { + // get the input and run eventually the promise + const result = generator.next(input); + + // pause to the next iteration + generator.next(); + + return result + } + }) + } + + const isString = str => typeof str === 'string'; + const parseURL = (...args) => new URL(...args); + + /** + * Replace the base path from a path + * @param {string} path - router path string + * @returns {string} path cleaned up without the base + */ + const replaceBase = path => path.replace(defaults.base, ''); + + /** + * Try to match the current path or skip it + * @param {RegExp} pathRegExp - target path transformed by pathToRegexp + * @returns {string|Symbol} if the path match we return it otherwise we cancel the stream + */ + const matchOrSkip = pathRegExp => path => match(path, pathRegExp) ? path : erre.cancel(); + + /** + * Combine 2 streams connecting the events of dispatcherStream to the receiverStream + * @param {Stream} dispatcherStream - main stream dispatching events + * @param {Stream} receiverStream - sub stream receiving events from the dispatcher + * @returns {Stream} receiverStream + */ + const joinStreams = (dispatcherStream, receiverStream) => { + dispatcherStream.on.value(receiverStream.push); + + receiverStream.on.end(() => { + dispatcherStream.off.value(receiverStream.push); + }); + + return receiverStream + }; + + /** + * Error handling function + * @param {Error} error - error to catch + * @returns {void} + */ + /* c8 ignore start */ + const panic$2 = error => { + if (defaults.silentErrors) return + + throw new Error(error) + }; + /* c8 ignore stop */ + + // make sure that the router will always receive strings params + const filterStrings = str => isString(str) ? str : erre.cancel(); + + // create the streaming router + const router = erre(filterStrings).on.error(panic$2); // cast the values of this stream always to string + + /** + * Merge the user options with the defaults + * @param {Object} options - custom user options + * @returns {Object} options object merged with defaults + */ + const mergeOptions = options => ({...defaults, ...options}); + + /* @type {object} general configuration object */ + const defaults = { + base: 'https://localhost', + silentErrors: false, + // pathToRegexp options + sensitive: false, + strict: false, + end: true, + start: true, + delimiter: '/#?', + encode: undefined, + endsWith: undefined, + prefixes: './' + }; + + /** + * Configure the router options overriding the defaults + * @param {Object} options - custom user options to override + * @returns {Object} new defaults + */ + const configure = (options) => { + Object.entries(options).forEach(([key, value]) => { + if (Object.hasOwn(defaults, key)) defaults[key] = value; + }); + + return defaults + }; + + + + /* {@link https://github.com/pillarjs/path-to-regexp#usage} */ + const toRegexp = (path, keys, options) => pathToRegexp(path, keys, mergeOptions(options)); + + /** + * Convert a router entry to a real path computing the url parameters + * @param {string} path - router path string + * @param {Object} params - named matched parameters + * @param {Object} options - pathToRegexp options object + * @returns {string} computed url string + */ + const toPath = (path, params, options) => compile(path, mergeOptions(options))(params); + + /** + * Parse a string path generating an object containing + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - object containing the base path + * @returns {URL} url object enhanced with the `match` attribute + */ + const toURL = (path, pathRegExp, options = {}) => { + const {base} = mergeOptions(options); + const [, ...params] = pathRegExp.exec(path); + const url = parseURL(path, base); + + // extend the url object adding the matched params + url.params = params.reduce((acc, param, index) => { + const key = options.keys && options.keys[index]; + if (key) acc[key.name] = param ? decodeURIComponent(param) : param; + return acc + }, {}); + + return url + }; + + /** + * Return true if a path will be matched + * @param {string} path - target path + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @returns {boolean} true if the path matches the regexp + */ + const match = (path, pathRegExp) => pathRegExp.test(path); + + /** + * Factory function to create an sequence of functions to pass to erre.js + * This function will be used in the erre stream + * @param {RegExp} pathRegExp - path transformed to regexp via pathToRegexp + * @param {Object} options - pathToRegexp options object + * @returns {Array} a functions array that will be used as stream pipe for erre.js + */ + const createURLStreamPipe = (pathRegExp, options) => [ + decodeURI, + replaceBase, + matchOrSkip(pathRegExp), + path => toURL(path, pathRegExp, options) + ]; + + /** + * Create a fork of the main router stream + * @param {string} path - route to match + * @param {Object} options - pathToRegexp options object + * @returns {Stream} new route stream + */ + function createRoute(path, options) { + const keys = []; + const pathRegExp = pathToRegexp(path, keys, options); + const URLStream = erre(...createURLStreamPipe(pathRegExp, { + ...options, + keys + })); + + return joinStreams(router, URLStream).on.error(panic$2) + } + + const WINDOW_EVENTS = 'popstate'; + const CLICK_EVENT = 'click'; + const DOWNLOAD_LINK_ATTRIBUTE = 'download'; + const HREF_LINK_ATTRIBUTE = 'href'; + const TARGET_SELF_LINK_ATTRIBUTE = '_self'; + const LINK_TAG_NAME = 'A'; + const HASH = '#'; + const SLASH = '/'; + const PATH_ATTRIBUTE = 'path'; + const RE_ORIGIN = /^.+?\/\/+[^/]+/; + + /** + * Converts any DOM node/s to a loopable array + * @param { HTMLElement|NodeList } els - single html element or a node list + * @returns { Array } always a loopable object + */ + function domToArray(els) { + // can this object be already looped? + if (!Array.isArray(els)) { + // is it a node list? + if ( + /^\[object (HTMLCollection|NodeList|Object)\]$/ + .test(Object.prototype.toString.call(els)) + && typeof els.length === 'number' + ) + return Array.from(els) + else + // if it's a single node + // it will be returned as "array" with one single entry + return [els] + } + // this object could be looped out of the box + return els + } + + /** + * Simple helper to find DOM nodes returning them as array like loopable object + * @param { string|DOMNodeList } selector - either the query or the DOM nodes to arraify + * @param { HTMLElement } scope - context defining where the query will search for the DOM nodes + * @returns { Array } DOM nodes found as array + */ + function $(selector, scope) { + return domToArray(typeof selector === 'string' ? + (document).querySelectorAll(selector) : + selector + ) + } + + const getCurrentRoute = ((currentRoute) => { + // listen the route changes events to store the current route + router.on.value((r) => (currentRoute = r)); + + return () => { + return currentRoute + } + })(null); + + /** + * Normalize the return values, in case of a single value we avoid to return an array + * @param { Array } values - list of values we want to return + * @returns { Array|string|boolean } either the whole list of values or the single one found + * @private + */ + const normalize = values => values.length === 1 ? values[0] : values; + + /** + * Parse all the nodes received to get/remove/check their attributes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes + * @param { string } method - method that will be used to parse the attributes + * @returns { Array|string } result of the parsing in a list or a single value + * @private + */ + function parseNodes(els, name, method) { + const names = typeof name === 'string' ? [name] : name; + return normalize(domToArray(els).map(el => { + return normalize(names.map(n => el[method](n))) + })) + } + + /** + * Get any attribute from a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to get + * @returns { Array|string } list of the attributes found + * + * @example + * + * import { get } from 'bianco.attr' + * + * const img = document.createElement('img') + * + * get(img, 'width') // => '200' + * + * // or also + * get(img, ['width', 'height']) // => ['200', '300'] + * + * // or also + * get([img1, img2], ['width', 'height']) // => [['200', '300'], ['500', '200']] + */ + function get(els, name) { + return parseNodes(els, name, 'getAttribute') + } + + /** + * Set any attribute on a single or a list of DOM nodes + * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse + * @param { string|Array } name - name or list of attributes to detect + * @returns { boolean|Array } true or false or an array of boolean values + * @example + * + * import { has } from 'bianco.attr' + * + * has(img, 'width') // false + * + * // or also + * has(img, ['width', 'height']) // => [false, false] + * + * // or also + * has([img1, img2], ['width', 'height']) // => [[false, false], [false, false]] + */ + function has(els, name) { + return parseNodes(els, name, 'hasAttribute') + } + + /** + * Convert a string from camel case to dash-case + * @param {string} string - probably a component tag name + * @returns {string} component name normalized + */ + + /** + * Convert a string containing dashes to camel case + * @param {string} string - input string + * @returns {string} my-string -> myString + */ + function dashToCamelCase(string) { + return string.replace(/-(\w)/g, (_, c) => c.toUpperCase()) + } + + /** + * Check if a value is null or undefined + * @param {*} value - anything + * @returns {boolean} true only for the 'undefined' and 'null' types + */ + function isNil(value) { + return value === null || value === undefined + } + + const getGlobal = () => getWindow() || global; + const getWindow = () => (typeof window === 'undefined' ? null : window); + const getDocument = () => + typeof document === 'undefined' ? null : document; + const getHistory = () => + typeof history === 'undefined' ? null : history; + const getLocation = () => { + const win = getWindow(); + return win ? win.location : {} + }; + + const defer = (() => { + const globalScope = getGlobal(); + + return globalScope.requestAnimationFrame || globalScope.setTimeout + })(); + + const cancelDefer = (() => { + const globalScope = getGlobal(); + + return globalScope.cancelAnimationFrame || globalScope.clearTimeout + })(); + + const getAttribute = (attributes, name, context) => { + if (!attributes) return null + + const normalizedAttributes = attributes.flatMap((attr) => + isNil(attr.name) + ? // add support for spread attributes https://github.com/riot/route/issues/178 + Object.entries(attr.evaluate(context)).map(([key, value]) => ({ + // evaluate each value of the spread attribute and store it into the array + name: key, + // create a nested evaluate function pointing to the original value of the spread object + evaluate: () => value, + })) + : attr, + ); + + return normalizedAttributes.find((a) => dashToCamelCase(a.name) === name) + }; + + const createDefaultSlot = (attributes = []) => { + const { template, bindingTypes, expressionTypes } = riot.__.DOMBindings; + + return template(null, [ + { + type: bindingTypes.SLOT, + name: 'default', + attributes: attributes.map((attr) => ({ + ...attr, + type: expressionTypes.ATTRIBUTE, + })), + }, + ]) + }; + + // True if the selector string is valid + const isValidQuerySelectorString = (selector) => + /^([a-zA-Z0-9-_*#.:[\]\s>+~()='"]|\\.)+$/.test(selector); + + /** + * Similar to compose but performs from left-to-right function composition.
+ * {@link https://30secondsofcode.org/function#composeright see also} + * @param {...[function]} fns) - list of unary function + * @returns {*} result of the computation + */ + + /** + * Performs right-to-left function composition.
+ * Use Array.prototype.reduce() to perform right-to-left function composition.
+ * The last (rightmost) function can accept one or more arguments; the remaining functions must be unary.
+ * {@link https://30secondsofcode.org/function#compose original source code} + * @param {...[function]} fns) - list of unary function + * @returns {*} result of the computation + */ + function compose(...fns) { + return fns.reduce((f, g) => (...args) => f(g(...args))) + } + + const getInitialRouteValue = (pathToRegexp, path, options) => { + const route = compose( + ...createURLStreamPipe(pathToRegexp, options).reverse(), + )(path); + + return route.params ? route : null + }; + + const clearDOMBetweenNodes = (first, last, includeBoundaries) => { + const clear = (node) => { + if (!node || (node === last && !includeBoundaries)) return + const { nextSibling } = node; + node.remove(); + clear(nextSibling); + }; + + clear(includeBoundaries ? first : first.nextSibling); + }; + + const routeHoc$1 = ({ slots, attributes }) => { + const placeholders = { + before: document.createTextNode(''), + after: document.createTextNode(''), + }; + + return { + mount(el, context) { + // create the component state + const currentRoute = getCurrentRoute(); + const path = + getAttribute(attributes, PATH_ATTRIBUTE, context)?.evaluate(context) || + get(el, PATH_ATTRIBUTE); + const pathToRegexp = toRegexp(path, []); + const state = { + pathToRegexp, + route: + currentRoute && match(currentRoute, pathToRegexp) + ? getInitialRouteValue(pathToRegexp, currentRoute, {}) + : null, + }; + this.el = el; + this.slot = createDefaultSlot([ + { + isBoolean: false, + name: 'route', + evaluate: () => this.state.route, + }, + ]); + this.context = context; + this.state = state; + // set the route listeners + this.boundOnBeforeRoute = this.onBeforeRoute.bind(this); + this.boundOnRoute = this.onRoute.bind(this); + router.on.value(this.boundOnBeforeRoute); + this.stream = createRoute(path).on.value(this.boundOnRoute); + // update the DOM + el.replaceWith(placeholders.before); + placeholders.before.parentNode.insertBefore( + placeholders.after, + placeholders.before.nextSibling, + ); + if (state.route) this.mountSlot(); + }, + update(context) { + this.context = context; + if (this.state.route) this.slot.update({}, context); + }, + mountSlot() { + const { route } = this.state; + // insert the route root element after the before placeholder + placeholders.before.parentNode.insertBefore( + this.el, + placeholders.before.nextSibling, + ); + this.callLifecycleProperty('onBeforeMount', route); + this.slot.mount( + this.el, + { + slots, + }, + this.context, + ); + this.callLifecycleProperty('onMounted', route); + }, + clearDOM(includeBoundaries) { + // remove all the DOM nodes between the placeholders + clearDOMBetweenNodes( + placeholders.before, + placeholders.after, + includeBoundaries, + ); + }, + unmount() { + router.off.value(this.boundOnBeforeRoute); + this.slot.unmount({}, this.context, true); + this.clearDOM(true); + this.stream.end(); + }, + onBeforeRoute(path) { + const { route } = this.state; + // this component was not mounted or the current path matches + // we don't need to unmount this component + if (!route || match(path, this.state.pathToRegexp)) return + + this.callLifecycleProperty('onBeforeUnmount', route); + this.slot.unmount({}, this.context, true); + this.clearDOM(false); + this.state.route = null; + this.callLifecycleProperty('onUnmounted', route); + }, + onRoute(route) { + const prevRoute = this.state.route; + this.state.route = route; + + // if this route component was already mounted we need to update it + if (prevRoute) { + this.callLifecycleProperty('onBeforeUpdate', route); + this.slot.update({}, this.context); + this.callLifecycleProperty('onUpdated', route); + } + // this route component was never mounted, so we need to create its DOM + else this.mountSlot(); + + // emulate the default browser anchor links behaviour + if (route.hash && isValidQuerySelectorString(route.hash)) + $(route.hash)?.[0].scrollIntoView(); + }, + callLifecycleProperty(method, ...params) { + const attr = getAttribute(attributes, method, this.context); + + if (attr) attr.evaluate(this.context)(...params); + }, + } + }; + + var routeHoc = { + css: null, + + exports: riot.pure( + routeHoc$1 + ), + + template: null, + name: 'route-hoc' + }; + + const normalizeInitialSlash = (str) => + str[0] === SLASH ? str : `${SLASH}${str}`; + const removeTrailingSlash = (str) => + str[str.length - 1] === SLASH ? str.substr(0, str.length - 1) : str; + + const normalizeBase = (base) => { + const win = getWindow(); + const loc = win.location; + const root = loc ? `${loc.protocol}//${loc.host}` : ''; + const { pathname } = loc ? loc : {}; + + switch (true) { + // pure root url + pathname + case Boolean(base) === false: + return removeTrailingSlash(`${root}${pathname || ''}`) + // full path base + case /(www|http(s)?:)/.test(base): + return base + // hash navigation + case base[0] === HASH: + return `${root}${pathname && pathname !== SLASH ? pathname : ''}${base}` + // root url with trailing slash + case base === SLASH: + return removeTrailingSlash(root) + // custom pathname + default: + return removeTrailingSlash(`${root}${normalizeInitialSlash(base)}`) + } + }; + + function setBase(base) { + configure({ base: normalizeBase(base) }); + } + + /** + * Throw an error with a descriptive message + * @param { string } message - error message + * @param { string } cause - optional error cause object + * @returns { undefined } hoppla... at this point the program should stop working + */ + function panic(message, cause) { + throw new Error(message, { cause }) + } + + /** + * Split a string into several items separed by spaces + * @param { string } l - events list + * @returns { Array } all the events detected + * @private + */ + const split = l => l.split(/\s/); + + /** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|NodeList|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind or unbind space separated + * @param { Function } cb - listeners callback + * @param { string } method - either 'addEventListener' or 'removeEventListener' + * @param { Object } options - event options (capture, once and passive) + * @returns { undefined } + * @private + */ + function manageEvents(els, evList, cb, method, options) { + els = domToArray(els); + + split(evList).forEach((e) => { + els.forEach(el => el[method](e, cb, options || false)); + }); + } + + /** + * Set a listener for all the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the listeners will be bound + * @param { string } evList - list of events we want to bind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ + function add(els, evList, cb, options) { + manageEvents(els, evList, cb, 'addEventListener', options); + return els + } + + /** + * Remove all the listeners for the events received separated by spaces + * @param { HTMLElement|Array } els - DOM node/s where the events will be unbind + * @param { string } evList - list of events we want unbind space separated + * @param { Function } cb - listeners callback + * @param { Object } options - event options (capture, once and passive) + * @returns { HTMLElement|NodeList|Array } DOM node/s and first argument of the function + */ + function remove(els, evList, cb, options) { + manageEvents(els, evList, cb, 'removeEventListener', options); + return els + } + + const onWindowEvent = () => + router.push(normalizePath(String(getLocation().href))); + const onRouterPush = (path) => { + const url = path.includes(defaults.base) ? path : defaults.base + path; + const loc = getLocation(); + const hist = getHistory(); + const doc = getDocument(); + + // update the browser history only if it's necessary + if (hist && url !== loc.href) { + hist.pushState(null, doc.title, url); + } + }; + const getLinkElement = (node) => + node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node; + const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME; + const isCrossOriginLink = (path) => + path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1; + const isTargetSelfLink = (el) => + el.target && el.target !== TARGET_SELF_LINK_ATTRIBUTE; + const isEventForbidden = (event) => + (event.which && event.which !== 1) || // not left click + event.metaKey || + event.ctrlKey || + event.shiftKey || // or meta keys + event.defaultPrevented; // or default prevented + const isForbiddenLink = (el) => + !el || + !isLinkNode(el) || // not A tag + has(el, DOWNLOAD_LINK_ATTRIBUTE) || // has download attr + !has(el, HREF_LINK_ATTRIBUTE) || // has no href attr + isTargetSelfLink(el) || + isCrossOriginLink(el.href); + const normalizePath = (path) => path.replace(defaults.base, ''); + const isInBase = (path) => !defaults.base || path.includes(defaults.base); + + /** + * Callback called anytime something will be clicked on the page + * @param {Event} event - click event + * @returns {undefined} void method + */ + const onClick = (event) => { + if (isEventForbidden(event)) return + + const el = getLinkElement(event.target); + + if (isForbiddenLink(el) || !isInBase(el.href)) return + + event.preventDefault(); + + router.push(normalizePath(el.href)); + }; + + /** + * Link the rawth router to the DOM events + * @param { HTMLElement } container - DOM node where the links are located + * @returns {Function} teardown function + */ + function initDomListeners(container) { + const win = getWindow(); + const root = container || getDocument(); + + if (win) { + add(win, WINDOW_EVENTS, onWindowEvent); + add(root, CLICK_EVENT, onClick); + } + + router.on.value(onRouterPush); + + return () => { + if (win) { + remove(win, WINDOW_EVENTS, onWindowEvent); + remove(root, CLICK_EVENT, onClick); + } + + router.off.value(onRouterPush); + } + } + + const BASE_ATTRIBUTE_NAME = 'base'; + const INITIAL_ROUTE = 'initialRoute'; + const ON_STARTED_ATTRIBUTE_NAME = 'onStarted'; + + const routerHoc$1 = ({ slots, attributes, props }) => { + if (routerHoc$1.wasInitialized) + panic('Multiple components are not supported'); + + return { + slot: null, + el: null, + teardown: null, + mount(el, context) { + const initialRouteAttr = getAttribute(attributes, INITIAL_ROUTE, context); + const initialRoute = initialRouteAttr + ? initialRouteAttr.evaluate(context) + : null; + const currentRoute = getCurrentRoute(); + const onFirstRoute = () => { + this.createSlot(context); + router.off.value(onFirstRoute); + }; + routerHoc$1.wasInitialized = true; + + this.el = el; + this.teardown = initDomListeners(this.root); + + this.setBase(context); + + // mount the slots only if the current route was defined + if (currentRoute && !initialRoute) { + this.createSlot(context); + } else { + router.on.value(onFirstRoute); + router.push(initialRoute || window.location.href); + } + }, + createSlot(context) { + if (!slots || !slots.length) return + const onStartedAttr = getAttribute( + attributes, + ON_STARTED_ATTRIBUTE_NAME, + context, + ); + + this.slot = createDefaultSlot(); + + this.slot.mount( + this.el, + { + slots, + }, + context, + ); + + if (onStartedAttr) { + onStartedAttr.evaluate(context)(getCurrentRoute()); + } + }, + update(context) { + this.setBase(context); + + // defer the updates to avoid internal recursive update calls + // see https://github.com/riot/route/issues/148 + if (this.slot) { + cancelDefer(this.deferred); + + this.deferred = defer(() => { + this.slot.update({}, context); + }); + } + }, + unmount(...args) { + this.teardown(); + routerHoc$1.wasInitialized = false; + + if (this.slot) { + this.slot.unmount(...args); + } + }, + getBase(context) { + const baseAttr = getAttribute(attributes, BASE_ATTRIBUTE_NAME, context); + + return baseAttr + ? baseAttr.evaluate(context) + : this.el.getAttribute(BASE_ATTRIBUTE_NAME) || '/' + }, + setBase(context) { + setBase(props ? props.base : this.getBase(context)); + }, + } + }; + + // flag to avoid multiple router instances + routerHoc$1.wasInitialized = false; + + var routerHoc = { + css: null, + + exports: riot.pure( + routerHoc$1 + ), + + template: null, + name: 'router-hoc' + }; + + exports.Route = routeHoc; + exports.Router = routerHoc; + exports.configure = configure; + exports.createURLStreamPipe = createURLStreamPipe; + exports.defaults = defaults; + exports.getCurrentRoute = getCurrentRoute; + exports.initDomListeners = initDomListeners; + exports.match = match; + exports.route = createRoute; + exports.router = router; + exports.setBase = setBase; + exports.toPath = toPath; + exports.toRegexp = toRegexp; + exports.toURL = toURL; + +})); diff --git a/node_modules/@riotjs/route/package.json b/node_modules/@riotjs/route/package.json new file mode 100644 index 0000000..cf8890a --- /dev/null +++ b/node_modules/@riotjs/route/package.json @@ -0,0 +1,92 @@ +{ + "name": "@riotjs/route", + "version": "10.0.0", + "description": "Riot.js isomorphic router", + "type": "module", + "main": "index.umd.js", + "jsnext:main": "index.js", + "module": "index.js", + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.js", + "require": "./index.umd.js", + "browser": "./index.umd.js" + }, + "./standalone": { + "types": "./index.d.ts", + "import": "./index.standalone.js", + "require": "./index.standalone.umd.js", + "browser": "./index.standalone.umd.js" + } + }, + "scripts": { + "prepublishOnly": "npm run build && npm test", + "lint": "eslint src test rollup.config.js && prettier -c .", + "build": "rollup -c && npm run build-demo", + "build-demo": "riot demos/components -o demos/components", + "demo": "npm run build && serve", + "cov": "c8 report --reporter=lcov", + "cov-html": "c8 report --reporter=html", + "test": "npm run lint && c8 mocha -r test/setup.js test/*.spec.js" + }, + "files": [ + "index.d.ts", + "index.js", + "index.umd.js", + "index.standalone.js", + "index.standalone.umd.js" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/riot/route.git" + }, + "keywords": [ + "riot", + "Riot.js", + "router", + "riot-route", + "route" + ], + "author": "Gianluca Guarini (https://gianlucaguarini.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/riot/route/issues" + }, + "homepage": "https://github.com/riot/route#readme", + "devDependencies": { + "@riotjs/cli": "10.0.0", + "@riotjs/compiler": "10.0.0", + "@riotjs/prettier-config": "^1.1.0", + "@riotjs/register": "10.0.0", + "@rollup/plugin-commonjs": "^28.0.6", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-virtual": "^3.0.2", + "c8": "^10.1.3", + "chai": "^6.0.1", + "eslint": "^9.33.0", + "eslint-config-riot": "^5.0.2", + "globals": "^16.3.0", + "jsdom": "26.1.0", + "jsdom-global": "3.0.2", + "mocha": "^11.7.1", + "prettier": "^3.6.2", + "riot": "^10.0.0", + "rollup": "^4.47.1", + "rollup-plugin-riot": "10.0.0", + "serve": "^14.2.4", + "sinon": "^21.0.0", + "sinon-chai": "^4.0.1" + }, + "peerDependency": { + "riot": "^6.0.0 || ^7.0.0 || ^9.0.0 || ^10.0.0" + }, + "dependencies": { + "@riotjs/util": "^10.0.0", + "bianco.attr": "^1.1.1", + "bianco.events": "^1.1.1", + "bianco.query": "^1.1.4", + "cumpa": "^2.0.1", + "rawth": "^3.0.0" + } +} diff --git a/node_modules/@riotjs/util/LICENSE b/node_modules/@riotjs/util/LICENSE new file mode 100644 index 0000000..74e6791 --- /dev/null +++ b/node_modules/@riotjs/util/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Gianluca Guarini + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/@riotjs/util/README.md b/node_modules/@riotjs/util/README.md new file mode 100644 index 0000000..5e1cccb --- /dev/null +++ b/node_modules/@riotjs/util/README.md @@ -0,0 +1,19 @@ +# @riotjs/util + +Riot.js shared util scripts + +[![Build Status][ci-image]][ci-url] +[![Issue Count][qlty-image]][qlty-url] +[![NPM version][npm-version-image]][npm-url] +[![NPM downloads][npm-downloads-image]][npm-url] +[![MIT License][license-image]][license-url] + +[ci-image]: https://img.shields.io/github/actions/workflow/status/riot/util/test.yml?style=flat-square +[ci-url]: https://github.com/riot/util/actions +[license-image]: https://img.shields.io/badge/license-MIT-000000.svg?style=flat-square +[license-url]: LICENSE +[npm-version-image]: https://img.shields.io/npm/v/@riotjs/util.svg?style=flat-square +[npm-downloads-image]: https://img.shields.io/npm/dm/@riotjs/util.svg?style=flat-square +[npm-url]: https://npmjs.org/package/@riotjs/util +[qlty-image]: https://qlty.sh/gh/riot/projects/util/maintainability.svg +[qlty-url]: https://qlty.sh/gh/riot/projects/util diff --git a/node_modules/@riotjs/util/binding-types.cjs b/node_modules/@riotjs/util/binding-types.cjs new file mode 100644 index 0000000..1dc5603 --- /dev/null +++ b/node_modules/@riotjs/util/binding-types.cjs @@ -0,0 +1,24 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +const EACH = 0; +const IF = 1; +const SIMPLE = 2; +const TAG = 3; +const SLOT = 4; + +var bindingTypes = { + EACH, + IF, + SIMPLE, + TAG, + SLOT, +}; + +exports.EACH = EACH; +exports.IF = IF; +exports.SIMPLE = SIMPLE; +exports.SLOT = SLOT; +exports.TAG = TAG; +exports.default = bindingTypes; diff --git a/node_modules/@riotjs/util/binding-types.js b/node_modules/@riotjs/util/binding-types.js new file mode 100644 index 0000000..cc82714 --- /dev/null +++ b/node_modules/@riotjs/util/binding-types.js @@ -0,0 +1,13 @@ +export const EACH = 0 +export const IF = 1 +export const SIMPLE = 2 +export const TAG = 3 +export const SLOT = 4 + +export default { + EACH, + IF, + SIMPLE, + TAG, + SLOT, +} diff --git a/node_modules/@riotjs/util/checks.cjs b/node_modules/@riotjs/util/checks.cjs new file mode 100644 index 0000000..1dc5076 --- /dev/null +++ b/node_modules/@riotjs/util/checks.cjs @@ -0,0 +1,96 @@ +'use strict'; + +var constants = require('./constants.cjs'); + +/** + * Quick type checking + * @param {*} element - anything + * @param {string} type - type definition + * @returns {boolean} true if the type corresponds + */ +function checkType(element, type) { + return typeof element === type +} + +/** + * Check if an element is part of an svg + * @param {HTMLElement} el - element to check + * @returns {boolean} true if we are in an svg context + */ +function isSvg(el) { + const owner = el.ownerSVGElement; + + return !!owner || owner === null +} + +/** + * Check if an element is a template tag + * @param {HTMLElement} el - element to check + * @returns {boolean} true if it's a