diff --git a/package-lock.json b/package-lock.json index e991a9c3..98f98f90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1173,10 +1173,13 @@ } }, "@types/browserslist": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@types/browserslist/-/browserslist-4.8.0.tgz", - "integrity": "sha512-4PyO9OM08APvxxo1NmQyQKlJdowPCOQIy5D/NLO3aO0vGC57wsMptvGp3b8IbYnupFZr92l1dlVief1JvS6STQ==", - "dev": true + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@types/browserslist/-/browserslist-4.15.0.tgz", + "integrity": "sha512-h9LyKErRGZqMsHh9bd+FE8yCIal4S0DxKTOeui56VgVXqa66TKiuaIUxCAI7c1O0LjaUzOTcsMyOpO9GetozRA==", + "dev": true, + "requires": { + "browserslist": "*" + } }, "@types/clean-css": { "version": "4.2.3", @@ -1254,9 +1257,9 @@ } }, "@types/estree": { - "version": "0.0.45", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", - "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", + "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", "dev": true }, "@types/glob": { @@ -1325,9 +1328,9 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/micromatch": { @@ -1352,9 +1355,9 @@ "dev": true }, "@types/node": { - "version": "14.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", - "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==", "dev": true }, "@types/parse-glob": { @@ -1680,6 +1683,12 @@ "@xtuc/long": "4.2.2" } }, + "@webpack-cli/configtest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz", + "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==", + "dev": true + }, "@webpack-cli/info": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", @@ -1690,9 +1699,9 @@ } }, "@webpack-cli/serve": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz", - "integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz", + "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==", "dev": true }, "@xtuc/ieee754": { @@ -1718,9 +1727,9 @@ } }, "acorn": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", - "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==", "dev": true }, "acorn-node": { @@ -1991,13 +2000,13 @@ "dev": true }, "autoprefixer": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.1.tgz", - "integrity": "sha512-dwP0UjyYvROUvtU+boBx8ff5pPWami1NGTrJs9YUsS/oZVbRAcdNHOOuXSA1fc46tgKqe072cVaKD69rvCc3QQ==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.3.tgz", + "integrity": "sha512-vlz+iv+EnLkVaTgX8wApfYzmK3LUfK8Z9XAnmflzxMy/+oFuNK8fVGQV79SOpBv4jxk2YQJimw4hXIKZ29570A==", "dev": true, "requires": { "browserslist": "^4.16.1", - "caniuse-lite": "^1.0.30001173", + "caniuse-lite": "^1.0.30001178", "colorette": "^1.2.1", "fraction.js": "^4.0.13", "normalize-range": "^0.1.2", @@ -2498,9 +2507,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001178", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz", - "integrity": "sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==", + "version": "1.0.30001179", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001179.tgz", + "integrity": "sha512-blMmO0QQujuUWZKyVrD1msR4WNDAqb/UPO1Sw2WWsQ7deoM5bJiicKnWJ1Y0NS/aGINSnKPIWBMw5luX+NDUCA==", "dev": true }, "chalk": { @@ -2963,12 +2972,12 @@ "dev": true }, "core-js-compat": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", - "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", + "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "dev": true, "requires": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.1", "semver": "7.0.0" }, "dependencies": { @@ -3799,9 +3808,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.641", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.641.tgz", - "integrity": "sha512-b0DLhsHSHESC1I+Nx6n4w4Lr61chMd3m/av1rZQhS2IXTzaS5BMM5N+ldWdMIlni9CITMRM09m8He4+YV/92TA==", + "version": "1.3.645", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.645.tgz", + "integrity": "sha512-T7mYop3aDpRHIQaUYcmzmh6j9MAe560n6ukqjJMbVC6bVTau7dSpvB18bcsBPPtOSe10cKxhJFtlbEzLa0LL1g==", "dev": true }, "elliptic": { @@ -3865,9 +3874,9 @@ } }, "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, "envinfo": { @@ -4627,9 +4636,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.0.tgz", + "integrity": "sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -5312,9 +5321,9 @@ "dev": true }, "ip-regex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz", - "integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "dev": true }, "ipaddr.js": { @@ -5771,9 +5780,9 @@ "dev": true }, "laravel-mix": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-6.0.10.tgz", - "integrity": "sha512-wVWpX5I/weqUZDQpVbr3k2pvb1Of2Di/4G5UDIfDiPNOBc4WFp1fvmJV88/H/LZJBncv3VZ+7chzbYMasNzYiw==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-6.0.11.tgz", + "integrity": "sha512-1M5HSQ80p1e1JW64miAZnPW/Er1k2dQDYk036v58xjgVFUn7dbpFh5j1T6sAYK1tTBpRO9344uPtdruw7yjqLA==", "dev": true, "requires": { "@babel/core": "^7.12.3", @@ -6753,9 +6762,9 @@ "dev": true }, "p-retry": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", - "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.3.0.tgz", + "integrity": "sha512-Pow4yaHpOiJou1QcpGcBJhGHiS4782LdDa6GhU91hlaNh3ExOOupjSJcxPQZYmUSZk3Pl2ARz/LRvW8Qu0+3mQ==", "dev": true, "requires": { "@types/retry": "^0.12.0", @@ -7376,16 +7385,16 @@ } }, "postcss-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.1.0.tgz", - "integrity": "sha512-vbCkP70F3Q9PIk6d47aBwjqAMI4LfkXCoyxj+7NPNuVIwfTGdzv2KVQes59/RuxMniIgsYQCFSY42P3+ykJfaw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.2.0.tgz", + "integrity": "sha512-mqgScxHqbiz1yxbnNcPdKYo/6aVt+XExURmEbQlviFVWogDbM4AJ0A/B+ZBpYsJrTRxKw7HyRazg9x0Q9SWwLA==", "dev": true, "requires": { "cosmiconfig": "^7.0.0", "klona": "^2.0.4", "loader-utils": "^2.0.0", "schema-utils": "^3.0.0", - "semver": "^7.3.2" + "semver": "^7.3.4" }, "dependencies": { "cosmiconfig": { @@ -10447,6 +10456,11 @@ } } }, + "vue-router": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.3.tgz", + "integrity": "sha512-AD1OjtVPyQHTSpoRsEGfPpxRQwhAhxcacOYO3zJ3KNkYP/r09mileSp6kdMQKhZWP2cFsPR3E2M3PZguSN5/ww==" + }, "vuex": { "version": "4.0.0-rc.2", "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.0-rc.2.tgz", @@ -10472,13 +10486,13 @@ } }, "webpack": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.15.0.tgz", - "integrity": "sha512-y/xG+ONDz78yn3VvP6gAvGr1/gkxOgitvHSXBmquyN8KDtrGEyE3K9WkXOPB7QmfcOBCpO4ELXwNcCYQnEmexA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.18.0.tgz", + "integrity": "sha512-RmiP/iy6ROvVe/S+u0TrvL/oOmvP+2+Bs8MWjvBwwY/j82Q51XJyDJ75m0QAGntL1Wx6B//Xc0+4VPP/hlNHmw==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.45", + "@types/estree": "^0.0.46", "@webassemblyjs/ast": "1.11.0", "@webassemblyjs/wasm-edit": "1.11.0", "@webassemblyjs/wasm-parser": "1.11.0", @@ -10579,14 +10593,15 @@ } }, "webpack-cli": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz", - "integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz", + "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.0.0", "@webpack-cli/info": "^1.2.1", - "@webpack-cli/serve": "^1.2.1", + "@webpack-cli/serve": "^1.2.2", "colorette": "^1.2.1", "commander": "^6.2.0", "enquirer": "^2.3.6", @@ -10596,18 +10611,7 @@ "interpret": "^2.2.0", "rechoir": "^0.7.0", "v8-compile-cache": "^2.2.0", - "webpack-merge": "^4.2.2" - }, - "dependencies": { - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - } + "webpack-merge": "^5.7.3" } }, "webpack-dev-middleware": { diff --git a/package.json b/package.json index 301e3a3a..2c7b6653 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@vue/compiler-sfc": "^3.0.5", "axios": "^0.21.1", "cross-env": "^7.0.3", - "laravel-mix": "^6.0.10", + "laravel-mix": "^6.0.11", "lodash": "^4.17.20", "postcss": "^8.2.4", "resolve-url-loader": "^3.1.2", @@ -22,6 +22,7 @@ }, "dependencies": { "vue": "^3.0.5", + "vue-router": "^4.0.3", "vuex": "^4.0.0-rc.2" } } diff --git a/public/js/app.js b/public/js/app.js index f7e1c2da..a98e7afd 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -16339,6 +16339,21 @@ __webpack_require__.r(__webpack_exports__); /***/ }), +/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=script&lang=js": +/*!***********************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=script&lang=js ***! + \***********************************************************************************************************************************************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__ +/* harmony export */ }); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({}); + +/***/ }), + /***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Header/Navbar.vue?vue&type=template&id=41f95097&scoped=true": /*!***********************************************************************************************************************************************************************************************************************************************************************************************!*\ !*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Header/Navbar.vue?vue&type=template&id=41f95097&scoped=true ***! @@ -16358,42 +16373,89 @@ var _withId = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.withScopeId)("dat (0,vue__WEBPACK_IMPORTED_MODULE_0__.pushScopeId)("data-v-41f95097"); var _hoisted_1 = { - "class": "text-gray-700 body-font" + role: "banner", + "class": "container flex flex-col justify-center px-0 pt-6 mx-auto mb-6" }; var _hoisted_2 = { - "class": "container flex flex-col flex-wrap items-center p-5 mx-auto md:flex-row" + "class": "flex flex-wrap lg:px-4" +}; + +var _hoisted_3 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", { + "class": "w-9/12 pr-2 my-2 overflow-hidden lg:w-3/12 md:w-10/12" +}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", { + "class": "ml-4 lg:ml-0" +}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("img", { + alt: "Logo", + "class": "h-20 lg:h-24", + "aria-label": "Nettbutikk logo", + src: "https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" +})])], -1 +/* HOISTED */ +); + +var _hoisted_4 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" MobileMenu "); + +var _hoisted_5 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", { + "class": "hidden lg:w-1/12 lg:block" +}, null, -1 +/* HOISTED */ +); + +var _hoisted_6 = { + id: "nav-content", + "class": "hidden w-full mt-4 bg-black lg:w-8/12 lg:block lg:bg-white lg:mt-0 lg:text-right", + "aria-expanded": "false" +}; +var _hoisted_7 = { + "class": "px-6 lg:px-0 lg:pt-5 xl:pt-7" +}; +var _hoisted_8 = { + id: "block-main", + role: "navigation", + "aria-labelledby": "block-main-menu" +}; +var _hoisted_9 = { + "class": "items-center justify-end flex-1 pr-4 -mr-4 list-reset lg:flex" +}; +var _hoisted_10 = { + "class": "inline-block py-2 text-xl font-semibold no-underline lg:text-base lg:px-4" }; -var _hoisted_3 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("a", { - "class": "flex items-center mb-4 font-medium text-gray-900 title-font md:mb-0" -}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("svg", { - xmlns: "http://www.w3.org/2000/svg", - fill: "none", - stroke: "currentColor", - "stroke-linecap": "round", - "stroke-linejoin": "round", - "stroke-width": "2", - "class": "w-10 h-10 p-2 text-white bg-indigo-500 rounded-full", - viewBox: "0 0 24 24" -}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("path", { - d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" -})]), /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("span", { - "class": "ml-3 text-xl" -}, "Shopping Cart")], -1 +var _hoisted_11 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("span", { + "class": "text-xl text-white no-underline lg:text-black is-active" +}, " Home ", -1 /* HOISTED */ ); -var _hoisted_4 = { - "class": "flex flex-wrap items-center justify-center text-base md:mr-auto md:ml-4 md:py-1 md:pl-4 md:border-l md:border-gray-400" +var _hoisted_12 = { + "class": "inline-block py-2 text-xl font-semibold no-underline lg:text-base lg:px-4" }; -var _hoisted_5 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" Products "); +var _hoisted_13 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("span", { + "class": "text-xl text-white no-underline lg:text-black" +}, "Products", -1 +/* HOISTED */ +); -var _hoisted_6 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" Checkout"); +var _hoisted_14 = { + "class": "inline-block py-2 text-xl font-semibold no-underline lg:text-base lg:px-4" +}; -var _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("span", { - "class": "inline-block ml-1" -}, null, -1 +var _hoisted_15 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("span", { + "class": "text-xl text-white no-underline lg:text-black is-active" +}, "Categories", -1 +/* HOISTED */ +); + +var _hoisted_16 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("li", { + "class": "inline-block py-2 text-xl font-semibold no-underline lg:text-base lg:px-4" +}, " Search ", -1 +/* HOISTED */ +); + +var _hoisted_17 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("li", { + "class": "inline-block py-2 text-xl font-semibold no-underline lg:text-base lg:px-4" +}, " Shopping cart ", -1 /* HOISTED */ ); @@ -16402,35 +16464,38 @@ var _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(" var render = /*#__PURE__*/_withId(function (_ctx, _cache, $props, $setup, $data, $options) { var _component_router_link = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("router-link"); - return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)("div", null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("header", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", _hoisted_2, [_hoisted_3, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("nav", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { - "class": "mr-5 hover:text-gray-900", - to: { - name: 'products.index' - } + var _component_router_view = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("router-view"); + + return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("header", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", _hoisted_2, [_hoisted_3, _hoisted_4, _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", _hoisted_7, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("nav", _hoisted_8, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("ul", _hoisted_9, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("li", _hoisted_10, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { + to: "/" }, { "default": _withId(function () { - return [_hoisted_5]; + return [_hoisted_11]; }), _: 1 /* STABLE */ - }, 8 - /* PROPS */ - , ["to"])]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { - "class": "inline-flex items-center px-3 py-1 mt-4 text-base bg-gray-200 border-0 rounded focus:outline-none hover:bg-gray-300 md:mt-0", - to: { - name: 'order.checkout' - } + })]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("li", _hoisted_12, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { + to: "/products" + }, { + "default": _withId(function () { + return [_hoisted_13]; + }), + _: 1 + /* STABLE */ + + })]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("li", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { + to: "/categories" }, { "default": _withId(function () { - return [_hoisted_6, _hoisted_7]; + return [_hoisted_15]; }), _: 1 /* STABLE */ - }, 8 - /* PROPS */ - , ["to"])])])]); + })]), _hoisted_16, _hoisted_17])])])])])])]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_view)], 64 + /* STABLE_FRAGMENT */ + ); }); /***/ }), @@ -16515,6 +16580,23 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { /***/ }), +/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918": +/*!***************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918 ***! + \***************************************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "render": () => /* binding */ render +/* harmony export */ }); +function render(_ctx, _cache, $props, $setup, $data, $options) { + return "Show all products"; +} + +/***/ }), + /***/ "./resources/js/app.js": /*!*****************************!*\ !*** ./resources/js/app.js ***! @@ -16524,18 +16606,37 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.esm-bundler.js"); +/* harmony import */ var vue_router__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! vue-router */ "./node_modules/vue-router/dist/vue-router.esm-bundler.js"); /* harmony import */ var _store_index__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./store/index */ "./resources/js/store/index.js"); /* harmony import */ var _components_MainIndex_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./components/MainIndex.vue */ "./resources/js/components/MainIndex.vue"); +/* harmony import */ var _components_Header_Navbar_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components/Header/Navbar.vue */ "./resources/js/components/Header/Navbar.vue"); +/* harmony import */ var _components_Products_Products_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components/Products/Products.vue */ "./resources/js/components/Products/Products.vue"); __webpack_require__(/*! ./bootstrap */ "./resources/js/bootstrap.js"); + + + +var routes = [{ + path: "/", + component: _components_Header_Navbar_vue__WEBPACK_IMPORTED_MODULE_3__.default +}, { + path: "/products", + component: _components_Products_Products_vue__WEBPACK_IMPORTED_MODULE_4__.default +}]; +var router = (0,vue_router__WEBPACK_IMPORTED_MODULE_5__.createRouter)({ + history: (0,vue_router__WEBPACK_IMPORTED_MODULE_5__.createWebHashHistory)(), + routes: routes +}); (0,vue__WEBPACK_IMPORTED_MODULE_0__.createApp)({ components: { - MainIndex: _components_MainIndex_vue__WEBPACK_IMPORTED_MODULE_2__.default + Navbar: _components_Header_Navbar_vue__WEBPACK_IMPORTED_MODULE_3__.default, + MainIndex: _components_MainIndex_vue__WEBPACK_IMPORTED_MODULE_2__.default, + Products: _components_Products_Products_vue__WEBPACK_IMPORTED_MODULE_4__.default } -}).use(_store_index__WEBPACK_IMPORTED_MODULE_1__.default).mount("#app"); //app.use(store); +}).use(_store_index__WEBPACK_IMPORTED_MODULE_1__.default).use(router).mount("#app"); /***/ }), @@ -16618,7 +16719,7 @@ __webpack_require__.r(__webpack_exports__); var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()(function(i){return i[1]}); // Module -___CSS_LOADER_EXPORT___.push([module.id, "\nheader[data-v-41f95097] {\n max-width: 1400px;\n}\n", ""]); +___CSS_LOADER_EXPORT___.push([module.id, "\n.red[data-v-41f95097] {\n@apply text-8xl;\n}\n", ""]); // Exports /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); @@ -34461,6 +34562,32 @@ _MainIndex_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__.default.__fi /***/ }), +/***/ "./resources/js/components/Products/Products.vue": +/*!*******************************************************!*\ + !*** ./resources/js/components/Products/Products.vue ***! + \*******************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__ +/* harmony export */ }); +/* harmony import */ var _Products_vue_vue_type_template_id_4a553918__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Products.vue?vue&type=template&id=4a553918 */ "./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918"); +/* harmony import */ var _Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Products.vue?vue&type=script&lang=js */ "./resources/js/components/Products/Products.vue?vue&type=script&lang=js"); + + + +_Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__.default.render = _Products_vue_vue_type_template_id_4a553918__WEBPACK_IMPORTED_MODULE_0__.render +/* hot reload */ +if (false) {} + +_Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__.default.__file = "resources/js/components/Products/Products.vue" + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__.default); + +/***/ }), + /***/ "./resources/js/components/Header/Navbar.vue?vue&type=script&lang=js": /*!***************************************************************************!*\ !*** ./resources/js/components/Header/Navbar.vue?vue&type=script&lang=js ***! @@ -34491,6 +34618,22 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_MainIndex_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./MainIndex.vue?vue&type=script&lang=js */ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/MainIndex.vue?vue&type=script&lang=js"); +/***/ }), + +/***/ "./resources/js/components/Products/Products.vue?vue&type=script&lang=js": +/*!*******************************************************************************!*\ + !*** ./resources/js/components/Products/Products.vue?vue&type=script&lang=js ***! + \*******************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__.default +/* harmony export */ }); +/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Products_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!../../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Products.vue?vue&type=script&lang=js */ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=script&lang=js"); + + /***/ }), /***/ "./resources/js/components/Header/Navbar.vue?vue&type=template&id=41f95097&scoped=true": @@ -34539,6 +34682,22 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_MainIndex_vue_vue_type_template_id_12fed784__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!../../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./MainIndex.vue?vue&type=template&id=12fed784 */ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/MainIndex.vue?vue&type=template&id=12fed784"); +/***/ }), + +/***/ "./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918": +/*!*************************************************************************************!*\ + !*** ./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918 ***! + \*************************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "render": () => /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Products_vue_vue_type_template_id_4a553918__WEBPACK_IMPORTED_MODULE_0__.render +/* harmony export */ }); +/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Products_vue_vue_type_template_id_4a553918__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!../../../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!../../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Products.vue?vue&type=template&id=4a553918 */ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Products/Products.vue?vue&type=template&id=4a553918"); + + /***/ }), /***/ "./resources/js/components/Header/Navbar.vue?vue&type=style&index=0&id=41f95097&scoped=true&lang=css": @@ -34552,6 +34711,3319 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _node_modules_style_loader_dist_cjs_js_node_modules_css_loader_dist_cjs_js_clonedRuleSet_9_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_9_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Navbar_vue_vue_type_style_index_0_id_41f95097_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../../node_modules/style-loader/dist/cjs.js!../../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-9.use[1]!../../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-9.use[2]!../../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Navbar.vue?vue&type=style&index=0&id=41f95097&scoped=true&lang=css */ "./node_modules/style-loader/dist/cjs.js!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-9.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-9.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./resources/js/components/Header/Navbar.vue?vue&type=style&index=0&id=41f95097&scoped=true&lang=css"); +/***/ }), + +/***/ "./node_modules/vue-router/dist/vue-router.esm-bundler.js": +/*!****************************************************************!*\ + !*** ./node_modules/vue-router/dist/vue-router.esm-bundler.js ***! + \****************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "NavigationFailureType": () => /* binding */ NavigationFailureType, +/* harmony export */ "RouterLink": () => /* binding */ RouterLink, +/* harmony export */ "RouterView": () => /* binding */ RouterView, +/* harmony export */ "START_LOCATION": () => /* binding */ START_LOCATION_NORMALIZED, +/* harmony export */ "createMemoryHistory": () => /* binding */ createMemoryHistory, +/* harmony export */ "createRouter": () => /* binding */ createRouter, +/* harmony export */ "createRouterMatcher": () => /* binding */ createRouterMatcher, +/* harmony export */ "createWebHashHistory": () => /* binding */ createWebHashHistory, +/* harmony export */ "createWebHistory": () => /* binding */ createWebHistory, +/* harmony export */ "isNavigationFailure": () => /* binding */ isNavigationFailure, +/* harmony export */ "matchedRouteKey": () => /* binding */ matchedRouteKey, +/* harmony export */ "onBeforeRouteLeave": () => /* binding */ onBeforeRouteLeave, +/* harmony export */ "onBeforeRouteUpdate": () => /* binding */ onBeforeRouteUpdate, +/* harmony export */ "parseQuery": () => /* binding */ parseQuery, +/* harmony export */ "routeLocationKey": () => /* binding */ routeLocationKey, +/* harmony export */ "routerKey": () => /* binding */ routerKey, +/* harmony export */ "routerViewLocationKey": () => /* binding */ routerViewLocationKey, +/* harmony export */ "stringifyQuery": () => /* binding */ stringifyQuery, +/* harmony export */ "useLink": () => /* binding */ useLink, +/* harmony export */ "useRoute": () => /* binding */ useRoute, +/* harmony export */ "useRouter": () => /* binding */ useRouter, +/* harmony export */ "viewDepthKey": () => /* binding */ viewDepthKey +/* harmony export */ }); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.esm-bundler.js"); +/*! + * vue-router v4.0.3 + * (c) 2021 Eduardo San Martin Morote + * @license MIT + */ + + +const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; +const PolySymbol = (name) => +// vr = vue router +hasSymbol + ? Symbol(( true) ? '[vue-router]: ' + name : 0) + : (( true) ? '[vue-router]: ' : 0) + name; +// rvlm = Router View Location Matched +/** + * RouteRecord being rendered by the closest ancestor Router View. Used for + * `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View + * Location Matched + * + * @internal + */ +const matchedRouteKey = /*#__PURE__*/ PolySymbol(( true) ? 'router view location matched' : 0); +/** + * Allows overriding the router view depth to control which component in + * `matched` is rendered. rvd stands for Router View Depth + * + * @internal + */ +const viewDepthKey = /*#__PURE__*/ PolySymbol(( true) ? 'router view depth' : 0); +/** + * Allows overriding the router instance returned by `useRouter` in tests. r + * stands for router + * + * @internal + */ +const routerKey = /*#__PURE__*/ PolySymbol(( true) ? 'router' : 0); +/** + * Allows overriding the current route returned by `useRoute` in tests. rl + * stands for route location + * + * @internal + */ +const routeLocationKey = /*#__PURE__*/ PolySymbol(( true) ? 'route location' : 0); +/** + * Allows overriding the current route used by router-view. Internally this is + * used when the `route` prop is passed. + * + * @internal + */ +const routerViewLocationKey = /*#__PURE__*/ PolySymbol(( true) ? 'router view location' : 0); + +const isBrowser = typeof window !== 'undefined'; + +function isESModule(obj) { + return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module'); +} +const assign = Object.assign; +function applyToParams(fn, params) { + const newParams = {}; + for (const key in params) { + const value = params[key]; + newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value); + } + return newParams; +} +let noop = () => { }; + +function warn(msg) { + // avoid using ...args as it breaks in older Edge builds + const args = Array.from(arguments).slice(1); + console.warn.apply(console, ['[Vue Router warn]: ' + msg].concat(args)); +} + +const TRAILING_SLASH_RE = /\/$/; +const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, ''); +/** + * Transforms an URI into a normalized history location + * + * @param parseQuery + * @param location - URI to normalize + * @param currentLocation - current absolute location. Allows resolving relative + * paths. Must start with `/`. Defaults to `/` + * @returns a normalized history location + */ +function parseURL(parseQuery, location, currentLocation = '/') { + let path, query = {}, searchString = '', hash = ''; + // Could use URL and URLSearchParams but IE 11 doesn't support it + const searchPos = location.indexOf('?'); + const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0); + if (searchPos > -1) { + path = location.slice(0, searchPos); + searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length); + query = parseQuery(searchString); + } + if (hashPos > -1) { + path = path || location.slice(0, hashPos); + // keep the # character + hash = location.slice(hashPos, location.length); + } + // no search and no query + path = resolveRelativePath(path != null ? path : location, currentLocation); + // empty path means a relative query or hash `?foo=f`, `#thing` + return { + fullPath: path + (searchString && '?') + searchString + hash, + path, + query, + hash, + }; +} +/** + * Stringifies a URL object + * + * @param stringifyQuery + * @param location + */ +function stringifyURL(stringifyQuery, location) { + let query = location.query ? stringifyQuery(location.query) : ''; + return location.path + (query && '?') + query + (location.hash || ''); +} +/** + * Strips off the base from the beginning of a location.pathname in a non + * case-sensitive way. + * + * @param pathname - location.pathname + * @param base - base to strip off + */ +function stripBase(pathname, base) { + // no base or base is not found at the beginning + if (!base || pathname.toLowerCase().indexOf(base.toLowerCase())) + return pathname; + return pathname.slice(base.length) || '/'; +} +/** + * Checks if two RouteLocation are equal. This means that both locations are + * pointing towards the same {@link RouteRecord} and that all `params`, `query` + * parameters and `hash` are the same + * + * @param a - first {@link RouteLocation} + * @param b - second {@link RouteLocation} + */ +function isSameRouteLocation(stringifyQuery, a, b) { + let aLastIndex = a.matched.length - 1; + let bLastIndex = b.matched.length - 1; + return (aLastIndex > -1 && + aLastIndex === bLastIndex && + isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) && + isSameRouteLocationParams(a.params, b.params) && + stringifyQuery(a.query) === stringifyQuery(b.query) && + a.hash === b.hash); +} +/** + * Check if two `RouteRecords` are equal. Takes into account aliases: they are + * considered equal to the `RouteRecord` they are aliasing. + * + * @param a - first {@link RouteRecord} + * @param b - second {@link RouteRecord} + */ +function isSameRouteRecord(a, b) { + // since the original record has an undefined value for aliasOf + // but all aliases point to the original record, this will always compare + // the original record + return (a.aliasOf || a) === (b.aliasOf || b); +} +function isSameRouteLocationParams(a, b) { + if (Object.keys(a).length !== Object.keys(b).length) + return false; + for (let key in a) { + if (!isSameRouteLocationParamsValue(a[key], b[key])) + return false; + } + return true; +} +function isSameRouteLocationParamsValue(a, b) { + return Array.isArray(a) + ? isEquivalentArray(a, b) + : Array.isArray(b) + ? isEquivalentArray(b, a) + : a === b; +} +/** + * Check if two arrays are the same or if an array with one single entry is the + * same as another primitive value. Used to check query and parameters + * + * @param a - array of values + * @param b - array of values or a single value + */ +function isEquivalentArray(a, b) { + return Array.isArray(b) + ? a.length === b.length && a.every((value, i) => value === b[i]) + : a.length === 1 && a[0] === b; +} +/** + * Resolves a relative path that starts with `.`. + * + * @param to - path location we are resolving + * @param from - currentLocation.path, should start with `/` + */ +function resolveRelativePath(to, from) { + if (to.startsWith('/')) + return to; + if (( true) && !from.startsWith('/')) { + warn(`Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`); + return to; + } + if (!to) + return from; + const fromSegments = from.split('/'); + const toSegments = to.split('/'); + let position = fromSegments.length - 1; + let toPosition; + let segment; + for (toPosition = 0; toPosition < toSegments.length; toPosition++) { + segment = toSegments[toPosition]; + // can't go below zero + if (position === 1 || segment === '.') + continue; + if (segment === '..') + position--; + // found something that is not relative path + else + break; + } + return (fromSegments.slice(0, position).join('/') + + '/' + + toSegments + .slice(toPosition - (toPosition === toSegments.length ? 1 : 0)) + .join('/')); +} + +var NavigationType; +(function (NavigationType) { + NavigationType["pop"] = "pop"; + NavigationType["push"] = "push"; +})(NavigationType || (NavigationType = {})); +var NavigationDirection; +(function (NavigationDirection) { + NavigationDirection["back"] = "back"; + NavigationDirection["forward"] = "forward"; + NavigationDirection["unknown"] = ""; +})(NavigationDirection || (NavigationDirection = {})); +/** + * Starting location for Histories + */ +const START = ''; +// Generic utils +/** + * Normalizes a base by removing any trailing slash and reading the base tag if + * present. + * + * @param base - base to normalize + */ +function normalizeBase(base) { + if (!base) { + if (isBrowser) { + // respect tag + const baseEl = document.querySelector('base'); + base = (baseEl && baseEl.getAttribute('href')) || '/'; + // strip full URL origin + base = base.replace(/^\w+:\/\/[^\/]+/, ''); + } + else { + base = '/'; + } + } + // ensure leading slash when it was removed by the regex above avoid leading + // slash with hash because the file could be read from the disk like file:// + // and the leading slash would cause problems + if (base[0] !== '/' && base[0] !== '#') + base = '/' + base; + // remove the trailing slash so all other method can just do `base + fullPath` + // to build an href + return removeTrailingSlash(base); +} +// remove any character before the hash +const BEFORE_HASH_RE = /^[^#]+#/; +function createHref(base, location) { + return base.replace(BEFORE_HASH_RE, '#') + location; +} + +function getElementPosition(el, offset) { + const docRect = document.documentElement.getBoundingClientRect(); + const elRect = el.getBoundingClientRect(); + return { + behavior: offset.behavior, + left: elRect.left - docRect.left - (offset.left || 0), + top: elRect.top - docRect.top - (offset.top || 0), + }; +} +const computeScrollPosition = () => ({ + left: window.pageXOffset, + top: window.pageYOffset, +}); +function scrollToPosition(position) { + let scrollToOptions; + if ('el' in position) { + let positionEl = position.el; + const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#'); + /** + * `id`s can accept pretty much any characters, including CSS combinators + * like `>` or `~`. It's still possible to retrieve elements using + * `document.getElementById('~')` but it needs to be escaped when using + * `document.querySelector('#\\~')` for it to be valid. The only + * requirements for `id`s are them to be unique on the page and to not be + * empty (`id=""`). Because of that, when passing an id selector, it should + * be properly escaped for it to work with `querySelector`. We could check + * for the id selector to be simple (no CSS combinators `+ >~`) but that + * would make things inconsistent since they are valid characters for an + * `id` but would need to be escaped when using `querySelector`, breaking + * their usage and ending up in no selector returned. Selectors need to be + * escaped: + * + * - `#1-thing` becomes `#\31 -thing` + * - `#with~symbols` becomes `#with\\~symbols` + * + * - More information about the topic can be found at + * https://mathiasbynens.be/notes/html5-id-class. + * - Practical example: https://mathiasbynens.be/demo/html5-id + */ + if (( true) && typeof position.el === 'string') { + if (!isIdSelector || !document.getElementById(position.el.slice(1))) { + try { + let foundEl = document.querySelector(position.el); + if (isIdSelector && foundEl) { + warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`); + // return to avoid other warnings + return; + } + } + catch (err) { + warn(`The selector "${position.el}" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).`); + // return to avoid other warnings + return; + } + } + } + const el = typeof positionEl === 'string' + ? isIdSelector + ? document.getElementById(positionEl.slice(1)) + : document.querySelector(positionEl) + : positionEl; + if (!el) { + ( true) && + warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`); + return; + } + scrollToOptions = getElementPosition(el, position); + } + else { + scrollToOptions = position; + } + if ('scrollBehavior' in document.documentElement.style) + window.scrollTo(scrollToOptions); + else { + window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.pageXOffset, scrollToOptions.top != null ? scrollToOptions.top : window.pageYOffset); + } +} +function getScrollKey(path, delta) { + const position = history.state ? history.state.position - delta : -1; + return position + path; +} +const scrollPositions = new Map(); +function saveScrollPosition(key, scrollPosition) { + scrollPositions.set(key, scrollPosition); +} +function getSavedScrollPosition(key) { + const scroll = scrollPositions.get(key); + // consume it so it's not used again + scrollPositions.delete(key); + return scroll; +} +// TODO: RFC about how to save scroll position +/** + * ScrollBehavior instance used by the router to compute and restore the scroll + * position when navigating. + */ +// export interface ScrollHandler { +// // returns a scroll position that can be saved in history +// compute(): ScrollPositionEntry +// // can take an extended ScrollPositionEntry +// scroll(position: ScrollPosition): void +// } +// export const scrollHandler: ScrollHandler = { +// compute: computeScroll, +// scroll: scrollToPosition, +// } + +let createBaseLocation = () => location.protocol + '//' + location.host; +/** + * Creates a normalized history location from a window.location object + * @param location - + */ +function createCurrentLocation(base, location) { + const { pathname, search, hash } = location; + // allows hash based url + const hashPos = base.indexOf('#'); + if (hashPos > -1) { + // prepend the starting slash to hash so the url starts with /# + let pathFromHash = hash.slice(1); + if (pathFromHash[0] !== '/') + pathFromHash = '/' + pathFromHash; + return stripBase(pathFromHash, ''); + } + const path = stripBase(pathname, base); + return path + search + hash; +} +function useHistoryListeners(base, historyState, currentLocation, replace) { + let listeners = []; + let teardowns = []; + // TODO: should it be a stack? a Dict. Check if the popstate listener + // can trigger twice + let pauseState = null; + const popStateHandler = ({ state, }) => { + const to = createCurrentLocation(base, location); + const from = currentLocation.value; + const fromState = historyState.value; + let delta = 0; + if (state) { + currentLocation.value = to; + historyState.value = state; + // ignore the popstate and reset the pauseState + if (pauseState && pauseState === from) { + pauseState = null; + return; + } + delta = fromState ? state.position - fromState.position : 0; + } + else { + replace(to); + } + // console.log({ deltaFromCurrent }) + // Here we could also revert the navigation by calling history.go(-delta) + // this listener will have to be adapted to not trigger again and to wait for the url + // to be updated before triggering the listeners. Some kind of validation function would also + // need to be passed to the listeners so the navigation can be accepted + // call all listeners + listeners.forEach(listener => { + listener(currentLocation.value, from, { + delta, + type: NavigationType.pop, + direction: delta + ? delta > 0 + ? NavigationDirection.forward + : NavigationDirection.back + : NavigationDirection.unknown, + }); + }); + }; + function pauseListeners() { + pauseState = currentLocation.value; + } + function listen(callback) { + // setup the listener and prepare teardown callbacks + listeners.push(callback); + const teardown = () => { + const index = listeners.indexOf(callback); + if (index > -1) + listeners.splice(index, 1); + }; + teardowns.push(teardown); + return teardown; + } + function beforeUnloadListener() { + const { history } = window; + if (!history.state) + return; + history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), ''); + } + function destroy() { + for (const teardown of teardowns) + teardown(); + teardowns = []; + window.removeEventListener('popstate', popStateHandler); + window.removeEventListener('beforeunload', beforeUnloadListener); + } + // setup the listeners and prepare teardown callbacks + window.addEventListener('popstate', popStateHandler); + window.addEventListener('beforeunload', beforeUnloadListener); + return { + pauseListeners, + listen, + destroy, + }; +} +/** + * Creates a state object + */ +function buildState(back, current, forward, replaced = false, computeScroll = false) { + return { + back, + current, + forward, + replaced, + position: window.history.length, + scroll: computeScroll ? computeScrollPosition() : null, + }; +} +function useHistoryStateNavigation(base) { + const { history, location } = window; + // private variables + let currentLocation = { + value: createCurrentLocation(base, location), + }; + let historyState = { value: history.state }; + // build current history entry as this is a fresh navigation + if (!historyState.value) { + changeLocation(currentLocation.value, { + back: null, + current: currentLocation.value, + forward: null, + // the length is off by one, we need to decrease it + position: history.length - 1, + replaced: true, + // don't add a scroll as the user may have an anchor and we want + // scrollBehavior to be triggered without a saved position + scroll: null, + }, true); + } + function changeLocation(to, state, replace) { + /** + * if a base tag is provided and we are on a normal domain, we have to + * respect the provided `base` attribute because pushState() will use it and + * potentially erase anything before the `#` like at + * https://github.com/vuejs/vue-router-next/issues/685 where a base of + * `/folder/#` but a base of `/` would erase the `/folder/` section. If + * there is no host, the `` tag makes no sense and if there isn't a + * base tag we can just use everything after the `#`. + */ + const hashIndex = base.indexOf('#'); + const url = hashIndex > -1 + ? (location.host && document.querySelector('base') + ? base + : base.slice(hashIndex)) + to + : createBaseLocation() + base + to; + try { + // BROWSER QUIRK + // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds + history[replace ? 'replaceState' : 'pushState'](state, '', url); + historyState.value = state; + } + catch (err) { + if ((true)) { + warn('Error with push/replace State', err); + } + else {} + // Force the navigation, this also resets the call count + location[replace ? 'replace' : 'assign'](url); + } + } + function replace(to, data) { + const state = assign({}, history.state, buildState(historyState.value.back, + // keep back and forward entries but override current position + to, historyState.value.forward, true), data, { position: historyState.value.position }); + changeLocation(to, state, true); + currentLocation.value = to; + } + function push(to, data) { + // Add to current entry the information of where we are going + // as well as saving the current position + const currentState = assign({}, + // use current history state to gracefully handle a wrong call to + // history.replaceState + // https://github.com/vuejs/vue-router-next/issues/366 + historyState.value, history.state, { + forward: to, + scroll: computeScrollPosition(), + }); + if (( true) && !history.state) { + warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` + + `history.replaceState(history.state, '', url)\n\n` + + `You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`); + } + changeLocation(currentState.current, currentState, true); + const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data); + changeLocation(to, state, false); + currentLocation.value = to; + } + return { + location: currentLocation, + state: historyState, + push, + replace, + }; +} +/** + * Creates an HTML5 history. Most common history for single page applications. + * + * @param base - + */ +function createWebHistory(base) { + base = normalizeBase(base); + const historyNavigation = useHistoryStateNavigation(base); + const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace); + function go(delta, triggerListeners = true) { + if (!triggerListeners) + historyListeners.pauseListeners(); + history.go(delta); + } + const routerHistory = assign({ + // it's overridden right after + location: '', + base, + go, + createHref: createHref.bind(null, base), + }, historyNavigation, historyListeners); + Object.defineProperty(routerHistory, 'location', { + get: () => historyNavigation.location.value, + }); + Object.defineProperty(routerHistory, 'state', { + get: () => historyNavigation.state.value, + }); + return routerHistory; +} + +/** + * Creates a in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere. + * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`. + * + * @param base - Base applied to all urls, defaults to '/' + * @returns a history object that can be passed to the router constructor + */ +function createMemoryHistory(base = '') { + let listeners = []; + let queue = [START]; + let position = 0; + function setLocation(location) { + position++; + if (position === queue.length) { + // we are at the end, we can simply append a new entry + queue.push(location); + } + else { + // we are in the middle, we remove everything from here in the queue + queue.splice(position); + queue.push(location); + } + } + function triggerListeners(to, from, { direction, delta }) { + const info = { + direction, + delta, + type: NavigationType.pop, + }; + for (let callback of listeners) { + callback(to, from, info); + } + } + const routerHistory = { + // rewritten by Object.defineProperty + location: START, + state: {}, + base, + createHref: createHref.bind(null, base), + replace(to) { + // remove current entry and decrement position + queue.splice(position--, 1); + setLocation(to); + }, + push(to, data) { + setLocation(to); + }, + listen(callback) { + listeners.push(callback); + return () => { + const index = listeners.indexOf(callback); + if (index > -1) + listeners.splice(index, 1); + }; + }, + destroy() { + listeners = []; + }, + go(delta, shouldTrigger = true) { + const from = this.location; + const direction = + // we are considering delta === 0 going forward, but in abstract mode + // using 0 for the delta doesn't make sense like it does in html5 where + // it reloads the page + delta < 0 ? NavigationDirection.back : NavigationDirection.forward; + position = Math.max(0, Math.min(position + delta, queue.length - 1)); + if (shouldTrigger) { + triggerListeners(this.location, from, { + direction, + delta, + }); + } + }, + }; + Object.defineProperty(routerHistory, 'location', { + get: () => queue[position], + }); + return routerHistory; +} + +/** + * Creates a hash history. Useful for web applications with no host (e.g. + * `file://`) or when configuring a server to handle any URL is not possible. + * + * @param base - optional base to provide. Defaults to `location.pathname + + * location.search` If there is a `` tag in the `head`, its value will be + * ignored in favor of this parameter **but note it affects all the + * history.pushState() calls**, meaning that if you use a `` tag, it's + * `href` value **has to match this parameter** (ignoring anything after the + * `#`). + * + * @example + * ```js + * // at https://example.com/folder + * createWebHashHistory() // gives a url of `https://example.com/folder#` + * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#` + * // if the `#` is provided in the base, it won't be added by `createWebHashHistory` + * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/` + * // you should avoid doing this because it changes the original url and breaks copying urls + * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#` + * + * // at file:///usr/etc/folder/index.html + * // for locations with no `host`, the base is ignored + * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#` + * ``` + */ +function createWebHashHistory(base) { + // Make sure this implementation is fine in terms of encoding, specially for IE11 + // for `file://`, directly use the pathname and ignore the base + // location.pathname contains an initial `/` even at the root: `https://example.com` + base = location.host ? base || location.pathname + location.search : ''; + // allow the user to provide a `#` in the middle: `/base/#/app` + if (base.indexOf('#') < 0) + base += '#'; + if (( true) && !base.endsWith('#/') && !base.endsWith('#')) { + warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, '#')}".`); + } + return createWebHistory(base); +} + +function isRouteLocation(route) { + return typeof route === 'string' || (route && typeof route === 'object'); +} +function isRouteName(name) { + return typeof name === 'string' || typeof name === 'symbol'; +} + +/** + * Initial route location where the router is. Can be used in navigation guards + * to differentiate the initial navigation. + * + * @example + * ```js + * import { START_LOCATION } from 'vue-router' + * + * router.beforeEach((to, from) => { + * if (from === START_LOCATION) { + * // initial navigation + * } + * }) + * ``` + */ +const START_LOCATION_NORMALIZED = { + path: '/', + name: undefined, + params: {}, + query: {}, + hash: '', + fullPath: '/', + matched: [], + meta: {}, + redirectedFrom: undefined, +}; + +const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol(( true) ? 'navigation failure' : 0); +/** + * Enumeration with all possible types for navigation failures. Can be passed to + * {@link isNavigationFailure} to check for specific failures. + */ +var NavigationFailureType; +(function (NavigationFailureType) { + /** + * An aborted navigation is a navigation that failed because a navigation + * guard returned `false` or called `next(false)` + */ + NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted"; + /** + * A cancelled navigation is a navigation that failed because a more recent + * navigation finished started (not necessarily finished). + */ + NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled"; + /** + * A duplicated navigation is a navigation that failed because it was + * initiated while already being at the exact same location. + */ + NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated"; +})(NavigationFailureType || (NavigationFailureType = {})); +// DEV only debug messages +const ErrorTypeMessages = { + [1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) { + return `No match for\n ${JSON.stringify(location)}${currentLocation + ? '\nwhile being at\n' + JSON.stringify(currentLocation) + : ''}`; + }, + [2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) { + return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`; + }, + [4 /* NAVIGATION_ABORTED */]({ from, to }) { + return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`; + }, + [8 /* NAVIGATION_CANCELLED */]({ from, to }) { + return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`; + }, + [16 /* NAVIGATION_DUPLICATED */]({ from, to }) { + return `Avoided redundant navigation to current location: "${from.fullPath}".`; + }, +}; +function createRouterError(type, params) { + if (true) { + return assign(new Error(ErrorTypeMessages[type](params)), { + type, + [NavigationFailureSymbol]: true, + }, params); + } + else {} +} +function isNavigationFailure(error, type) { + return (error instanceof Error && + NavigationFailureSymbol in error && + (type == null || !!(error.type & type))); +} +const propertiesToLog = ['params', 'query', 'hash']; +function stringifyRoute(to) { + if (typeof to === 'string') + return to; + if ('path' in to) + return to.path; + const location = {}; + for (const key of propertiesToLog) { + if (key in to) + location[key] = to[key]; + } + return JSON.stringify(location, null, 2); +} + +// default pattern for a param: non greedy everything but / +const BASE_PARAM_PATTERN = '[^/]+?'; +const BASE_PATH_PARSER_OPTIONS = { + sensitive: false, + strict: false, + start: true, + end: true, +}; +// Special Regex characters that must be escaped in static tokens +const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g; +/** + * Creates a path parser from an array of Segments (a segment is an array of Tokens) + * + * @param segments - array of segments returned by tokenizePath + * @param extraOptions - optional options for the regexp + * @returns a PathParser + */ +function tokensToParser(segments, extraOptions) { + const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions); + // the amount of scores is the same as the length of segments except for the root segment "/" + let score = []; + // the regexp as a string + let pattern = options.start ? '^' : ''; + // extracted keys + const keys = []; + for (const segment of segments) { + // the root segment needs special treatment + const segmentScores = segment.length ? [] : [90 /* Root */]; + // allow trailing slash + if (options.strict && !segment.length) + pattern += '/'; + for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) { + const token = segment[tokenIndex]; + // resets the score if we are inside a sub segment /:a-other-:b + let subSegmentScore = 40 /* Segment */ + + (options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0); + if (token.type === 0 /* Static */) { + // prepend the slash if we are starting a new segment + if (!tokenIndex) + pattern += '/'; + pattern += token.value.replace(REGEX_CHARS_RE, '\\$&'); + subSegmentScore += 40 /* Static */; + } + else if (token.type === 1 /* Param */) { + const { value, repeatable, optional, regexp } = token; + keys.push({ + name: value, + repeatable, + optional, + }); + const re = regexp ? regexp : BASE_PARAM_PATTERN; + // the user provided a custom regexp /:id(\\d+) + if (re !== BASE_PARAM_PATTERN) { + subSegmentScore += 10 /* BonusCustomRegExp */; + // make sure the regexp is valid before using it + try { + new RegExp(`(${re})`); + } + catch (err) { + throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` + + err.message); + } + } + // when we repeat we must take care of the repeating leading slash + let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`; + // prepend the slash if we are starting a new segment + if (!tokenIndex) + subPattern = optional ? `(?:/${subPattern})` : '/' + subPattern; + if (optional) + subPattern += '?'; + pattern += subPattern; + subSegmentScore += 20 /* Dynamic */; + if (optional) + subSegmentScore += -8 /* BonusOptional */; + if (repeatable) + subSegmentScore += -20 /* BonusRepeatable */; + if (re === '.*') + subSegmentScore += -50 /* BonusWildcard */; + } + segmentScores.push(subSegmentScore); + } + // an empty array like /home/ -> [[{home}], []] + // if (!segment.length) pattern += '/' + score.push(segmentScores); + } + // only apply the strict bonus to the last score + if (options.strict && options.end) { + const i = score.length - 1; + score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */; + } + // TODO: dev only warn double trailing slash + if (!options.strict) + pattern += '/?'; + if (options.end) + pattern += '$'; + // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else + else if (options.strict) + pattern += '(?:/|$)'; + const re = new RegExp(pattern, options.sensitive ? '' : 'i'); + function parse(path) { + const match = path.match(re); + const params = {}; + if (!match) + return null; + for (let i = 1; i < match.length; i++) { + const value = match[i] || ''; + const key = keys[i - 1]; + params[key.name] = value && key.repeatable ? value.split('/') : value; + } + return params; + } + function stringify(params) { + let path = ''; + // for optional parameters to allow to be empty + let avoidDuplicatedSlash = false; + for (const segment of segments) { + if (!avoidDuplicatedSlash || !path.endsWith('/')) + path += '/'; + avoidDuplicatedSlash = false; + for (const token of segment) { + if (token.type === 0 /* Static */) { + path += token.value; + } + else if (token.type === 1 /* Param */) { + const { value, repeatable, optional } = token; + const param = value in params ? params[value] : ''; + if (Array.isArray(param) && !repeatable) + throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`); + const text = Array.isArray(param) ? param.join('/') : param; + if (!text) { + if (optional) { + // remove the last slash as we could be at the end + if (path.endsWith('/')) + path = path.slice(0, -1); + // do not append a slash on the next iteration + else + avoidDuplicatedSlash = true; + } + else + throw new Error(`Missing required param "${value}"`); + } + path += text; + } + } + } + return path; + } + return { + re, + score, + keys, + parse, + stringify, + }; +} +/** + * Compares an array of numbers as used in PathParser.score and returns a + * number. This function can be used to `sort` an array + * @param a - first array of numbers + * @param b - second array of numbers + * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b + * should be sorted first + */ +function compareScoreArray(a, b) { + let i = 0; + while (i < a.length && i < b.length) { + const diff = b[i] - a[i]; + // only keep going if diff === 0 + if (diff) + return diff; + i++; + } + // if the last subsegment was Static, the shorter segments should be sorted first + // otherwise sort the longest segment first + if (a.length < b.length) { + return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */ + ? -1 + : 1; + } + else if (a.length > b.length) { + return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */ + ? 1 + : -1; + } + return 0; +} +/** + * Compare function that can be used with `sort` to sort an array of PathParser + * @param a - first PathParser + * @param b - second PathParser + * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b + */ +function comparePathParserScore(a, b) { + let i = 0; + const aScore = a.score; + const bScore = b.score; + while (i < aScore.length && i < bScore.length) { + const comp = compareScoreArray(aScore[i], bScore[i]); + // do not return if both are equal + if (comp) + return comp; + i++; + } + // if a and b share the same score entries but b has more, sort b first + return bScore.length - aScore.length; + // this is the ternary version + // return aScore.length < bScore.length + // ? 1 + // : aScore.length > bScore.length + // ? -1 + // : 0 +} + +const ROOT_TOKEN = { + type: 0 /* Static */, + value: '', +}; +const VALID_PARAM_RE = /[a-zA-Z0-9_]/; +// After some profiling, the cache seems to be unnecessary because tokenizePath +// (the slowest part of adding a route) is very fast +// const tokenCache = new Map() +function tokenizePath(path) { + if (!path) + return [[]]; + if (path === '/') + return [[ROOT_TOKEN]]; + if (!path.startsWith('/')) { + throw new Error(( true) + ? `Route paths should start with a "/": "${path}" should be "/${path}".` + : 0); + } + // if (tokenCache.has(path)) return tokenCache.get(path)! + function crash(message) { + throw new Error(`ERR (${state})/"${buffer}": ${message}`); + } + let state = 0 /* Static */; + let previousState = state; + const tokens = []; + // the segment will always be valid because we get into the initial state + // with the leading / + let segment; + function finalizeSegment() { + if (segment) + tokens.push(segment); + segment = []; + } + // index on the path + let i = 0; + // char at index + let char; + // buffer of the value read + let buffer = ''; + // custom regexp for a param + let customRe = ''; + function consumeBuffer() { + if (!buffer) + return; + if (state === 0 /* Static */) { + segment.push({ + type: 0 /* Static */, + value: buffer, + }); + } + else if (state === 1 /* Param */ || + state === 2 /* ParamRegExp */ || + state === 3 /* ParamRegExpEnd */) { + if (segment.length > 1 && (char === '*' || char === '+')) + crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`); + segment.push({ + type: 1 /* Param */, + value: buffer, + regexp: customRe, + repeatable: char === '*' || char === '+', + optional: char === '*' || char === '?', + }); + } + else { + crash('Invalid state to consume buffer'); + } + buffer = ''; + } + function addCharToBuffer() { + buffer += char; + } + while (i < path.length) { + char = path[i++]; + if (char === '\\' && state !== 2 /* ParamRegExp */) { + previousState = state; + state = 4 /* EscapeNext */; + continue; + } + switch (state) { + case 0 /* Static */: + if (char === '/') { + if (buffer) { + consumeBuffer(); + } + finalizeSegment(); + } + else if (char === ':') { + consumeBuffer(); + state = 1 /* Param */; + } + else { + addCharToBuffer(); + } + break; + case 4 /* EscapeNext */: + addCharToBuffer(); + state = previousState; + break; + case 1 /* Param */: + if (char === '(') { + state = 2 /* ParamRegExp */; + } + else if (VALID_PARAM_RE.test(char)) { + addCharToBuffer(); + } + else { + consumeBuffer(); + state = 0 /* Static */; + // go back one character if we were not modifying + if (char !== '*' && char !== '?' && char !== '+') + i--; + } + break; + case 2 /* ParamRegExp */: + // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix) + // it already works by escaping the closing ) + // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB# + // is this really something people need since you can also write + // /prefix_:p()_suffix + if (char === ')') { + // handle the escaped ) + if (customRe[customRe.length - 1] == '\\') + customRe = customRe.slice(0, -1) + char; + else + state = 3 /* ParamRegExpEnd */; + } + else { + customRe += char; + } + break; + case 3 /* ParamRegExpEnd */: + // same as finalizing a param + consumeBuffer(); + state = 0 /* Static */; + // go back one character if we were not modifying + if (char !== '*' && char !== '?' && char !== '+') + i--; + customRe = ''; + break; + default: + crash('Unknown state'); + break; + } + } + if (state === 2 /* ParamRegExp */) + crash(`Unfinished custom RegExp for param "${buffer}"`); + consumeBuffer(); + finalizeSegment(); + // tokenCache.set(path, tokens) + return tokens; +} + +function createRouteRecordMatcher(record, parent, options) { + const parser = tokensToParser(tokenizePath(record.path), options); + // warn against params with the same name + if ((true)) { + const existingKeys = new Set(); + for (const key of parser.keys) { + if (existingKeys.has(key.name)) + warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`); + existingKeys.add(key.name); + } + } + const matcher = assign(parser, { + record, + parent, + // these needs to be populated by the parent + children: [], + alias: [], + }); + if (parent) { + // both are aliases or both are not aliases + // we don't want to mix them because the order is used when + // passing originalRecord in Matcher.addRoute + if (!matcher.record.aliasOf === !parent.record.aliasOf) + parent.children.push(matcher); + } + return matcher; +} + +/** + * Creates a Router Matcher. + * + * @internal + * @param routes - array of initial routes + * @param globalOptions - global route options + */ +function createRouterMatcher(routes, globalOptions) { + // normalized ordered array of matchers + const matchers = []; + const matcherMap = new Map(); + globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions); + function getRecordMatcher(name) { + return matcherMap.get(name); + } + function addRoute(record, parent, originalRecord) { + // used later on to remove by name + let isRootAdd = !originalRecord; + let mainNormalizedRecord = normalizeRouteRecord(record); + // we might be the child of an alias + mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record; + const options = mergeOptions(globalOptions, record); + // generate an array of records to correctly handle aliases + const normalizedRecords = [ + mainNormalizedRecord, + ]; + if ('alias' in record) { + const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias; + for (const alias of aliases) { + normalizedRecords.push(assign({}, mainNormalizedRecord, { + // this allows us to hold a copy of the `components` option + // so that async components cache is hold on the original record + components: originalRecord + ? originalRecord.record.components + : mainNormalizedRecord.components, + path: alias, + // we might be the child of an alias + aliasOf: originalRecord + ? originalRecord.record + : mainNormalizedRecord, + })); + } + } + let matcher; + let originalMatcher; + for (const normalizedRecord of normalizedRecords) { + let { path } = normalizedRecord; + // Build up the path for nested routes if the child isn't an absolute + // route. Only add the / delimiter if the child path isn't empty and if the + // parent path doesn't have a trailing slash + if (parent && path[0] !== '/') { + let parentPath = parent.record.path; + let connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/'; + normalizedRecord.path = + parent.record.path + (path && connectingSlash + path); + } + if (( true) && normalizedRecord.path === '*') { + throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' + + 'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.'); + } + // create the object before hand so it can be passed to children + matcher = createRouteRecordMatcher(normalizedRecord, parent, options); + if (( true) && parent && path[0] === '/') + checkMissingParamsInAbsolutePath(matcher, parent); + // if we are an alias we must tell the original record that we exist + // so we can be removed + if (originalRecord) { + originalRecord.alias.push(matcher); + if ((true)) { + checkSameParams(originalRecord, matcher); + } + } + else { + // otherwise, the first record is the original and others are aliases + originalMatcher = originalMatcher || matcher; + if (originalMatcher !== matcher) + originalMatcher.alias.push(matcher); + // remove the route if named and only for the top record (avoid in nested calls) + // this works because the original record is the first one + if (isRootAdd && record.name && !isAliasRecord(matcher)) + removeRoute(record.name); + } + if ('children' in mainNormalizedRecord) { + let children = mainNormalizedRecord.children; + for (let i = 0; i < children.length; i++) { + addRoute(children[i], matcher, originalRecord && originalRecord.children[i]); + } + } + // if there was no original record, then the first one was not an alias and all + // other alias (if any) need to reference this record when adding children + originalRecord = originalRecord || matcher; + // TODO: add normalized records for more flexibility + // if (parent && isAliasRecord(originalRecord)) { + // parent.children.push(originalRecord) + // } + insertMatcher(matcher); + } + return originalMatcher + ? () => { + // since other matchers are aliases, they should be removed by the original matcher + removeRoute(originalMatcher); + } + : noop; + } + function removeRoute(matcherRef) { + if (isRouteName(matcherRef)) { + const matcher = matcherMap.get(matcherRef); + if (matcher) { + matcherMap.delete(matcherRef); + matchers.splice(matchers.indexOf(matcher), 1); + matcher.children.forEach(removeRoute); + matcher.alias.forEach(removeRoute); + } + } + else { + let index = matchers.indexOf(matcherRef); + if (index > -1) { + matchers.splice(index, 1); + if (matcherRef.record.name) + matcherMap.delete(matcherRef.record.name); + matcherRef.children.forEach(removeRoute); + matcherRef.alias.forEach(removeRoute); + } + } + } + function getRoutes() { + return matchers; + } + function insertMatcher(matcher) { + let i = 0; + // console.log('i is', { i }) + while (i < matchers.length && + comparePathParserScore(matcher, matchers[i]) >= 0) + i++; + // console.log('END i is', { i }) + // while (i < matchers.length && matcher.score <= matchers[i].score) i++ + matchers.splice(i, 0, matcher); + // only add the original record to the name map + if (matcher.record.name && !isAliasRecord(matcher)) + matcherMap.set(matcher.record.name, matcher); + } + function resolve(location, currentLocation) { + let matcher; + let params = {}; + let path; + let name; + if ('name' in location && location.name) { + matcher = matcherMap.get(location.name); + if (!matcher) + throw createRouterError(1 /* MATCHER_NOT_FOUND */, { + location, + }); + name = matcher.record.name; + params = assign( + // paramsFromLocation is a new object + paramsFromLocation(currentLocation.params, + // only keep params that exist in the resolved location + // TODO: only keep optional params coming from a parent record + matcher.keys.filter(k => !k.optional).map(k => k.name)), location.params); + // throws if cannot be stringified + path = matcher.stringify(params); + } + else if ('path' in location) { + // no need to resolve the path with the matcher as it was provided + // this also allows the user to control the encoding + path = location.path; + if (( true) && !path.startsWith('/')) { + warn(`The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-router-next.`); + } + matcher = matchers.find(m => m.re.test(path)); + // matcher should have a value after the loop + if (matcher) { + // TODO: dev warning of unused params if provided + // we know the matcher works because we tested the regexp + params = matcher.parse(path); + name = matcher.record.name; + } + // location is a relative path + } + else { + // match by name or path of current route + matcher = currentLocation.name + ? matcherMap.get(currentLocation.name) + : matchers.find(m => m.re.test(currentLocation.path)); + if (!matcher) + throw createRouterError(1 /* MATCHER_NOT_FOUND */, { + location, + currentLocation, + }); + name = matcher.record.name; + // since we are navigating to the same location, we don't need to pick the + // params like when `name` is provided + params = assign({}, currentLocation.params, location.params); + path = matcher.stringify(params); + } + const matched = []; + let parentMatcher = matcher; + while (parentMatcher) { + // reversed order so parents are at the beginning + matched.unshift(parentMatcher.record); + parentMatcher = parentMatcher.parent; + } + return { + name, + path, + params, + matched, + meta: mergeMetaFields(matched), + }; + } + // add initial routes + routes.forEach(route => addRoute(route)); + return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }; +} +function paramsFromLocation(params, keys) { + let newParams = {}; + for (let key of keys) { + if (key in params) + newParams[key] = params[key]; + } + return newParams; +} +/** + * Normalizes a RouteRecordRaw. Creates a copy + * + * @param record + * @returns the normalized version + */ +function normalizeRouteRecord(record) { + return { + path: record.path, + redirect: record.redirect, + name: record.name, + meta: record.meta || {}, + aliasOf: undefined, + beforeEnter: record.beforeEnter, + props: normalizeRecordProps(record), + children: record.children || [], + instances: {}, + leaveGuards: new Set(), + updateGuards: new Set(), + enterCallbacks: {}, + components: 'components' in record + ? record.components || {} + : { default: record.component }, + }; +} +/** + * Normalize the optional `props` in a record to always be an object similar to + * components. Also accept a boolean for components. + * @param record + */ +function normalizeRecordProps(record) { + const propsObject = {}; + // props does not exist on redirect records but we can set false directly + const props = record.props || false; + if ('component' in record) { + propsObject.default = props; + } + else { + // NOTE: we could also allow a function to be applied to every component. + // Would need user feedback for use cases + for (let name in record.components) + propsObject[name] = typeof props === 'boolean' ? props : props[name]; + } + return propsObject; +} +/** + * Checks if a record or any of its parent is an alias + * @param record + */ +function isAliasRecord(record) { + while (record) { + if (record.record.aliasOf) + return true; + record = record.parent; + } + return false; +} +/** + * Merge meta fields of an array of records + * + * @param matched - array of matched records + */ +function mergeMetaFields(matched) { + return matched.reduce((meta, record) => assign(meta, record.meta), {}); +} +function mergeOptions(defaults, partialOptions) { + let options = {}; + for (let key in defaults) { + options[key] = + key in partialOptions ? partialOptions[key] : defaults[key]; + } + return options; +} +function isSameParam(a, b) { + return (a.name === b.name && + a.optional === b.optional && + a.repeatable === b.repeatable); +} +function checkSameParams(a, b) { + for (let key of a.keys) { + if (!b.keys.find(isSameParam.bind(null, key))) + return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`); + } + for (let key of b.keys) { + if (!a.keys.find(isSameParam.bind(null, key))) + return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`); + } +} +function checkMissingParamsInAbsolutePath(record, parent) { + for (let key of parent.keys) { + if (!record.keys.find(isSameParam.bind(null, key))) + return warn(`Absolute path "${record.record.path}" should have the exact same param named "${key.name}" as its parent "${parent.record.path}".`); + } +} + +/** + * Encoding Rules ␣ = Space Path: ␣ " < > # ? { } Query: ␣ " < > # & = Hash: ␣ " + * < > ` + * + * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2) + * defines some extra characters to be encoded. Most browsers do not encode them + * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to + * also encode `!'()*`. Leaving unencoded only ASCII alphanumeric(`a-zA-Z0-9`) + * plus `-._~`. This extra safety should be applied to query by patching the + * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\` + * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\` + * into a `/` if directly typed in. The _backtick_ (`````) should also be + * encoded everywhere because some browsers like FF encode it when directly + * written while others don't. Safari and IE don't encode ``"<>{}``` in hash. + */ +// const EXTRA_RESERVED_RE = /[!'()*]/g +// const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16) +const HASH_RE = /#/g; // %23 +const AMPERSAND_RE = /&/g; // %26 +const SLASH_RE = /\//g; // %2F +const EQUAL_RE = /=/g; // %3D +const IM_RE = /\?/g; // %3F +const PLUS_RE = /\+/g; // %2B +/** + * NOTE: It's not clear to me if we should encode the + symbol in queries, it + * seems to be less flexible than not doing so and I can't find out the legacy + * systems requiring this for regular requests like text/html. In the standard, + * the encoding of the plus character is only mentioned for + * application/x-www-form-urlencoded + * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo + * leave the plus character as is in queries. To be more flexible, we allow the + * plus character on the query but it can also be manually encoded by the user. + * + * Resources: + * - https://url.spec.whatwg.org/#urlencoded-parsing + * - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20 + */ +const ENC_BRACKET_OPEN_RE = /%5B/g; // [ +const ENC_BRACKET_CLOSE_RE = /%5D/g; // ] +const ENC_CARET_RE = /%5E/g; // ^ +const ENC_BACKTICK_RE = /%60/g; // ` +const ENC_CURLY_OPEN_RE = /%7B/g; // { +const ENC_PIPE_RE = /%7C/g; // | +const ENC_CURLY_CLOSE_RE = /%7D/g; // } +const ENC_SPACE_RE = /%20/g; // } +/** + * Encode characters that need to be encoded on the path, search and hash + * sections of the URL. + * + * @internal + * @param text - string to encode + * @returns encoded string + */ +function commonEncode(text) { + return encodeURI('' + text) + .replace(ENC_PIPE_RE, '|') + .replace(ENC_BRACKET_OPEN_RE, '[') + .replace(ENC_BRACKET_CLOSE_RE, ']'); +} +/** + * Encode characters that need to be encoded on the hash section of the URL. + * + * @param text - string to encode + * @returns encoded string + */ +function encodeHash(text) { + return commonEncode(text) + .replace(ENC_CURLY_OPEN_RE, '{') + .replace(ENC_CURLY_CLOSE_RE, '}') + .replace(ENC_CARET_RE, '^'); +} +/** + * Encode characters that need to be encoded query values on the query + * section of the URL. + * + * @param text - string to encode + * @returns encoded string + */ +function encodeQueryValue(text) { + return (commonEncode(text) + // Encode the space as +, encode the + to differentiate it from the space + .replace(PLUS_RE, '%2B') + .replace(ENC_SPACE_RE, '+') + .replace(HASH_RE, '%23') + .replace(AMPERSAND_RE, '%26') + .replace(ENC_BACKTICK_RE, '`') + .replace(ENC_CURLY_OPEN_RE, '{') + .replace(ENC_CURLY_CLOSE_RE, '}') + .replace(ENC_CARET_RE, '^')); +} +/** + * Like `encodeQueryValue` but also encodes the `=` character. + * + * @param text - string to encode + */ +function encodeQueryKey(text) { + return encodeQueryValue(text).replace(EQUAL_RE, '%3D'); +} +/** + * Encode characters that need to be encoded on the path section of the URL. + * + * @param text - string to encode + * @returns encoded string + */ +function encodePath(text) { + return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F'); +} +/** + * Encode characters that need to be encoded on the path section of the URL as a + * param. This function encodes everything {@link encodePath} does plus the + * slash (`/`) character. + * + * @param text - string to encode + * @returns encoded string + */ +function encodeParam(text) { + return encodePath(text).replace(SLASH_RE, '%2F'); +} +/** + * Decode text using `decodeURIComponent`. Returns the original text if it + * fails. + * + * @param text - string to decode + * @returns decoded string + */ +function decode(text) { + try { + return decodeURIComponent('' + text); + } + catch (err) { + ( true) && warn(`Error decoding "${text}". Using original value`); + } + return '' + text; +} + +/** + * Transforms a queryString into a {@link LocationQuery} object. Accept both, a + * version with the leading `?` and without Should work as URLSearchParams + + * @internal + * + * @param search - search string to parse + * @returns a query object + */ +function parseQuery(search) { + const query = {}; + // avoid creating an object with an empty key and empty value + // because of split('&') + if (search === '' || search === '?') + return query; + const hasLeadingIM = search[0] === '?'; + const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&'); + for (let i = 0; i < searchParams.length; ++i) { + // pre decode the + into space + const searchParam = searchParams[i].replace(PLUS_RE, ' '); + // allow the = character + let eqPos = searchParam.indexOf('='); + let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos)); + let value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1)); + if (key in query) { + // an extra variable for ts types + let currentValue = query[key]; + if (!Array.isArray(currentValue)) { + currentValue = query[key] = [currentValue]; + } + currentValue.push(value); + } + else { + query[key] = value; + } + } + return query; +} +/** + * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it + * doesn't prepend a `?` + * + * @internal + * + * @param query - query object to stringify + * @returns string version of the query without the leading `?` + */ +function stringifyQuery(query) { + let search = ''; + for (let key in query) { + if (search.length) + search += '&'; + const value = query[key]; + key = encodeQueryKey(key); + if (value == null) { + // only null adds the value + if (value !== undefined) + search += key; + continue; + } + // keep null values + let values = Array.isArray(value) + ? value.map(v => v && encodeQueryValue(v)) + : [value && encodeQueryValue(value)]; + for (let i = 0; i < values.length; i++) { + // only append & with i > 0 + search += (i ? '&' : '') + key; + if (values[i] != null) + search += ('=' + values[i]); + } + } + return search; +} +/** + * Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting + * numbers into strings, removing keys with an undefined value and replacing + * undefined with null in arrays + * + * @param query - query object to normalize + * @returns a normalized query object + */ +function normalizeQuery(query) { + const normalizedQuery = {}; + for (let key in query) { + let value = query[key]; + if (value !== undefined) { + normalizedQuery[key] = Array.isArray(value) + ? value.map(v => (v == null ? null : '' + v)) + : value == null + ? value + : '' + value; + } + } + return normalizedQuery; +} + +/** + * Create a list of callbacks that can be reset. Used to create before and after navigation guards list + */ +function useCallbacks() { + let handlers = []; + function add(handler) { + handlers.push(handler); + return () => { + const i = handlers.indexOf(handler); + if (i > -1) + handlers.splice(i, 1); + }; + } + function reset() { + handlers = []; + } + return { + add, + list: () => handlers, + reset, + }; +} + +function registerGuard(record, name, guard) { + const removeFromList = () => { + record[name].delete(guard); + }; + (0,vue__WEBPACK_IMPORTED_MODULE_0__.onUnmounted)(removeFromList); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.onDeactivated)(removeFromList); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.onActivated)(() => { + record[name].add(guard); + }); + record[name].add(guard); +} +/** + * Add a navigation guard that triggers whenever the component for the current + * location is about to be left. Similar to {@link beforeRouteLeave} but can be + * used in any component. The guard is removed when the component is unmounted. + * + * @param leaveGuard - {@link NavigationGuard} + */ +function onBeforeRouteLeave(leaveGuard) { + if (( true) && !(0,vue__WEBPACK_IMPORTED_MODULE_0__.getCurrentInstance)()) { + warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function'); + return; + } + const activeRecord = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(matchedRouteKey, {}).value; + if (!activeRecord) { + ( true) && + warn('No active route record was found. Are you missing a component?'); + return; + } + registerGuard(activeRecord, 'leaveGuards', leaveGuard); +} +/** + * Add a navigation guard that triggers whenever the current location is about + * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any + * component. The guard is removed when the component is unmounted. + * + * @param updateGuard - {@link NavigationGuard} + */ +function onBeforeRouteUpdate(updateGuard) { + if (( true) && !(0,vue__WEBPACK_IMPORTED_MODULE_0__.getCurrentInstance)()) { + warn('getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function'); + return; + } + const activeRecord = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(matchedRouteKey, {}).value; + if (!activeRecord) { + ( true) && + warn('No active route record was found. Are you missing a component?'); + return; + } + registerGuard(activeRecord, 'updateGuards', updateGuard); +} +function guardToPromiseFn(guard, to, from, record, name) { + // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place + const enterCallbackArray = record && + // name is defined if record is because of the function overload + (record.enterCallbacks[name] = record.enterCallbacks[name] || []); + return () => new Promise((resolve, reject) => { + const next = (valid) => { + if (valid === false) + reject(createRouterError(4 /* NAVIGATION_ABORTED */, { + from, + to, + })); + else if (valid instanceof Error) { + reject(valid); + } + else if (isRouteLocation(valid)) { + reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, { + from: to, + to: valid, + })); + } + else { + if (enterCallbackArray && + // since enterCallbackArray is truthy, both record and name also are + record.enterCallbacks[name] === enterCallbackArray && + typeof valid === 'function') + enterCallbackArray.push(valid); + resolve(); + } + }; + // wrapping with Promise.resolve allows it to work with both async and sync guards + const guardReturn = guard.call(record && record.instances[name], to, from, ( true) ? canOnlyBeCalledOnce(next, to, from) : 0); + let guardCall = Promise.resolve(guardReturn); + if (guard.length < 3) + guardCall = guardCall.then(next); + if (( true) && guard.length > 2) { + const message = `The "next" callback was never called inside of ${guard.name ? '"' + guard.name + '"' : ''}:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.`; + if (typeof guardReturn === 'object' && 'then' in guardReturn) { + guardCall = guardCall.then(resolvedValue => { + // @ts-ignore: _called is added at canOnlyBeCalledOnce + if (!next._called) { + warn(message); + return Promise.reject(new Error('Invalid navigation guard')); + } + return resolvedValue; + }); + // TODO: test me! + } + else if (guardReturn !== undefined) { + // @ts-ignore: _called is added at canOnlyBeCalledOnce + if (!next._called) { + warn(message); + reject(new Error('Invalid navigation guard')); + return; + } + } + } + guardCall.catch(err => reject(err)); + }); +} +function canOnlyBeCalledOnce(next, to, from) { + let called = 0; + return function () { + if (called++ === 1) + warn(`The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.`); + // @ts-ignore: we put it in the original one because it's easier to check + next._called = true; + if (called === 1) + next.apply(null, arguments); + }; +} +function extractComponentsGuards(matched, guardType, to, from) { + const guards = []; + for (const record of matched) { + for (const name in record.components) { + let rawComponent = record.components[name]; + if ((true)) { + if (!rawComponent || + (typeof rawComponent !== 'object' && + typeof rawComponent !== 'function')) { + warn(`Component "${name}" in record with path "${record.path}" is not` + + ` a valid component. Received "${String(rawComponent)}".`); + // throw to ensure we stop here but warn to ensure the message isn't + // missed by the user + throw new Error('Invalid route component'); + } + else if ('then' in rawComponent) { + // warn if user wrote import('/component.vue') instead of () => + // import('./component.vue') + warn(`Component "${name}" in record with path "${record.path}" is a ` + + `Promise instead of a function that returns a Promise. Did you ` + + `write "import('./MyPage.vue')" instead of ` + + `"() => import('./MyPage.vue')" ? This will break in ` + + `production if not fixed.`); + let promise = rawComponent; + rawComponent = () => promise; + } + else if (rawComponent.__asyncLoader && + // warn only once per component + !rawComponent.__warnedDefineAsync) { + rawComponent.__warnedDefineAsync = true; + warn(`Component "${name}" in record with path "${record.path}" is defined ` + + `using "defineAsyncComponent()". ` + + `Write "() => import('./MyPage.vue')" instead of ` + + `"defineAsyncComponent(() => import('./MyPage.vue'))".`); + } + } + // skip update and leave guards if the route component is not mounted + if (guardType !== 'beforeRouteEnter' && !record.instances[name]) + continue; + if (isRouteComponent(rawComponent)) { + // __vccOpts is added by vue-class-component and contain the regular options + let options = rawComponent.__vccOpts || rawComponent; + const guard = options[guardType]; + guard && guards.push(guardToPromiseFn(guard, to, from, record, name)); + } + else { + // start requesting the chunk already + let componentPromise = rawComponent(); + if (( true) && !('catch' in componentPromise)) { + warn(`Component "${name}" in record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`); + componentPromise = Promise.resolve(componentPromise); + } + else { + // display the error if any + componentPromise = componentPromise.catch(( true) ? err => err && warn(err) : 0); + } + guards.push(() => componentPromise.then(resolved => { + if (!resolved) + return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}"`)); + const resolvedComponent = isESModule(resolved) + ? resolved.default + : resolved; + // replace the function with the resolved component + record.components[name] = resolvedComponent; + // @ts-ignore: the options types are not propagated to Component + const guard = resolvedComponent[guardType]; + return guard && guardToPromiseFn(guard, to, from, record, name)(); + })); + } + } + } + return guards; +} +/** + * Allows differentiating lazy components from functional components and vue-class-component + * @param component + */ +function isRouteComponent(component) { + return (typeof component === 'object' || + 'displayName' in component || + 'props' in component || + '__vccOpts' in component); +} + +// TODO: we could allow currentRoute as a prop to expose `isActive` and +// `isExactActive` behavior should go through an RFC +function useLink(props) { + const router = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routerKey); + const currentRoute = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routeLocationKey); + const route = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => router.resolve((0,vue__WEBPACK_IMPORTED_MODULE_0__.unref)(props.to))); + const activeRecordIndex = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => { + let { matched } = route.value; + let { length } = matched; + const routeMatched = matched[length - 1]; + let currentMatched = currentRoute.matched; + if (!routeMatched || !currentMatched.length) + return -1; + let index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched)); + if (index > -1) + return index; + // possible parent record + let parentRecordPath = getOriginalPath(matched[length - 2]); + return ( + // we are dealing with nested routes + length > 1 && + // if the parent and matched route have the same path, this link is + // referring to the empty child. Or we currently are on a different + // child of the same parent + getOriginalPath(routeMatched) === parentRecordPath && + // avoid comparing the child with its parent + currentMatched[currentMatched.length - 1].path !== parentRecordPath + ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2])) + : index); + }); + const isActive = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => activeRecordIndex.value > -1 && + includesParams(currentRoute.params, route.value.params)); + const isExactActive = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => activeRecordIndex.value > -1 && + activeRecordIndex.value === currentRoute.matched.length - 1 && + isSameRouteLocationParams(currentRoute.params, route.value.params)); + function navigate(e = {}) { + if (guardEvent(e)) + return router[(0,vue__WEBPACK_IMPORTED_MODULE_0__.unref)(props.replace) ? 'replace' : 'push']((0,vue__WEBPACK_IMPORTED_MODULE_0__.unref)(props.to)); + return Promise.resolve(); + } + return { + route, + href: (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => route.value.href), + isActive, + isExactActive, + navigate, + }; +} +const RouterLinkImpl = /*#__PURE__*/ (0,vue__WEBPACK_IMPORTED_MODULE_0__.defineComponent)({ + name: 'RouterLink', + props: { + to: { + type: [String, Object], + required: true, + }, + replace: Boolean, + activeClass: String, + // inactiveClass: String, + exactActiveClass: String, + custom: Boolean, + ariaCurrentValue: { + type: String, + default: 'page', + }, + }, + setup(props, { slots, attrs }) { + const link = (0,vue__WEBPACK_IMPORTED_MODULE_0__.reactive)(useLink(props)); + const { options } = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routerKey); + const elClass = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => ({ + [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive, + // [getLinkClass( + // props.inactiveClass, + // options.linkInactiveClass, + // 'router-link-inactive' + // )]: !link.isExactActive, + [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive, + })); + return () => { + const children = slots.default && slots.default(link); + return props.custom + ? children + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.h)('a', assign({ + 'aria-current': link.isExactActive + ? props.ariaCurrentValue + : null, + onClick: link.navigate, + href: link.href, + }, attrs, { + class: elClass.value, + }), children); + }; + }, +}); +// export the public type for h/tsx inference +// also to avoid inline import() in generated d.ts files +/** + * Component to render a link that triggers a navigation on click. + */ +const RouterLink = RouterLinkImpl; +function guardEvent(e) { + // don't redirect with control keys + if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) + return; + // don't redirect when preventDefault called + if (e.defaultPrevented) + return; + // don't redirect on right click + if (e.button !== undefined && e.button !== 0) + return; + // don't redirect if `target="_blank"` + // @ts-ignore getAttribute does exist + if (e.currentTarget && e.currentTarget.getAttribute) { + // @ts-ignore getAttribute exists + const target = e.currentTarget.getAttribute('target'); + if (/\b_blank\b/i.test(target)) + return; + } + // this may be a Weex event which doesn't have this method + if (e.preventDefault) + e.preventDefault(); + return true; +} +function includesParams(outer, inner) { + for (let key in inner) { + let innerValue = inner[key]; + let outerValue = outer[key]; + if (typeof innerValue === 'string') { + if (innerValue !== outerValue) + return false; + } + else { + if (!Array.isArray(outerValue) || + outerValue.length !== innerValue.length || + innerValue.some((value, i) => value !== outerValue[i])) + return false; + } + } + return true; +} +/** + * Get the original path value of a record by following its aliasOf + * @param record + */ +function getOriginalPath(record) { + return record ? (record.aliasOf ? record.aliasOf.path : record.path) : ''; +} +/** + * Utility class to get the active class based on defaults. + * @param propClass + * @param globalClass + * @param defaultClass + */ +const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null + ? propClass + : globalClass != null + ? globalClass + : defaultClass; + +const RouterViewImpl = /*#__PURE__*/ (0,vue__WEBPACK_IMPORTED_MODULE_0__.defineComponent)({ + name: 'RouterView', + // #674 we manually inherit them + inheritAttrs: false, + props: { + name: { + type: String, + default: 'default', + }, + route: Object, + }, + setup(props, { attrs, slots }) { + ( true) && warnDeprecatedUsage(); + const injectedRoute = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routerViewLocationKey); + const routeToDisplay = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => props.route || injectedRoute.value); + const depth = (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(viewDepthKey, 0); + const matchedRouteRef = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => routeToDisplay.value.matched[depth]); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.provide)(viewDepthKey, depth + 1); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.provide)(matchedRouteKey, matchedRouteRef); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.provide)(routerViewLocationKey, routeToDisplay); + const viewRef = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(); + // watch at the same time the component instance, the route record we are + // rendering, and the name + (0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => { + // copy reused instances + if (to) { + // this will update the instance for new instances as well as reused + // instances when navigating to a new route + to.instances[name] = instance; + // the component instance is reused for a different route or name so + // we copy any saved update or leave guards + if (from && from !== to && instance && instance === oldInstance) { + to.leaveGuards = from.leaveGuards; + to.updateGuards = from.updateGuards; + } + } + // trigger beforeRouteEnter next callbacks + if (instance && + to && + // if there is no instance but to and from are the same this might be + // the first visit + (!from || !isSameRouteRecord(to, from) || !oldInstance)) { + (to.enterCallbacks[name] || []).forEach(callback => callback(instance)); + } + }, { flush: 'post' }); + return () => { + const route = routeToDisplay.value; + const matchedRoute = matchedRouteRef.value; + const ViewComponent = matchedRoute && matchedRoute.components[props.name]; + // we need the value at the time we render because when we unmount, we + // navigated to a different location so the value is different + const currentName = props.name; + if (!ViewComponent) { + return normalizeSlot(slots.default, { Component: ViewComponent, route }); + } + // props from route configuration + const routePropsOption = matchedRoute.props[props.name]; + const routeProps = routePropsOption + ? routePropsOption === true + ? route.params + : typeof routePropsOption === 'function' + ? routePropsOption(route) + : routePropsOption + : null; + const onVnodeUnmounted = vnode => { + // remove the instance reference to prevent leak + if (vnode.component.isUnmounted) { + matchedRoute.instances[currentName] = null; + } + }; + const component = (0,vue__WEBPACK_IMPORTED_MODULE_0__.h)(ViewComponent, assign({}, routeProps, attrs, { + onVnodeUnmounted, + ref: viewRef, + })); + return ( + // pass the vnode to the slot as a prop. + // h and both accept vnodes + normalizeSlot(slots.default, { Component: component, route }) || + component); + }; + }, +}); +function normalizeSlot(slot, data) { + if (!slot) + return null; + const slotContent = slot(data); + return slotContent.length === 1 ? slotContent[0] : slotContent; +} +// export the public type for h/tsx inference +// also to avoid inline import() in generated d.ts files +/** + * Component to display the current route the user is at. + */ +const RouterView = RouterViewImpl; +// warn against deprecated usage with & +// due to functional component being no longer eager in Vue 3 +function warnDeprecatedUsage() { + const instance = (0,vue__WEBPACK_IMPORTED_MODULE_0__.getCurrentInstance)(); + const parentName = instance.parent && instance.parent.type.name; + if (parentName && + (parentName === 'KeepAlive' || parentName.includes('Transition'))) { + const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition'; + warn(` can no longer be used directly inside or .\n` + + `Use slot props instead:\n\n` + + `\n` + + ` <${comp}>\n` + + ` \n` + + ` \n` + + ``); + } +} + +function getDevtoolsGlobalHook() { + return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__; +} +function getTarget() { + // @ts-ignore + return typeof navigator !== 'undefined' + ? window + : typeof __webpack_require__.g !== 'undefined' + ? __webpack_require__.g + : {}; +} + +const HOOK_SETUP = 'devtools-plugin:setup'; + +function setupDevtoolsPlugin(pluginDescriptor, setupFn) { + const hook = getDevtoolsGlobalHook(); + if (hook) { + hook.emit(HOOK_SETUP, pluginDescriptor, setupFn); + } + else { + const target = getTarget(); + const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || []; + list.push({ + pluginDescriptor, + setupFn + }); + } +} + +function formatRouteLocation(routeLocation, tooltip) { + const copy = assign({}, routeLocation, { + // remove variables that can contain vue instances + matched: routeLocation.matched.map(matched => omit(matched, ['instances', 'children', 'aliasOf'])), + }); + return { + _custom: { + type: null, + readOnly: true, + display: routeLocation.fullPath, + tooltip, + value: copy, + }, + }; +} +function formatDisplay(display) { + return { + _custom: { + display, + }, + }; +} +// to support multiple router instances +let routerId = 0; +function addDevtools(app, router, matcher) { + // Take over router.beforeEach and afterEach + // increment to support multiple router instances + const id = routerId++; + setupDevtoolsPlugin({ + id: 'Router' + id ? ' ' + id : 0, + label: 'Router devtools', + app, + }, api => { + api.on.inspectComponent((payload, ctx) => { + if (payload.instanceData) { + payload.instanceData.state.push({ + type: 'Routing', + key: '$route', + editable: false, + value: formatRouteLocation(router.currentRoute.value, 'Current Route'), + }); + } + }); + (0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(router.currentRoute, () => { + // refresh active state + refreshRoutesView(); + // @ts-ignore + api.notifyComponentUpdate(); + api.sendInspectorTree(routerInspectorId); + }); + const navigationsLayerId = 'router:navigations:' + id; + api.addTimelineLayer({ + id: navigationsLayerId, + label: `Router${id ? ' ' + id : ''} Navigations`, + color: 0x40a8c4, + }); + // const errorsLayerId = 'router:errors' + // api.addTimelineLayer({ + // id: errorsLayerId, + // label: 'Router Errors', + // color: 0xea5455, + // }) + router.onError(error => { + api.addTimelineEvent({ + layerId: navigationsLayerId, + event: { + // @ts-ignore + logType: 'error', + time: Date.now(), + data: { error }, + }, + }); + }); + router.beforeEach((to, from) => { + const data = { + guard: formatDisplay('beforeEach'), + from: formatRouteLocation(from, 'Current Location during this navigation'), + to: formatRouteLocation(to, 'Target location'), + }; + api.addTimelineEvent({ + layerId: navigationsLayerId, + event: { + time: Date.now(), + meta: {}, + data, + }, + }); + }); + router.afterEach((to, from, failure) => { + const data = { + guard: formatDisplay('afterEach'), + }; + if (failure) { + data.failure = { + _custom: { + type: Error, + readOnly: true, + display: failure ? failure.message : '', + tooltip: 'Navigation Failure', + value: failure, + }, + }; + data.status = formatDisplay('❌'); + } + else { + data.status = formatDisplay('✅'); + } + // we set here to have the right order + data.from = formatRouteLocation(from, 'Current Location during this navigation'); + data.to = formatRouteLocation(to, 'Target location'); + api.addTimelineEvent({ + layerId: navigationsLayerId, + event: { + time: Date.now(), + data, + // @ts-ignore + logType: failure ? 'warning' : 'default', + meta: {}, + }, + }); + }); + /** + * Inspector of Existing routes + */ + const routerInspectorId = 'router-inspector:' + id; + api.addInspector({ + id: routerInspectorId, + label: 'Routes' + (id ? ' ' + id : ''), + icon: 'book', + treeFilterPlaceholder: 'Search routes', + }); + function refreshRoutesView() { + // the routes view isn't active + if (!activeRoutesPayload) + return; + const payload = activeRoutesPayload; + // children routes will appear as nested + let routes = matcher.getRoutes().filter(route => !route.parent); + // reset match state to false + routes.forEach(resetMatchStateOnRouteRecord); + // apply a match state if there is a payload + if (payload.filter) { + routes = routes.filter(route => + // save matches state based on the payload + isRouteMatching(route, payload.filter.toLowerCase())); + } + // mark active routes + routes.forEach(route => markRouteRecordActive(route, router.currentRoute.value)); + payload.rootNodes = routes.map(formatRouteRecordForInspector); + } + let activeRoutesPayload; + api.on.getInspectorTree(payload => { + activeRoutesPayload = payload; + if (payload.app === app && payload.inspectorId === routerInspectorId) { + refreshRoutesView(); + } + }); + /** + * Display information about the currently selected route record + */ + api.on.getInspectorState(payload => { + if (payload.app === app && payload.inspectorId === routerInspectorId) { + const routes = matcher.getRoutes(); + const route = routes.find(route => route.record.__vd_id === payload.nodeId); + if (route) { + payload.state = { + options: formatRouteRecordMatcherForStateInspector(route), + }; + } + } + }); + api.sendInspectorTree(routerInspectorId); + api.sendInspectorState(routerInspectorId); + }); +} +function modifierForKey(key) { + if (key.optional) { + return key.repeatable ? '*' : '?'; + } + else { + return key.repeatable ? '+' : ''; + } +} +function formatRouteRecordMatcherForStateInspector(route) { + const { record } = route; + const fields = [ + { editable: false, key: 'path', value: record.path }, + ]; + if (record.name != null) { + fields.push({ + editable: false, + key: 'name', + value: record.name, + }); + } + fields.push({ editable: false, key: 'regexp', value: route.re }); + if (route.keys.length) { + fields.push({ + editable: false, + key: 'keys', + value: { + _custom: { + type: null, + readOnly: true, + display: route.keys + .map(key => `${key.name}${modifierForKey(key)}`) + .join(' '), + tooltip: 'Param keys', + value: route.keys, + }, + }, + }); + } + if (record.redirect != null) { + fields.push({ + editable: false, + key: 'redirect', + value: record.redirect, + }); + } + if (route.alias.length) { + fields.push({ + editable: false, + key: 'aliases', + value: route.alias.map(alias => alias.record.path), + }); + } + fields.push({ + key: 'score', + editable: false, + value: { + _custom: { + type: null, + readOnly: true, + display: route.score.map(score => score.join(', ')).join(' | '), + tooltip: 'Score used to sort routes', + value: route.score, + }, + }, + }); + return fields; +} +/** + * Extracted from tailwind palette + */ +const PINK_500 = 0xec4899; +const BLUE_600 = 0x2563eb; +const LIME_500 = 0x84cc16; +const CYAN_400 = 0x22d3ee; +const ORANGE_400 = 0xfb923c; +// const GRAY_100 = 0xf4f4f5 +const DARK = 0x666666; +function formatRouteRecordForInspector(route) { + const tags = []; + const { record } = route; + if (record.name != null) { + tags.push({ + label: String(record.name), + textColor: 0, + backgroundColor: CYAN_400, + }); + } + if (record.aliasOf) { + tags.push({ + label: 'alias', + textColor: 0, + backgroundColor: ORANGE_400, + }); + } + if (route.__vd_match) { + tags.push({ + label: 'matches', + textColor: 0, + backgroundColor: PINK_500, + }); + } + if (route.__vd_exactActive) { + tags.push({ + label: 'exact', + textColor: 0, + backgroundColor: LIME_500, + }); + } + if (route.__vd_active) { + tags.push({ + label: 'active', + textColor: 0, + backgroundColor: BLUE_600, + }); + } + if (record.redirect) { + tags.push({ + label: 'redirect: ' + + (typeof record.redirect === 'string' ? record.redirect : 'Object'), + textColor: 0xffffff, + backgroundColor: DARK, + }); + } + // add an id to be able to select it. Using the `path` is not possible because + // empty path children would collide with their parents + let id = String(routeRecordId++); + record.__vd_id = id; + return { + id, + label: record.path, + tags, + // @ts-ignore + children: route.children.map(formatRouteRecordForInspector), + }; +} +// incremental id for route records and inspector state +let routeRecordId = 0; +const EXTRACT_REGEXP_RE = /^\/(.*)\/([a-z]*)$/; +function markRouteRecordActive(route, currentRoute) { + // no route will be active if matched is empty + // reset the matching state + const isExactActive = currentRoute.matched.length && + isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record); + route.__vd_exactActive = route.__vd_active = isExactActive; + if (!isExactActive) { + route.__vd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record)); + } + route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute)); +} +function resetMatchStateOnRouteRecord(route) { + route.__vd_match = false; + route.children.forEach(resetMatchStateOnRouteRecord); +} +function isRouteMatching(route, filter) { + const found = String(route.re).match(EXTRACT_REGEXP_RE); + route.__vd_match = false; + if (!found || found.length < 3) { + return false; + } + // use a regexp without $ at the end to match nested routes better + const nonEndingRE = new RegExp(found[1].replace(/\$$/, ''), found[2]); + if (nonEndingRE.test(filter)) { + // mark children as matches + route.children.forEach(child => isRouteMatching(child, filter)); + // exception case: `/` + if (route.record.path !== '/' || filter === '/') { + route.__vd_match = route.re.test(filter); + return true; + } + // hide the / route + return false; + } + const path = route.record.path.toLowerCase(); + const decodedPath = decode(path); + // also allow partial matching on the path + if (!filter.startsWith('/') && + (decodedPath.includes(filter) || path.includes(filter))) + return true; + if (decodedPath.startsWith(filter) || path.startsWith(filter)) + return true; + if (route.record.name && String(route.record.name).includes(filter)) + return true; + return route.children.some(child => isRouteMatching(child, filter)); +} +function omit(obj, keys) { + const ret = {}; + for (let key in obj) { + if (!keys.includes(key)) { + // @ts-ignore + ret[key] = obj[key]; + } + } + return ret; +} + +/** + * Creates a Router instance that can be used by a Vue app. + * + * @param options - {@link RouterOptions} + */ +function createRouter(options) { + const matcher = createRouterMatcher(options.routes, options); + let parseQuery$1 = options.parseQuery || parseQuery; + let stringifyQuery$1 = options.stringifyQuery || stringifyQuery; + let routerHistory = options.history; + const beforeGuards = useCallbacks(); + const beforeResolveGuards = useCallbacks(); + const afterGuards = useCallbacks(); + const currentRoute = (0,vue__WEBPACK_IMPORTED_MODULE_0__.shallowRef)(START_LOCATION_NORMALIZED); + let pendingLocation = START_LOCATION_NORMALIZED; + // leave the scrollRestoration if no scrollBehavior is provided + if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) { + history.scrollRestoration = 'manual'; + } + const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue); + const encodeParams = applyToParams.bind(null, encodeParam); + const decodeParams = applyToParams.bind(null, decode); + function addRoute(parentOrRoute, route) { + let parent; + let record; + if (isRouteName(parentOrRoute)) { + parent = matcher.getRecordMatcher(parentOrRoute); + record = route; + } + else { + record = parentOrRoute; + } + return matcher.addRoute(record, parent); + } + function removeRoute(name) { + let recordMatcher = matcher.getRecordMatcher(name); + if (recordMatcher) { + matcher.removeRoute(recordMatcher); + } + else if ((true)) { + warn(`Cannot remove non-existent route "${String(name)}"`); + } + } + function getRoutes() { + return matcher.getRoutes().map(routeMatcher => routeMatcher.record); + } + function hasRoute(name) { + return !!matcher.getRecordMatcher(name); + } + function resolve(rawLocation, currentLocation) { + // const objectLocation = routerLocationAsObject(rawLocation) + // we create a copy to modify it later + currentLocation = assign({}, currentLocation || currentRoute.value); + if (typeof rawLocation === 'string') { + let locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path); + let matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation); + let href = routerHistory.createHref(locationNormalized.fullPath); + if ((true)) { + if (href.startsWith('//')) + warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`); + else if (!matchedRoute.matched.length) { + warn(`No match found for location with path "${rawLocation}"`); + } + } + // locationNormalized is always a new object + return assign(locationNormalized, matchedRoute, { + params: decodeParams(matchedRoute.params), + hash: decode(locationNormalized.hash), + redirectedFrom: undefined, + href, + }); + } + let matcherLocation; + // path could be relative in object as well + if ('path' in rawLocation) { + if (( true) && + 'params' in rawLocation && + !('name' in rawLocation) && + Object.keys(rawLocation.params).length) { + warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`); + } + matcherLocation = assign({}, rawLocation, { + path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path, + }); + } + else { + // pass encoded values to the matcher so it can produce encoded path and fullPath + matcherLocation = assign({}, rawLocation, { + params: encodeParams(rawLocation.params), + }); + // current location params are decoded, we need to encode them in case the + // matcher merges the params + currentLocation.params = encodeParams(currentLocation.params); + } + let matchedRoute = matcher.resolve(matcherLocation, currentLocation); + const hash = rawLocation.hash || ''; + if (( true) && hash && !hash.startsWith('#')) { + warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`); + } + // decoding them) the matcher might have merged current location params so + // we need to run the decoding again + matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params)); + const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, { + hash: encodeHash(hash), + path: matchedRoute.path, + })); + let href = routerHistory.createHref(fullPath); + if ((true)) { + if (href.startsWith('//')) { + warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`); + } + else if (!matchedRoute.matched.length) { + warn(`No match found for location with path "${'path' in rawLocation ? rawLocation.path : rawLocation}"`); + } + } + return assign({ + fullPath, + // keep the hash encoded so fullPath is effectively path + encodedQuery + + // hash + hash, + query: + // if the user is using a custom query lib like qs, we might have + // nested objects, so we keep the query as is, meaning it can contain + // numbers at `$route.query`, but at the point, the user will have to + // use their own type anyway. + // https://github.com/vuejs/vue-router-next/issues/328#issuecomment-649481567 + stringifyQuery$1 === stringifyQuery + ? normalizeQuery(rawLocation.query) + : rawLocation.query, + }, matchedRoute, { + redirectedFrom: undefined, + href, + }); + } + function locationAsObject(to) { + return typeof to === 'string' + ? parseURL(parseQuery$1, to, currentRoute.value.path) + : assign({}, to); + } + function checkCanceledNavigation(to, from) { + if (pendingLocation !== to) { + return createRouterError(8 /* NAVIGATION_CANCELLED */, { + from, + to, + }); + } + } + function push(to) { + return pushWithRedirect(to); + } + function replace(to) { + return push(assign(locationAsObject(to), { replace: true })); + } + function handleRedirectRecord(to) { + const lastMatched = to.matched[to.matched.length - 1]; + if (lastMatched && lastMatched.redirect) { + const { redirect } = lastMatched; + let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect; + if (typeof newTargetLocation === 'string') { + newTargetLocation = + newTargetLocation.indexOf('?') > -1 || + newTargetLocation.indexOf('#') > -1 + ? (newTargetLocation = locationAsObject(newTargetLocation)) + : { path: newTargetLocation }; + } + if (( true) && + !('path' in newTargetLocation) && + !('name' in newTargetLocation)) { + warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${to.fullPath}". A redirect must contain a name or path. This will break in production.`); + throw new Error('Invalid redirect'); + } + return assign({ + query: to.query, + hash: to.hash, + params: to.params, + }, newTargetLocation); + } + } + function pushWithRedirect(to, redirectedFrom) { + const targetLocation = (pendingLocation = resolve(to)); + const from = currentRoute.value; + const data = to.state; + const force = to.force; + // to could be a string where `replace` is a function + const replace = to.replace === true; + const shouldRedirect = handleRedirectRecord(targetLocation); + if (shouldRedirect) + return pushWithRedirect(assign(locationAsObject(shouldRedirect), { + state: data, + force, + replace, + }), + // keep original redirectedFrom if it exists + redirectedFrom || targetLocation); + // if it was a redirect we already called `pushWithRedirect` above + const toLocation = targetLocation; + toLocation.redirectedFrom = redirectedFrom; + let failure; + if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) { + failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from }); + // trigger scroll to allow scrolling to the same anchor + handleScroll(from, from, + // this is a push, the only way for it to be triggered from a + // history.listen is with a redirect, which makes it become a push + true, + // This cannot be the first navigation because the initial location + // cannot be manually navigated to + false); + } + return (failure ? Promise.resolve(failure) : navigate(toLocation, from)) + .catch((error) => isNavigationFailure(error) + ? error + : // reject any unknown error + triggerError(error)) + .then((failure) => { + if (failure) { + if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) { + if (( true) && + // we are redirecting to the same location we were already at + isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) && + // and we have done it a couple of times + redirectedFrom && + // @ts-ignore + (redirectedFrom._count = redirectedFrom._count + ? // @ts-ignore + redirectedFrom._count + 1 + : 1) > 10) { + warn(`Detected an infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow. This will break in production if not fixed.`); + return Promise.reject(new Error('Infinite redirect in navigation guard')); + } + return pushWithRedirect( + // keep options + assign(locationAsObject(failure.to), { + state: data, + force, + replace, + }), + // preserve the original redirectedFrom if any + redirectedFrom || toLocation); + } + } + else { + // if we fail we don't finalize the navigation + failure = finalizeNavigation(toLocation, from, true, replace, data); + } + triggerAfterEach(toLocation, from, failure); + return failure; + }); + } + /** + * Helper to reject and skip all navigation guards if a new navigation happened + * @param to + * @param from + */ + function checkCanceledNavigationAndReject(to, from) { + const error = checkCanceledNavigation(to, from); + return error ? Promise.reject(error) : Promise.resolve(); + } + // TODO: refactor the whole before guards by internally using router.beforeEach + function navigate(to, from) { + let guards; + const [leavingRecords, updatingRecords, enteringRecords,] = extractChangingRecords(to, from); + // all components here have been resolved once because we are leaving + guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from); + // leavingRecords is already reversed + for (const record of leavingRecords) { + record.leaveGuards.forEach(guard => { + guards.push(guardToPromiseFn(guard, to, from)); + }); + } + const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from); + guards.push(canceledNavigationCheck); + // run the queue of per route beforeRouteLeave guards + return (runGuardQueue(guards) + .then(() => { + // check global guards beforeEach + guards = []; + for (const guard of beforeGuards.list()) { + guards.push(guardToPromiseFn(guard, to, from)); + } + guards.push(canceledNavigationCheck); + return runGuardQueue(guards); + }) + .then(() => { + // check in components beforeRouteUpdate + guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from); + for (const record of updatingRecords) { + record.updateGuards.forEach(guard => { + guards.push(guardToPromiseFn(guard, to, from)); + }); + } + guards.push(canceledNavigationCheck); + // run the queue of per route beforeEnter guards + return runGuardQueue(guards); + }) + .then(() => { + // check the route beforeEnter + guards = []; + for (const record of to.matched) { + // do not trigger beforeEnter on reused views + if (record.beforeEnter && from.matched.indexOf(record) < 0) { + if (Array.isArray(record.beforeEnter)) { + for (const beforeEnter of record.beforeEnter) + guards.push(guardToPromiseFn(beforeEnter, to, from)); + } + else { + guards.push(guardToPromiseFn(record.beforeEnter, to, from)); + } + } + } + guards.push(canceledNavigationCheck); + // run the queue of per route beforeEnter guards + return runGuardQueue(guards); + }) + .then(() => { + // NOTE: at this point to.matched is normalized and does not contain any () => Promise + // clear existing enterCallbacks, these are added by extractComponentsGuards + to.matched.forEach(record => (record.enterCallbacks = {})); + // check in-component beforeRouteEnter + guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from); + guards.push(canceledNavigationCheck); + // run the queue of per route beforeEnter guards + return runGuardQueue(guards); + }) + .then(() => { + // check global guards beforeResolve + guards = []; + for (const guard of beforeResolveGuards.list()) { + guards.push(guardToPromiseFn(guard, to, from)); + } + guards.push(canceledNavigationCheck); + return runGuardQueue(guards); + }) + // catch any navigation canceled + .catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */) + ? err + : Promise.reject(err))); + } + function triggerAfterEach(to, from, failure) { + // navigation is confirmed, call afterGuards + // TODO: wrap with error handlers + for (const guard of afterGuards.list()) + guard(to, from, failure); + } + /** + * - Cleans up any navigation guards + * - Changes the url if necessary + * - Calls the scrollBehavior + */ + function finalizeNavigation(toLocation, from, isPush, replace, data) { + // a more recent navigation took place + const error = checkCanceledNavigation(toLocation, from); + if (error) + return error; + // only consider as push if it's not the first navigation + const isFirstNavigation = from === START_LOCATION_NORMALIZED; + const state = !isBrowser ? {} : history.state; + // change URL only if the user did a push/replace and if it's not the initial navigation because + // it's just reflecting the url + if (isPush) { + // on the initial navigation, we want to reuse the scroll position from + // history state if it exists + if (replace || isFirstNavigation) + routerHistory.replace(toLocation.fullPath, assign({ + scroll: isFirstNavigation && state && state.scroll, + }, data)); + else + routerHistory.push(toLocation.fullPath, data); + } + // accept current navigation + currentRoute.value = toLocation; + handleScroll(toLocation, from, isPush, isFirstNavigation); + markAsReady(); + } + let removeHistoryListener; + // attach listener to history to trigger navigations + function setupListeners() { + removeHistoryListener = routerHistory.listen((to, _from, info) => { + // cannot be a redirect route because it was in history + let toLocation = resolve(to); + // due to dynamic routing, and to hash history with manual navigation + // (manually changing the url or calling history.hash = '#/somewhere'), + // there could be a redirect record in history + const shouldRedirect = handleRedirectRecord(toLocation); + if (shouldRedirect) { + pushWithRedirect(assign(shouldRedirect, { replace: true }), toLocation).catch(noop); + return; + } + pendingLocation = toLocation; + const from = currentRoute.value; + // TODO: should be moved to web history? + if (isBrowser) { + saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition()); + } + navigate(toLocation, from) + .catch((error) => { + if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) { + return error; + } + if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) { + // Here we could call if (info.delta) routerHistory.go(-info.delta, + // false) but this is bug prone as we have no way to wait the + // navigation to be finished before calling pushWithRedirect. Using + // a setTimeout of 16ms seems to work but there is not guarantee for + // it to work on every browser. So Instead we do not restore the + // history entry and trigger a new navigation as requested by the + // navigation guard. + // the error is already handled by router.push we just want to avoid + // logging the error + pushWithRedirect(error.to, toLocation + // avoid an uncaught rejection, let push call triggerError + ).catch(noop); + // avoid the then branch + return Promise.reject(); + } + // do not restore history on unknown direction + if (info.delta) + routerHistory.go(-info.delta, false); + // unrecognized error, transfer to the global handler + return triggerError(error); + }) + .then((failure) => { + failure = + failure || + finalizeNavigation( + // after navigation, all matched components are resolved + toLocation, from, false); + // revert the navigation + if (failure && info.delta) + routerHistory.go(-info.delta, false); + triggerAfterEach(toLocation, from, failure); + }) + .catch(noop); + }); + } + // Initialization and Errors + let readyHandlers = useCallbacks(); + let errorHandlers = useCallbacks(); + let ready; + /** + * Trigger errorHandlers added via onError and throws the error as well + * @param error - error to throw + * @returns the error as a rejected promise + */ + function triggerError(error) { + markAsReady(error); + errorHandlers.list().forEach(handler => handler(error)); + return Promise.reject(error); + } + function isReady() { + if (ready && currentRoute.value !== START_LOCATION_NORMALIZED) + return Promise.resolve(); + return new Promise((resolve, reject) => { + readyHandlers.add([resolve, reject]); + }); + } + /** + * Mark the router as ready, resolving the promised returned by isReady(). Can + * only be called once, otherwise does nothing. + * @param err - optional error + */ + function markAsReady(err) { + if (ready) + return; + ready = true; + setupListeners(); + readyHandlers + .list() + .forEach(([resolve, reject]) => (err ? reject(err) : resolve())); + readyHandlers.reset(); + } + // Scroll behavior + function handleScroll(to, from, isPush, isFirstNavigation) { + const { scrollBehavior } = options; + if (!isBrowser || !scrollBehavior) + return Promise.resolve(); + let scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) || + ((isFirstNavigation || !isPush) && + history.state && + history.state.scroll) || + null; + return (0,vue__WEBPACK_IMPORTED_MODULE_0__.nextTick)() + .then(() => scrollBehavior(to, from, scrollPosition)) + .then(position => position && scrollToPosition(position)) + .catch(triggerError); + } + const go = (delta) => routerHistory.go(delta); + let started; + const installedApps = new Set(); + const router = { + currentRoute, + addRoute, + removeRoute, + hasRoute, + getRoutes, + resolve, + options, + push, + replace, + go, + back: () => go(-1), + forward: () => go(1), + beforeEach: beforeGuards.add, + beforeResolve: beforeResolveGuards.add, + afterEach: afterGuards.add, + onError: errorHandlers.add, + isReady, + install(app) { + const router = this; + app.component('RouterLink', RouterLink); + app.component('RouterView', RouterView); + app.config.globalProperties.$router = router; + Object.defineProperty(app.config.globalProperties, '$route', { + get: () => (0,vue__WEBPACK_IMPORTED_MODULE_0__.unref)(currentRoute), + }); + // this initial navigation is only necessary on client, on server it doesn't + // make sense because it will create an extra unnecessary navigation and could + // lead to problems + if (isBrowser && + // used for the initial navigation client side to avoid pushing + // multiple times when the router is used in multiple apps + !started && + currentRoute.value === START_LOCATION_NORMALIZED) { + // see above + started = true; + push(routerHistory.location).catch(err => { + if ((true)) + warn('Unexpected error when starting the router:', err); + }); + } + const reactiveRoute = {}; + for (let key in START_LOCATION_NORMALIZED) { + // @ts-ignore: the key matches + reactiveRoute[key] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => currentRoute.value[key]); + } + app.provide(routerKey, router); + app.provide(routeLocationKey, (0,vue__WEBPACK_IMPORTED_MODULE_0__.reactive)(reactiveRoute)); + app.provide(routerViewLocationKey, currentRoute); + let unmountApp = app.unmount; + installedApps.add(app); + app.unmount = function () { + installedApps.delete(app); + if (installedApps.size < 1) { + removeHistoryListener(); + currentRoute.value = START_LOCATION_NORMALIZED; + started = false; + ready = false; + } + unmountApp.call(this, arguments); + }; + if (true) { + addDevtools(app, router, matcher); + } + }, + }; + return router; +} +function runGuardQueue(guards) { + return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve()); +} +function extractChangingRecords(to, from) { + const leavingRecords = []; + const updatingRecords = []; + const enteringRecords = []; + const len = Math.max(from.matched.length, to.matched.length); + for (let i = 0; i < len; i++) { + const recordFrom = from.matched[i]; + if (recordFrom) { + if (to.matched.indexOf(recordFrom) < 0) + leavingRecords.push(recordFrom); + else + updatingRecords.push(recordFrom); + } + const recordTo = to.matched[i]; + if (recordTo) { + // the type doesn't matter because we are comparing per reference + if (from.matched.indexOf(recordTo) < 0) + enteringRecords.push(recordTo); + } + } + return [leavingRecords, updatingRecords, enteringRecords]; +} + +/** + * Returns the router instance. Equivalent to using `$router` inside + * templates. + */ +function useRouter() { + return (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routerKey); +} +/** + * Returns the current route location. Equivalent to using `$route` inside + * templates. + */ +function useRoute() { + return (0,vue__WEBPACK_IMPORTED_MODULE_0__.inject)(routeLocationKey); +} + + + + /***/ }), /***/ "./node_modules/vue/dist/vue.esm-bundler.js": @@ -36191,6 +39663,6 @@ const { /******/ /************************************************************************/ /******/ // run startup -/******/ return __webpack_require__.x(); +/******/ __webpack_require__.x(); /******/ })() ; \ No newline at end of file diff --git a/resources/js/app.js b/resources/js/app.js index a5195f33..e33e506a 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,14 +1,27 @@ require("./bootstrap"); import { createApp } from "vue"; +import { createRouter, createWebHashHistory } from "vue-router"; import store from "./store/index"; + import MainIndex from "./components/MainIndex.vue"; +import Navbar from "./components/Header/Navbar.vue"; +import Products from "./components/Products/Products.vue"; + +const routes = [ + { path: "/", component: Navbar }, + { path: "/products", component: Products }, +]; + +const router = createRouter({ + history: createWebHashHistory(), + routes, +}); createApp({ - components: { MainIndex }, + components: { Navbar, MainIndex, Products }, }) .use(store) + .use(router) .mount("#app"); - -//app.use(store); diff --git a/resources/js/components/Footer/Footer.vue b/resources/js/components/Footer/Footer.vue new file mode 100644 index 00000000..35b22077 --- /dev/null +++ b/resources/js/components/Footer/Footer.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/js/components/Header/Layout.vue b/resources/js/components/Header/Layout.vue new file mode 100644 index 00000000..75ad45d8 --- /dev/null +++ b/resources/js/components/Header/Layout.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/resources/js/components/Header/MobileMenu.vue b/resources/js/components/Header/MobileMenu.vue new file mode 100644 index 00000000..4d9c440b --- /dev/null +++ b/resources/js/components/Header/MobileMenu.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/resources/js/components/Header/Navbar.vue b/resources/js/components/Header/Navbar.vue index a596de0b..1edbde65 100644 --- a/resources/js/components/Header/Navbar.vue +++ b/resources/js/components/Header/Navbar.vue @@ -1,47 +1,85 @@ diff --git a/resources/js/components/Layouts/Layout.vue b/resources/js/components/Layouts/Layout.vue new file mode 100644 index 00000000..5e0d4aa9 --- /dev/null +++ b/resources/js/components/Layouts/Layout.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/resources/js/components/Products/Products.vue b/resources/js/components/Products/Products.vue new file mode 100644 index 00000000..7aba153f --- /dev/null +++ b/resources/js/components/Products/Products.vue @@ -0,0 +1,7 @@ + + + + +