12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629 |
- /*!
- * vue-router v4.0.16
- * (c) 2022 Eduardo San Martin Morote
- * @license MIT
- */
- var VueRouter = (function (exports, vue) {
- 'use strict';
- const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
- const PolySymbol = (name) =>
- // vr = vue router
- hasSymbol
- ? Symbol('[vue-router]: ' + name )
- : ('[vue-router]: ' ) + 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('router view location matched' );
- /**
- * 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('router view depth' );
- /**
- * Allows overriding the router instance returned by `useRouter` in tests. r
- * stands for router
- *
- * @internal
- */
- const routerKey = /*#__PURE__*/ PolySymbol('router' );
- /**
- * Allows overriding the current route returned by `useRoute` in tests. rl
- * stands for route location
- *
- * @internal
- */
- const routeLocationKey = /*#__PURE__*/ PolySymbol('route location' );
- /**
- * Allows overriding the current route used by router-view. Internally this is
- * used when the `route` prop is passed.
- *
- * @internal
- */
- const routerViewLocationKey = /*#__PURE__*/ PolySymbol('router view location' );
- 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;
- }
- const 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) {
- const 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().startsWith(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) {
- const aLastIndex = a.matched.length - 1;
- const 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 (const 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 (!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 <base> 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) {
- const 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 (typeof position.el === 'string') {
- if (!isIdSelector || !document.getElementById(position.el.slice(1))) {
- try {
- const 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) {
- 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<ScrollPositionEntry extends HistoryStateValue, ScrollPosition extends ScrollPositionEntry> {
- // // 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<ScrollPosition> = {
- // 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 bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
- const hashPos = base.indexOf('#');
- if (hashPos > -1) {
- let slicePos = hash.includes(base.slice(hashPos))
- ? base.slice(hashPos).length
- : 1;
- let pathFromHash = hash.slice(slicePos);
- // prepend the starting slash to hash so the url starts with /#
- 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
- const currentLocation = {
- value: createCurrentLocation(base, location),
- };
- const 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/router/issues/685 where a base of
- * `/folder/#` but a base of `/` would erase the `/folder/` section. If
- * there is no host, the `<base>` 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) {
- {
- warn('Error with push/replace State', err);
- }
- // 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/router/issues/366
- historyState.value, history.state, {
- forward: to,
- scroll: computeScrollPosition(),
- });
- if (!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', {
- enumerable: true,
- get: () => historyNavigation.location.value,
- });
- Object.defineProperty(routerHistory, 'state', {
- enumerable: true,
- 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;
- base = normalizeBase(base);
- 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 (const callback of listeners) {
- callback(to, from, info);
- }
- }
- const routerHistory = {
- // rewritten by Object.defineProperty
- location: START,
- // TODO: should be kept in queue
- 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 = [];
- queue = [START];
- position = 0;
- },
- 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', {
- enumerable: true,
- 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 `<base>` 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 `<base>` 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.includes('#'))
- base += '#';
- if (!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('navigation failure' );
- /**
- * Enumeration with all possible types for navigation failures. Can be passed to
- * {@link isNavigationFailure} to check for specific failures.
- */
- exports.NavigationFailureType = void 0;
- (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";
- })(exports.NavigationFailureType || (exports.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) {
- // keep full error messages in cjs versions
- {
- return assign(new Error(ErrorTypeMessages[type](params)), {
- type,
- [NavigationFailureSymbol]: true,
- }, params);
- }
- }
- 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 "/"
- const 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 =
- // avoid an optional / if there are more segments e.g. /:p?-static
- // or /:p?-:p2
- optional && segment.length < 2
- ? `(?:/${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) {
- // if we have more than one optional param like /:a?-static and there are more segments, we don't need to
- // care about the optional param
- if (segment.length < 2 && segments.length > 1) {
- // 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 (Math.abs(bScore.length - aScore.length) === 1) {
- if (isLastScoreNegative(aScore))
- return 1;
- if (isLastScoreNegative(bScore))
- return -1;
- }
- // 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
- }
- /**
- * This allows detecting splats at the end of a path: /home/:id(.*)*
- *
- * @param score - score to check
- * @returns true if the last entry is negative
- */
- function isLastScoreNegative(score) {
- const last = score[score.length - 1];
- return score.length > 0 && last[last.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<string, Token[][]>()
- function tokenizePath(path) {
- if (!path)
- return [[]];
- if (path === '/')
- return [[ROOT_TOKEN]];
- if (!path.startsWith('/')) {
- throw new Error(`Route paths should start with a "/": "${path}" should be "/${path}".`
- );
- }
- // 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
- {
- 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
- const isRootAdd = !originalRecord;
- const 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,
- // the aliases are always of the same kind as the original since they
- // are defined on the same record
- }));
- }
- }
- let matcher;
- let originalMatcher;
- for (const normalizedRecord of normalizedRecords) {
- const { 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] !== '/') {
- const parentPath = parent.record.path;
- const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
- normalizedRecord.path =
- parent.record.path + (path && connectingSlash + path);
- }
- if (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 (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);
- {
- 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) {
- const 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 {
- const 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;
- while (i < matchers.length &&
- comparePathParserScore(matcher, matchers[i]) >= 0 &&
- // Adding children with empty path should still appear before the parent
- // https://github.com/vuejs/router/issues/1124
- (matcher.record.path !== matchers[i].record.path ||
- !isRecordChildOf(matcher, matchers[i])))
- 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 (!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/router.`);
- }
- 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) {
- const newParams = {};
- for (const 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 (const 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) {
- const options = {};
- for (const 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);
- }
- /**
- * Check if a path and its alias have the same required params
- *
- * @param a - original record
- * @param b - alias record
- */
- function checkSameParams(a, b) {
- for (const key of a.keys) {
- if (!key.optional && !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 (const key of b.keys) {
- if (!key.optional && !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 (const 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}".`);
- }
- }
- function isRecordChildOf(record, parent) {
- return parent.children.some(child => child === record || isRecordChildOf(record, child));
- }
- /**
- * 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. If `text` is `null` or `undefined`, returns an empty
- * string instead.
- *
- * @param text - string to encode
- * @returns encoded string
- */
- function encodeParam(text) {
- return text == null ? '' : 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) {
- 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
- const eqPos = searchParam.indexOf('=');
- const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
- const 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) {
- const value = query[key];
- key = encodeQueryKey(key);
- if (value == null) {
- // only null adds the value
- if (value !== undefined) {
- search += (search.length ? '&' : '') + key;
- }
- continue;
- }
- // keep null values
- const values = Array.isArray(value)
- ? value.map(v => v && encodeQueryValue(v))
- : [value && encodeQueryValue(value)];
- values.forEach(value => {
- // skip undefined values in arrays as if they were not present
- // smaller code than using filter
- if (value !== undefined) {
- // only append & with non-empty search
- search += (search.length ? '&' : '') + key;
- if (value != null)
- search += '=' + value;
- }
- });
- }
- 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 (const key in query) {
- const 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);
- };
- vue.onUnmounted(removeFromList);
- vue.onDeactivated(removeFromList);
- vue.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 (!vue.getCurrentInstance()) {
- warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function');
- return;
- }
- const activeRecord = vue.inject(matchedRouteKey,
- // to avoid warning
- {}).value;
- if (!activeRecord) {
- warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside of a component child of <router-view>. Maybe you called it inside of App.vue?');
- 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 (!vue.getCurrentInstance()) {
- warn('getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function');
- return;
- }
- const activeRecord = vue.inject(matchedRouteKey,
- // to avoid warning
- {}).value;
- if (!activeRecord) {
- warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside of a component child of <router-view>. Maybe you called it inside of App.vue?');
- 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, canOnlyBeCalledOnce(next, to, from) );
- let guardCall = Promise.resolve(guardReturn);
- if (guard.length < 3)
- guardCall = guardCall.then(next);
- if (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-expect-error: _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-expect-error: _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-expect-error: 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 (!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.`);
- const 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
- const 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 (!('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);
- }
- 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;
- // __vccOpts is added by vue-class-component and contain the regular options
- const options = resolvedComponent.__vccOpts || resolvedComponent;
- const guard = options[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 = vue.inject(routerKey);
- const currentRoute = vue.inject(routeLocationKey);
- const route = vue.computed(() => router.resolve(vue.unref(props.to)));
- const activeRecordIndex = vue.computed(() => {
- const { matched } = route.value;
- const { length } = matched;
- const routeMatched = matched[length - 1];
- const currentMatched = currentRoute.matched;
- if (!routeMatched || !currentMatched.length)
- return -1;
- const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));
- if (index > -1)
- return index;
- // possible parent record
- const 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 = vue.computed(() => activeRecordIndex.value > -1 &&
- includesParams(currentRoute.params, route.value.params));
- const isExactActive = vue.computed(() => activeRecordIndex.value > -1 &&
- activeRecordIndex.value === currentRoute.matched.length - 1 &&
- isSameRouteLocationParams(currentRoute.params, route.value.params));
- function navigate(e = {}) {
- if (guardEvent(e)) {
- return router[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to)
- // avoid uncaught errors are they are logged anyway
- ).catch(noop);
- }
- return Promise.resolve();
- }
- // devtools only
- if (isBrowser) {
- const instance = vue.getCurrentInstance();
- if (instance) {
- const linkContextDevtools = {
- route: route.value,
- isActive: isActive.value,
- isExactActive: isExactActive.value,
- };
- // @ts-expect-error: this is internal
- instance.__vrl_devtools = instance.__vrl_devtools || [];
- // @ts-expect-error: this is internal
- instance.__vrl_devtools.push(linkContextDevtools);
- vue.watchEffect(() => {
- linkContextDevtools.route = route.value;
- linkContextDevtools.isActive = isActive.value;
- linkContextDevtools.isExactActive = isExactActive.value;
- }, { flush: 'post' });
- }
- }
- return {
- route,
- href: vue.computed(() => route.value.href),
- isActive,
- isExactActive,
- navigate,
- };
- }
- const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({
- name: 'RouterLink',
- compatConfig: { MODE: 3 },
- props: {
- to: {
- type: [String, Object],
- required: true,
- },
- replace: Boolean,
- activeClass: String,
- // inactiveClass: String,
- exactActiveClass: String,
- custom: Boolean,
- ariaCurrentValue: {
- type: String,
- default: 'page',
- },
- },
- useLink,
- setup(props, { slots }) {
- const link = vue.reactive(useLink(props));
- const { options } = vue.inject(routerKey);
- const elClass = vue.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
- : vue.h('a', {
- 'aria-current': link.isExactActive
- ? props.ariaCurrentValue
- : null,
- href: link.href,
- // this would override user added attrs but Vue will still add
- // the listener so we end up triggering both
- onClick: link.navigate,
- 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-expect-error getAttribute does exist
- if (e.currentTarget && e.currentTarget.getAttribute) {
- // @ts-expect-error 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 (const key in inner) {
- const innerValue = inner[key];
- const 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__*/ vue.defineComponent({
- name: 'RouterView',
- // #674 we manually inherit them
- inheritAttrs: false,
- props: {
- name: {
- type: String,
- default: 'default',
- },
- route: Object,
- },
- // Better compat for @vue/compat users
- // https://github.com/vuejs/router/issues/1315
- compatConfig: { MODE: 3 },
- setup(props, { attrs, slots }) {
- warnDeprecatedUsage();
- const injectedRoute = vue.inject(routerViewLocationKey);
- const routeToDisplay = vue.computed(() => props.route || injectedRoute.value);
- const depth = vue.inject(viewDepthKey, 0);
- const matchedRouteRef = vue.computed(() => routeToDisplay.value.matched[depth]);
- vue.provide(viewDepthKey, depth + 1);
- vue.provide(matchedRouteKey, matchedRouteRef);
- vue.provide(routerViewLocationKey, routeToDisplay);
- const viewRef = vue.ref();
- // watch at the same time the component instance, the route record we are
- // rendering, and the name
- vue.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. With async setup, the
- // mounting component will mount before the matchedRoute changes,
- // making instance === oldInstance, so we check if guards have been
- // added before. This works because we remove guards when
- // unmounting/deactivating components
- if (from && from !== to && instance && instance === oldInstance) {
- if (!to.leaveGuards.size) {
- to.leaveGuards = from.leaveGuards;
- }
- if (!to.updateGuards.size) {
- 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 = vue.h(ViewComponent, assign({}, routeProps, attrs, {
- onVnodeUnmounted,
- ref: viewRef,
- }));
- if (isBrowser &&
- component.ref) {
- // TODO: can display if it's an alias, its props
- const info = {
- depth,
- name: matchedRoute.name,
- path: matchedRoute.path,
- meta: matchedRoute.meta,
- };
- const internalInstances = Array.isArray(component.ref)
- ? component.ref.map(r => r.i)
- : [component.ref.i];
- internalInstances.forEach(instance => {
- // @ts-expect-error
- instance.__vrv_devtools = info;
- });
- }
- return (
- // pass the vnode to the slot as a prop.
- // h and <component :is="..."> 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 <transition> & <keep-alive>
- // due to functional component being no longer eager in Vue 3
- function warnDeprecatedUsage() {
- const instance = vue.getCurrentInstance();
- const parentName = instance.parent && instance.parent.type.name;
- if (parentName &&
- (parentName === 'KeepAlive' || parentName.includes('Transition'))) {
- const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';
- warn(`<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
- `Use slot props instead:\n\n` +
- `<router-view v-slot="{ Component }">\n` +
- ` <${comp}>\n` +
- ` <component :is="Component" />\n` +
- ` </${comp}>\n` +
- `</router-view>`);
- }
- }
- function getDevtoolsGlobalHook() {
- return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
- }
- function getTarget() {
- // @ts-ignore
- return (typeof navigator !== 'undefined' && typeof window !== 'undefined')
- ? window
- : typeof global !== 'undefined'
- ? global
- : {};
- }
- const isProxyAvailable = typeof Proxy === 'function';
- const HOOK_SETUP = 'devtools-plugin:setup';
- const HOOK_PLUGIN_SETTINGS_SET = 'plugin:settings:set';
- class ApiProxy {
- constructor(plugin, hook) {
- this.target = null;
- this.targetQueue = [];
- this.onQueue = [];
- this.plugin = plugin;
- this.hook = hook;
- const defaultSettings = {};
- if (plugin.settings) {
- for (const id in plugin.settings) {
- const item = plugin.settings[id];
- defaultSettings[id] = item.defaultValue;
- }
- }
- const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
- let currentSettings = Object.assign({}, defaultSettings);
- try {
- const raw = localStorage.getItem(localSettingsSaveId);
- const data = JSON.parse(raw);
- Object.assign(currentSettings, data);
- }
- catch (e) {
- // noop
- }
- this.fallbacks = {
- getSettings() {
- return currentSettings;
- },
- setSettings(value) {
- try {
- localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
- }
- catch (e) {
- // noop
- }
- currentSettings = value;
- },
- };
- if (hook) {
- hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
- if (pluginId === this.plugin.id) {
- this.fallbacks.setSettings(value);
- }
- });
- }
- this.proxiedOn = new Proxy({}, {
- get: (_target, prop) => {
- if (this.target) {
- return this.target.on[prop];
- }
- else {
- return (...args) => {
- this.onQueue.push({
- method: prop,
- args,
- });
- };
- }
- },
- });
- this.proxiedTarget = new Proxy({}, {
- get: (_target, prop) => {
- if (this.target) {
- return this.target[prop];
- }
- else if (prop === 'on') {
- return this.proxiedOn;
- }
- else if (Object.keys(this.fallbacks).includes(prop)) {
- return (...args) => {
- this.targetQueue.push({
- method: prop,
- args,
- resolve: () => { },
- });
- return this.fallbacks[prop](...args);
- };
- }
- else {
- return (...args) => {
- return new Promise(resolve => {
- this.targetQueue.push({
- method: prop,
- args,
- resolve,
- });
- });
- };
- }
- },
- });
- }
- async setRealTarget(target) {
- this.target = target;
- for (const item of this.onQueue) {
- this.target.on[item.method](...item.args);
- }
- for (const item of this.targetQueue) {
- item.resolve(await this.target[item.method](...item.args));
- }
- }
- }
- function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
- const descriptor = pluginDescriptor;
- const target = getTarget();
- const hook = getDevtoolsGlobalHook();
- const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
- if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
- hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
- }
- else {
- const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
- const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
- list.push({
- pluginDescriptor: descriptor,
- setupFn,
- proxy,
- });
- if (proxy)
- setupFn(proxy.proxiedTarget);
- }
- }
- 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
- // make sure we are not registering the devtool twice
- if (router.__hasDevtools)
- return;
- router.__hasDevtools = true;
- // increment to support multiple router instances
- const id = routerId++;
- setupDevtoolsPlugin({
- id: 'org.vuejs.router' + (id ? '.' + id : ''),
- label: 'Vue Router',
- packageName: 'vue-router',
- homepage: 'https://router.vuejs.org',
- logo: 'https://router.vuejs.org/logo.png',
- componentStateTypes: ['Routing'],
- app,
- }, api => {
- // display state added by the router
- 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'),
- });
- }
- });
- // mark router-link as active and display tags on router views
- api.on.visitComponentTree(({ treeNode: node, componentInstance }) => {
- if (componentInstance.__vrv_devtools) {
- const info = componentInstance.__vrv_devtools;
- node.tags.push({
- label: (info.name ? `${info.name.toString()}: ` : '') + info.path,
- textColor: 0,
- tooltip: 'This component is rendered by <router-view>',
- backgroundColor: PINK_500,
- });
- }
- // if multiple useLink are used
- if (Array.isArray(componentInstance.__vrl_devtools)) {
- componentInstance.__devtoolsApi = api;
- componentInstance.__vrl_devtools.forEach(devtoolsData => {
- let backgroundColor = ORANGE_400;
- let tooltip = '';
- if (devtoolsData.isExactActive) {
- backgroundColor = LIME_500;
- tooltip = 'This is exactly active';
- }
- else if (devtoolsData.isActive) {
- backgroundColor = BLUE_600;
- tooltip = 'This link is active';
- }
- node.tags.push({
- label: devtoolsData.route.path,
- textColor: 0,
- tooltip,
- backgroundColor,
- });
- });
- }
- });
- vue.watch(router.currentRoute, () => {
- // refresh active state
- refreshRoutesView();
- api.notifyComponentUpdate();
- api.sendInspectorTree(routerInspectorId);
- api.sendInspectorState(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, to) => {
- api.addTimelineEvent({
- layerId: navigationsLayerId,
- event: {
- title: 'Error during Navigation',
- subtitle: to.fullPath,
- logType: 'error',
- time: api.now(),
- data: { error },
- groupId: to.meta.__navigationId,
- },
- });
- });
- // attached to `meta` and used to group events
- let navigationId = 0;
- router.beforeEach((to, from) => {
- const data = {
- guard: formatDisplay('beforeEach'),
- from: formatRouteLocation(from, 'Current Location during this navigation'),
- to: formatRouteLocation(to, 'Target location'),
- };
- // Used to group navigations together, hide from devtools
- Object.defineProperty(to.meta, '__navigationId', {
- value: navigationId++,
- });
- api.addTimelineEvent({
- layerId: navigationsLayerId,
- event: {
- time: api.now(),
- title: 'Start of navigation',
- subtitle: to.fullPath,
- data,
- groupId: to.meta.__navigationId,
- },
- });
- });
- 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: {
- title: 'End of navigation',
- subtitle: to.fullPath,
- time: api.now(),
- data,
- logType: failure ? 'warning' : 'default',
- groupId: to.meta.__navigationId,
- },
- });
- });
- /**
- * 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 = record.__vd_id;
- if (id == null) {
- id = String(routeRecordId++);
- record.__vd_id = id;
- }
- return {
- id,
- label: record.path,
- tags,
- 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 (const key in obj) {
- if (!keys.includes(key)) {
- // @ts-expect-error
- 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);
- const parseQuery$1 = options.parseQuery || parseQuery;
- const stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
- const routerHistory = options.history;
- if (!routerHistory)
- throw new Error('Provide the "history" option when calling "createRouter()":' +
- ' https://next.router.vuejs.org/api/#history.');
- const beforeGuards = useCallbacks();
- const beforeResolveGuards = useCallbacks();
- const afterGuards = useCallbacks();
- const currentRoute = vue.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 =
- // @ts-expect-error: intentionally avoid the type check
- 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) {
- const recordMatcher = matcher.getRecordMatcher(name);
- if (recordMatcher) {
- matcher.removeRoute(recordMatcher);
- }
- else {
- 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') {
- const locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
- const matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
- const href = routerHistory.createHref(locationNormalized.fullPath);
- {
- 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 ('params' in rawLocation &&
- !('name' in rawLocation) &&
- // @ts-expect-error: the type is never
- Object.keys(rawLocation.params).length) {
- warn(`Path "${
- // @ts-expect-error: the type is never
- 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 {
- // remove any nullish param
- const targetParams = assign({}, rawLocation.params);
- for (const key in targetParams) {
- if (targetParams[key] == null) {
- delete targetParams[key];
- }
- }
- // 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);
- }
- const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
- const hash = rawLocation.hash || '';
- if (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,
- }));
- const href = routerHistory.createHref(fullPath);
- {
- 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/router/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.includes('?') || newTargetLocation.includes('#')
- ? (newTargetLocation = locationAsObject(newTargetLocation))
- : // force empty params
- { path: newTargetLocation };
- // @ts-expect-error: force empty params when a string is passed to let
- // the router parse them again
- newTargetLocation.params = {};
- }
- if (!('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)
- ? // navigation redirects still mark the router as ready
- isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)
- ? error
- : markAsReady(error) // also returns the error
- : // reject any unknown error
- triggerError(error, toLocation, from))
- .then((failure) => {
- if (failure) {
- if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
- if (// 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-expect-error: added only in dev
- (redirectedFrom._count = redirectedFrom._count
- ? // @ts-expect-error
- 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.includes(record)) {
- 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<Component>
- // 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() {
- // avoid setting up listeners twice due to an invalid first navigation
- if (removeHistoryListener)
- return;
- removeHistoryListener = routerHistory.listen((to, _from, info) => {
- // cannot be a redirect route because it was in history
- const 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
- )
- .then(failure => {
- // manual change in hash history #916 ending up in the URL not
- // changing but it was changed by the manual url change, so we
- // need to manually change it ourselves
- if (isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ |
- 16 /* NAVIGATION_DUPLICATED */) &&
- !info.delta &&
- info.type === NavigationType.pop) {
- routerHistory.go(-1, false);
- }
- })
- .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, toLocation, from);
- })
- .then((failure) => {
- failure =
- failure ||
- finalizeNavigation(
- // after navigation, all matched components are resolved
- toLocation, from, false);
- // revert the navigation
- if (failure) {
- if (info.delta) {
- routerHistory.go(-info.delta, false);
- }
- else if (info.type === NavigationType.pop &&
- isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ | 16 /* NAVIGATION_DUPLICATED */)) {
- // manual change in hash history #916
- // it's like a push but lacks the information of the direction
- routerHistory.go(-1, 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
- * @param to - location we were navigating to when the error happened
- * @param from - location we were navigating from when the error happened
- * @returns the error as a rejected promise
- */
- function triggerError(error, to, from) {
- markAsReady(error);
- const list = errorHandlers.list();
- if (list.length) {
- list.forEach(handler => handler(error, to, from));
- }
- else {
- {
- warn('uncaught error during route navigation:');
- }
- console.error(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]);
- });
- }
- function markAsReady(err) {
- if (!ready) {
- // still not ready if an error happened
- ready = !err;
- setupListeners();
- readyHandlers
- .list()
- .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
- readyHandlers.reset();
- }
- return err;
- }
- // Scroll behavior
- function handleScroll(to, from, isPush, isFirstNavigation) {
- const { scrollBehavior } = options;
- if (!isBrowser || !scrollBehavior)
- return Promise.resolve();
- const scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||
- ((isFirstNavigation || !isPush) &&
- history.state &&
- history.state.scroll) ||
- null;
- return vue.nextTick()
- .then(() => scrollBehavior(to, from, scrollPosition))
- .then(position => position && scrollToPosition(position))
- .catch(err => triggerError(err, to, from));
- }
- 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', {
- enumerable: true,
- get: () => vue.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 => {
- warn('Unexpected error when starting the router:', err);
- });
- }
- const reactiveRoute = {};
- for (const key in START_LOCATION_NORMALIZED) {
- // @ts-expect-error: the key matches
- reactiveRoute[key] = vue.computed(() => currentRoute.value[key]);
- }
- app.provide(routerKey, router);
- app.provide(routeLocationKey, vue.reactive(reactiveRoute));
- app.provide(routerViewLocationKey, currentRoute);
- const unmountApp = app.unmount;
- installedApps.add(app);
- app.unmount = function () {
- installedApps.delete(app);
- // the router is not attached to an app anymore
- if (installedApps.size < 1) {
- // invalidate the current navigation
- pendingLocation = START_LOCATION_NORMALIZED;
- removeHistoryListener && removeHistoryListener();
- removeHistoryListener = null;
- currentRoute.value = START_LOCATION_NORMALIZED;
- started = false;
- ready = false;
- }
- unmountApp();
- };
- if (isBrowser) {
- 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.find(record => isSameRouteRecord(record, recordFrom)))
- updatingRecords.push(recordFrom);
- else
- leavingRecords.push(recordFrom);
- }
- const recordTo = to.matched[i];
- if (recordTo) {
- // the type doesn't matter because we are comparing per reference
- if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
- enteringRecords.push(recordTo);
- }
- }
- }
- return [leavingRecords, updatingRecords, enteringRecords];
- }
- /**
- * Returns the router instance. Equivalent to using `$router` inside
- * templates.
- */
- function useRouter() {
- return vue.inject(routerKey);
- }
- /**
- * Returns the current route location. Equivalent to using `$route` inside
- * templates.
- */
- function useRoute() {
- return vue.inject(routeLocationKey);
- }
- exports.RouterLink = RouterLink;
- exports.RouterView = RouterView;
- exports.START_LOCATION = START_LOCATION_NORMALIZED;
- exports.createMemoryHistory = createMemoryHistory;
- exports.createRouter = createRouter;
- exports.createRouterMatcher = createRouterMatcher;
- exports.createWebHashHistory = createWebHashHistory;
- exports.createWebHistory = createWebHistory;
- exports.isNavigationFailure = isNavigationFailure;
- exports.matchedRouteKey = matchedRouteKey;
- exports.onBeforeRouteLeave = onBeforeRouteLeave;
- exports.onBeforeRouteUpdate = onBeforeRouteUpdate;
- exports.parseQuery = parseQuery;
- exports.routeLocationKey = routeLocationKey;
- exports.routerKey = routerKey;
- exports.routerViewLocationKey = routerViewLocationKey;
- exports.stringifyQuery = stringifyQuery;
- exports.useLink = useLink;
- exports.useRoute = useRoute;
- exports.useRouter = useRouter;
- exports.viewDepthKey = viewDepthKey;
- Object.defineProperty(exports, '__esModule', { value: true });
- return exports;
- })({}, Vue);
|