router.js 194 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072
  1. /**
  2. * @remix-run/router v1.23.2
  3. *
  4. * Copyright (c) Remix Software Inc.
  5. *
  6. * This source code is licensed under the MIT license found in the
  7. * LICENSE.md file in the root directory of this source tree.
  8. *
  9. * @license MIT
  10. */
  11. function _extends() {
  12. _extends = Object.assign ? Object.assign.bind() : function (target) {
  13. for (var i = 1; i < arguments.length; i++) {
  14. var source = arguments[i];
  15. for (var key in source) {
  16. if (Object.prototype.hasOwnProperty.call(source, key)) {
  17. target[key] = source[key];
  18. }
  19. }
  20. }
  21. return target;
  22. };
  23. return _extends.apply(this, arguments);
  24. }
  25. ////////////////////////////////////////////////////////////////////////////////
  26. //#region Types and Constants
  27. ////////////////////////////////////////////////////////////////////////////////
  28. /**
  29. * Actions represent the type of change to a location value.
  30. */
  31. var Action;
  32. (function (Action) {
  33. /**
  34. * A POP indicates a change to an arbitrary index in the history stack, such
  35. * as a back or forward navigation. It does not describe the direction of the
  36. * navigation, only that the current index changed.
  37. *
  38. * Note: This is the default action for newly created history objects.
  39. */
  40. Action["Pop"] = "POP";
  41. /**
  42. * A PUSH indicates a new entry being added to the history stack, such as when
  43. * a link is clicked and a new page loads. When this happens, all subsequent
  44. * entries in the stack are lost.
  45. */
  46. Action["Push"] = "PUSH";
  47. /**
  48. * A REPLACE indicates the entry at the current index in the history stack
  49. * being replaced by a new one.
  50. */
  51. Action["Replace"] = "REPLACE";
  52. })(Action || (Action = {}));
  53. const PopStateEventType = "popstate";
  54. /**
  55. * Memory history stores the current location in memory. It is designed for use
  56. * in stateful non-browser environments like tests and React Native.
  57. */
  58. function createMemoryHistory(options) {
  59. if (options === void 0) {
  60. options = {};
  61. }
  62. let {
  63. initialEntries = ["/"],
  64. initialIndex,
  65. v5Compat = false
  66. } = options;
  67. let entries; // Declare so we can access from createMemoryLocation
  68. entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
  69. let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
  70. let action = Action.Pop;
  71. let listener = null;
  72. function clampIndex(n) {
  73. return Math.min(Math.max(n, 0), entries.length - 1);
  74. }
  75. function getCurrentLocation() {
  76. return entries[index];
  77. }
  78. function createMemoryLocation(to, state, key) {
  79. if (state === void 0) {
  80. state = null;
  81. }
  82. let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
  83. warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
  84. return location;
  85. }
  86. function createHref(to) {
  87. return typeof to === "string" ? to : createPath(to);
  88. }
  89. let history = {
  90. get index() {
  91. return index;
  92. },
  93. get action() {
  94. return action;
  95. },
  96. get location() {
  97. return getCurrentLocation();
  98. },
  99. createHref,
  100. createURL(to) {
  101. return new URL(createHref(to), "http://localhost");
  102. },
  103. encodeLocation(to) {
  104. let path = typeof to === "string" ? parsePath(to) : to;
  105. return {
  106. pathname: path.pathname || "",
  107. search: path.search || "",
  108. hash: path.hash || ""
  109. };
  110. },
  111. push(to, state) {
  112. action = Action.Push;
  113. let nextLocation = createMemoryLocation(to, state);
  114. index += 1;
  115. entries.splice(index, entries.length, nextLocation);
  116. if (v5Compat && listener) {
  117. listener({
  118. action,
  119. location: nextLocation,
  120. delta: 1
  121. });
  122. }
  123. },
  124. replace(to, state) {
  125. action = Action.Replace;
  126. let nextLocation = createMemoryLocation(to, state);
  127. entries[index] = nextLocation;
  128. if (v5Compat && listener) {
  129. listener({
  130. action,
  131. location: nextLocation,
  132. delta: 0
  133. });
  134. }
  135. },
  136. go(delta) {
  137. action = Action.Pop;
  138. let nextIndex = clampIndex(index + delta);
  139. let nextLocation = entries[nextIndex];
  140. index = nextIndex;
  141. if (listener) {
  142. listener({
  143. action,
  144. location: nextLocation,
  145. delta
  146. });
  147. }
  148. },
  149. listen(fn) {
  150. listener = fn;
  151. return () => {
  152. listener = null;
  153. };
  154. }
  155. };
  156. return history;
  157. }
  158. /**
  159. * Browser history stores the location in regular URLs. This is the standard for
  160. * most web apps, but it requires some configuration on the server to ensure you
  161. * serve the same app at multiple URLs.
  162. *
  163. * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
  164. */
  165. function createBrowserHistory(options) {
  166. if (options === void 0) {
  167. options = {};
  168. }
  169. function createBrowserLocation(window, globalHistory) {
  170. let {
  171. pathname,
  172. search,
  173. hash
  174. } = window.location;
  175. return createLocation("", {
  176. pathname,
  177. search,
  178. hash
  179. },
  180. // state defaults to `null` because `window.history.state` does
  181. globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
  182. }
  183. function createBrowserHref(window, to) {
  184. return typeof to === "string" ? to : createPath(to);
  185. }
  186. return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
  187. }
  188. /**
  189. * Hash history stores the location in window.location.hash. This makes it ideal
  190. * for situations where you don't want to send the location to the server for
  191. * some reason, either because you do cannot configure it or the URL space is
  192. * reserved for something else.
  193. *
  194. * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
  195. */
  196. function createHashHistory(options) {
  197. if (options === void 0) {
  198. options = {};
  199. }
  200. function createHashLocation(window, globalHistory) {
  201. let {
  202. pathname = "/",
  203. search = "",
  204. hash = ""
  205. } = parsePath(window.location.hash.substr(1));
  206. // Hash URL should always have a leading / just like window.location.pathname
  207. // does, so if an app ends up at a route like /#something then we add a
  208. // leading slash so all of our path-matching behaves the same as if it would
  209. // in a browser router. This is particularly important when there exists a
  210. // root splat route (<Route path="*">) since that matches internally against
  211. // "/*" and we'd expect /#something to 404 in a hash router app.
  212. if (!pathname.startsWith("/") && !pathname.startsWith(".")) {
  213. pathname = "/" + pathname;
  214. }
  215. return createLocation("", {
  216. pathname,
  217. search,
  218. hash
  219. },
  220. // state defaults to `null` because `window.history.state` does
  221. globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
  222. }
  223. function createHashHref(window, to) {
  224. let base = window.document.querySelector("base");
  225. let href = "";
  226. if (base && base.getAttribute("href")) {
  227. let url = window.location.href;
  228. let hashIndex = url.indexOf("#");
  229. href = hashIndex === -1 ? url : url.slice(0, hashIndex);
  230. }
  231. return href + "#" + (typeof to === "string" ? to : createPath(to));
  232. }
  233. function validateHashLocation(location, to) {
  234. warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
  235. }
  236. return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
  237. }
  238. function invariant(value, message) {
  239. if (value === false || value === null || typeof value === "undefined") {
  240. throw new Error(message);
  241. }
  242. }
  243. function warning(cond, message) {
  244. if (!cond) {
  245. // eslint-disable-next-line no-console
  246. if (typeof console !== "undefined") console.warn(message);
  247. try {
  248. // Welcome to debugging history!
  249. //
  250. // This error is thrown as a convenience, so you can more easily
  251. // find the source for a warning that appears in the console by
  252. // enabling "pause on exceptions" in your JavaScript debugger.
  253. throw new Error(message);
  254. // eslint-disable-next-line no-empty
  255. } catch (e) {}
  256. }
  257. }
  258. function createKey() {
  259. return Math.random().toString(36).substr(2, 8);
  260. }
  261. /**
  262. * For browser-based histories, we combine the state and key into an object
  263. */
  264. function getHistoryState(location, index) {
  265. return {
  266. usr: location.state,
  267. key: location.key,
  268. idx: index
  269. };
  270. }
  271. /**
  272. * Creates a Location object with a unique key from the given Path
  273. */
  274. function createLocation(current, to, state, key) {
  275. if (state === void 0) {
  276. state = null;
  277. }
  278. let location = _extends({
  279. pathname: typeof current === "string" ? current : current.pathname,
  280. search: "",
  281. hash: ""
  282. }, typeof to === "string" ? parsePath(to) : to, {
  283. state,
  284. // TODO: This could be cleaned up. push/replace should probably just take
  285. // full Locations now and avoid the need to run through this flow at all
  286. // But that's a pretty big refactor to the current test suite so going to
  287. // keep as is for the time being and just let any incoming keys take precedence
  288. key: to && to.key || key || createKey()
  289. });
  290. return location;
  291. }
  292. /**
  293. * Creates a string URL path from the given pathname, search, and hash components.
  294. */
  295. function createPath(_ref) {
  296. let {
  297. pathname = "/",
  298. search = "",
  299. hash = ""
  300. } = _ref;
  301. if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
  302. if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
  303. return pathname;
  304. }
  305. /**
  306. * Parses a string URL path into its separate pathname, search, and hash components.
  307. */
  308. function parsePath(path) {
  309. let parsedPath = {};
  310. if (path) {
  311. let hashIndex = path.indexOf("#");
  312. if (hashIndex >= 0) {
  313. parsedPath.hash = path.substr(hashIndex);
  314. path = path.substr(0, hashIndex);
  315. }
  316. let searchIndex = path.indexOf("?");
  317. if (searchIndex >= 0) {
  318. parsedPath.search = path.substr(searchIndex);
  319. path = path.substr(0, searchIndex);
  320. }
  321. if (path) {
  322. parsedPath.pathname = path;
  323. }
  324. }
  325. return parsedPath;
  326. }
  327. function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
  328. if (options === void 0) {
  329. options = {};
  330. }
  331. let {
  332. window = document.defaultView,
  333. v5Compat = false
  334. } = options;
  335. let globalHistory = window.history;
  336. let action = Action.Pop;
  337. let listener = null;
  338. let index = getIndex();
  339. // Index should only be null when we initialize. If not, it's because the
  340. // user called history.pushState or history.replaceState directly, in which
  341. // case we should log a warning as it will result in bugs.
  342. if (index == null) {
  343. index = 0;
  344. globalHistory.replaceState(_extends({}, globalHistory.state, {
  345. idx: index
  346. }), "");
  347. }
  348. function getIndex() {
  349. let state = globalHistory.state || {
  350. idx: null
  351. };
  352. return state.idx;
  353. }
  354. function handlePop() {
  355. action = Action.Pop;
  356. let nextIndex = getIndex();
  357. let delta = nextIndex == null ? null : nextIndex - index;
  358. index = nextIndex;
  359. if (listener) {
  360. listener({
  361. action,
  362. location: history.location,
  363. delta
  364. });
  365. }
  366. }
  367. function push(to, state) {
  368. action = Action.Push;
  369. let location = createLocation(history.location, to, state);
  370. if (validateLocation) validateLocation(location, to);
  371. index = getIndex() + 1;
  372. let historyState = getHistoryState(location, index);
  373. let url = history.createHref(location);
  374. // try...catch because iOS limits us to 100 pushState calls :/
  375. try {
  376. globalHistory.pushState(historyState, "", url);
  377. } catch (error) {
  378. // If the exception is because `state` can't be serialized, let that throw
  379. // outwards just like a replace call would so the dev knows the cause
  380. // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
  381. // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
  382. if (error instanceof DOMException && error.name === "DataCloneError") {
  383. throw error;
  384. }
  385. // They are going to lose state here, but there is no real
  386. // way to warn them about it since the page will refresh...
  387. window.location.assign(url);
  388. }
  389. if (v5Compat && listener) {
  390. listener({
  391. action,
  392. location: history.location,
  393. delta: 1
  394. });
  395. }
  396. }
  397. function replace(to, state) {
  398. action = Action.Replace;
  399. let location = createLocation(history.location, to, state);
  400. if (validateLocation) validateLocation(location, to);
  401. index = getIndex();
  402. let historyState = getHistoryState(location, index);
  403. let url = history.createHref(location);
  404. globalHistory.replaceState(historyState, "", url);
  405. if (v5Compat && listener) {
  406. listener({
  407. action,
  408. location: history.location,
  409. delta: 0
  410. });
  411. }
  412. }
  413. function createURL(to) {
  414. // window.location.origin is "null" (the literal string value) in Firefox
  415. // under certain conditions, notably when serving from a local HTML file
  416. // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
  417. let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
  418. let href = typeof to === "string" ? to : createPath(to);
  419. // Treating this as a full URL will strip any trailing spaces so we need to
  420. // pre-encode them since they might be part of a matching splat param from
  421. // an ancestor route
  422. href = href.replace(/ $/, "%20");
  423. invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
  424. return new URL(href, base);
  425. }
  426. let history = {
  427. get action() {
  428. return action;
  429. },
  430. get location() {
  431. return getLocation(window, globalHistory);
  432. },
  433. listen(fn) {
  434. if (listener) {
  435. throw new Error("A history only accepts one active listener");
  436. }
  437. window.addEventListener(PopStateEventType, handlePop);
  438. listener = fn;
  439. return () => {
  440. window.removeEventListener(PopStateEventType, handlePop);
  441. listener = null;
  442. };
  443. },
  444. createHref(to) {
  445. return createHref(window, to);
  446. },
  447. createURL,
  448. encodeLocation(to) {
  449. // Encode a Location the same way window.location would
  450. let url = createURL(to);
  451. return {
  452. pathname: url.pathname,
  453. search: url.search,
  454. hash: url.hash
  455. };
  456. },
  457. push,
  458. replace,
  459. go(n) {
  460. return globalHistory.go(n);
  461. }
  462. };
  463. return history;
  464. }
  465. //#endregion
  466. var ResultType;
  467. (function (ResultType) {
  468. ResultType["data"] = "data";
  469. ResultType["deferred"] = "deferred";
  470. ResultType["redirect"] = "redirect";
  471. ResultType["error"] = "error";
  472. })(ResultType || (ResultType = {}));
  473. const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
  474. function isIndexRoute(route) {
  475. return route.index === true;
  476. }
  477. // Walk the route tree generating unique IDs where necessary, so we are working
  478. // solely with AgnosticDataRouteObject's within the Router
  479. function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
  480. if (parentPath === void 0) {
  481. parentPath = [];
  482. }
  483. if (manifest === void 0) {
  484. manifest = {};
  485. }
  486. return routes.map((route, index) => {
  487. let treePath = [...parentPath, String(index)];
  488. let id = typeof route.id === "string" ? route.id : treePath.join("-");
  489. invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
  490. invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
  491. if (isIndexRoute(route)) {
  492. let indexRoute = _extends({}, route, mapRouteProperties(route), {
  493. id
  494. });
  495. manifest[id] = indexRoute;
  496. return indexRoute;
  497. } else {
  498. let pathOrLayoutRoute = _extends({}, route, mapRouteProperties(route), {
  499. id,
  500. children: undefined
  501. });
  502. manifest[id] = pathOrLayoutRoute;
  503. if (route.children) {
  504. pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
  505. }
  506. return pathOrLayoutRoute;
  507. }
  508. });
  509. }
  510. /**
  511. * Matches the given routes to a location and returns the match data.
  512. *
  513. * @see https://reactrouter.com/v6/utils/match-routes
  514. */
  515. function matchRoutes(routes, locationArg, basename) {
  516. if (basename === void 0) {
  517. basename = "/";
  518. }
  519. return matchRoutesImpl(routes, locationArg, basename, false);
  520. }
  521. function matchRoutesImpl(routes, locationArg, basename, allowPartial) {
  522. let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
  523. let pathname = stripBasename(location.pathname || "/", basename);
  524. if (pathname == null) {
  525. return null;
  526. }
  527. let branches = flattenRoutes(routes);
  528. rankRouteBranches(branches);
  529. let matches = null;
  530. for (let i = 0; matches == null && i < branches.length; ++i) {
  531. // Incoming pathnames are generally encoded from either window.location
  532. // or from router.navigate, but we want to match against the unencoded
  533. // paths in the route definitions. Memory router locations won't be
  534. // encoded here but there also shouldn't be anything to decode so this
  535. // should be a safe operation. This avoids needing matchRoutes to be
  536. // history-aware.
  537. let decoded = decodePath(pathname);
  538. matches = matchRouteBranch(branches[i], decoded, allowPartial);
  539. }
  540. return matches;
  541. }
  542. function convertRouteMatchToUiMatch(match, loaderData) {
  543. let {
  544. route,
  545. pathname,
  546. params
  547. } = match;
  548. return {
  549. id: route.id,
  550. pathname,
  551. params,
  552. data: loaderData[route.id],
  553. handle: route.handle
  554. };
  555. }
  556. function flattenRoutes(routes, branches, parentsMeta, parentPath) {
  557. if (branches === void 0) {
  558. branches = [];
  559. }
  560. if (parentsMeta === void 0) {
  561. parentsMeta = [];
  562. }
  563. if (parentPath === void 0) {
  564. parentPath = "";
  565. }
  566. let flattenRoute = (route, index, relativePath) => {
  567. let meta = {
  568. relativePath: relativePath === undefined ? route.path || "" : relativePath,
  569. caseSensitive: route.caseSensitive === true,
  570. childrenIndex: index,
  571. route
  572. };
  573. if (meta.relativePath.startsWith("/")) {
  574. invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
  575. meta.relativePath = meta.relativePath.slice(parentPath.length);
  576. }
  577. let path = joinPaths([parentPath, meta.relativePath]);
  578. let routesMeta = parentsMeta.concat(meta);
  579. // Add the children before adding this route to the array, so we traverse the
  580. // route tree depth-first and child routes appear before their parents in
  581. // the "flattened" version.
  582. if (route.children && route.children.length > 0) {
  583. invariant(
  584. // Our types know better, but runtime JS may not!
  585. // @ts-expect-error
  586. route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
  587. flattenRoutes(route.children, branches, routesMeta, path);
  588. }
  589. // Routes without a path shouldn't ever match by themselves unless they are
  590. // index routes, so don't add them to the list of possible branches.
  591. if (route.path == null && !route.index) {
  592. return;
  593. }
  594. branches.push({
  595. path,
  596. score: computeScore(path, route.index),
  597. routesMeta
  598. });
  599. };
  600. routes.forEach((route, index) => {
  601. var _route$path;
  602. // coarse-grain check for optional params
  603. if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
  604. flattenRoute(route, index);
  605. } else {
  606. for (let exploded of explodeOptionalSegments(route.path)) {
  607. flattenRoute(route, index, exploded);
  608. }
  609. }
  610. });
  611. return branches;
  612. }
  613. /**
  614. * Computes all combinations of optional path segments for a given path,
  615. * excluding combinations that are ambiguous and of lower priority.
  616. *
  617. * For example, `/one/:two?/three/:four?/:five?` explodes to:
  618. * - `/one/three`
  619. * - `/one/:two/three`
  620. * - `/one/three/:four`
  621. * - `/one/three/:five`
  622. * - `/one/:two/three/:four`
  623. * - `/one/:two/three/:five`
  624. * - `/one/three/:four/:five`
  625. * - `/one/:two/three/:four/:five`
  626. */
  627. function explodeOptionalSegments(path) {
  628. let segments = path.split("/");
  629. if (segments.length === 0) return [];
  630. let [first, ...rest] = segments;
  631. // Optional path segments are denoted by a trailing `?`
  632. let isOptional = first.endsWith("?");
  633. // Compute the corresponding required segment: `foo?` -> `foo`
  634. let required = first.replace(/\?$/, "");
  635. if (rest.length === 0) {
  636. // Intepret empty string as omitting an optional segment
  637. // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
  638. return isOptional ? [required, ""] : [required];
  639. }
  640. let restExploded = explodeOptionalSegments(rest.join("/"));
  641. let result = [];
  642. // All child paths with the prefix. Do this for all children before the
  643. // optional version for all children, so we get consistent ordering where the
  644. // parent optional aspect is preferred as required. Otherwise, we can get
  645. // child sections interspersed where deeper optional segments are higher than
  646. // parent optional segments, where for example, /:two would explode _earlier_
  647. // then /:one. By always including the parent as required _for all children_
  648. // first, we avoid this issue
  649. result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
  650. // Then, if this is an optional value, add all child versions without
  651. if (isOptional) {
  652. result.push(...restExploded);
  653. }
  654. // for absolute paths, ensure `/` instead of empty segment
  655. return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
  656. }
  657. function rankRouteBranches(branches) {
  658. branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
  659. : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
  660. }
  661. const paramRe = /^:[\w-]+$/;
  662. const dynamicSegmentValue = 3;
  663. const indexRouteValue = 2;
  664. const emptySegmentValue = 1;
  665. const staticSegmentValue = 10;
  666. const splatPenalty = -2;
  667. const isSplat = s => s === "*";
  668. function computeScore(path, index) {
  669. let segments = path.split("/");
  670. let initialScore = segments.length;
  671. if (segments.some(isSplat)) {
  672. initialScore += splatPenalty;
  673. }
  674. if (index) {
  675. initialScore += indexRouteValue;
  676. }
  677. return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
  678. }
  679. function compareIndexes(a, b) {
  680. let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
  681. return siblings ?
  682. // If two routes are siblings, we should try to match the earlier sibling
  683. // first. This allows people to have fine-grained control over the matching
  684. // behavior by simply putting routes with identical paths in the order they
  685. // want them tried.
  686. a[a.length - 1] - b[b.length - 1] :
  687. // Otherwise, it doesn't really make sense to rank non-siblings by index,
  688. // so they sort equally.
  689. 0;
  690. }
  691. function matchRouteBranch(branch, pathname, allowPartial) {
  692. if (allowPartial === void 0) {
  693. allowPartial = false;
  694. }
  695. let {
  696. routesMeta
  697. } = branch;
  698. let matchedParams = {};
  699. let matchedPathname = "/";
  700. let matches = [];
  701. for (let i = 0; i < routesMeta.length; ++i) {
  702. let meta = routesMeta[i];
  703. let end = i === routesMeta.length - 1;
  704. let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
  705. let match = matchPath({
  706. path: meta.relativePath,
  707. caseSensitive: meta.caseSensitive,
  708. end
  709. }, remainingPathname);
  710. let route = meta.route;
  711. if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) {
  712. match = matchPath({
  713. path: meta.relativePath,
  714. caseSensitive: meta.caseSensitive,
  715. end: false
  716. }, remainingPathname);
  717. }
  718. if (!match) {
  719. return null;
  720. }
  721. Object.assign(matchedParams, match.params);
  722. matches.push({
  723. // TODO: Can this as be avoided?
  724. params: matchedParams,
  725. pathname: joinPaths([matchedPathname, match.pathname]),
  726. pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
  727. route
  728. });
  729. if (match.pathnameBase !== "/") {
  730. matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
  731. }
  732. }
  733. return matches;
  734. }
  735. /**
  736. * Returns a path with params interpolated.
  737. *
  738. * @see https://reactrouter.com/v6/utils/generate-path
  739. */
  740. function generatePath(originalPath, params) {
  741. if (params === void 0) {
  742. params = {};
  743. }
  744. let path = originalPath;
  745. if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
  746. warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
  747. path = path.replace(/\*$/, "/*");
  748. }
  749. // ensure `/` is added at the beginning if the path is absolute
  750. const prefix = path.startsWith("/") ? "/" : "";
  751. const stringify = p => p == null ? "" : typeof p === "string" ? p : String(p);
  752. const segments = path.split(/\/+/).map((segment, index, array) => {
  753. const isLastSegment = index === array.length - 1;
  754. // only apply the splat if it's the last segment
  755. if (isLastSegment && segment === "*") {
  756. const star = "*";
  757. // Apply the splat
  758. return stringify(params[star]);
  759. }
  760. const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
  761. if (keyMatch) {
  762. const [, key, optional] = keyMatch;
  763. let param = params[key];
  764. invariant(optional === "?" || param != null, "Missing \":" + key + "\" param");
  765. return stringify(param);
  766. }
  767. // Remove any optional markers from optional static segments
  768. return segment.replace(/\?$/g, "");
  769. })
  770. // Remove empty segments
  771. .filter(segment => !!segment);
  772. return prefix + segments.join("/");
  773. }
  774. /**
  775. * Performs pattern matching on a URL pathname and returns information about
  776. * the match.
  777. *
  778. * @see https://reactrouter.com/v6/utils/match-path
  779. */
  780. function matchPath(pattern, pathname) {
  781. if (typeof pattern === "string") {
  782. pattern = {
  783. path: pattern,
  784. caseSensitive: false,
  785. end: true
  786. };
  787. }
  788. let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
  789. let match = pathname.match(matcher);
  790. if (!match) return null;
  791. let matchedPathname = match[0];
  792. let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
  793. let captureGroups = match.slice(1);
  794. let params = compiledParams.reduce((memo, _ref, index) => {
  795. let {
  796. paramName,
  797. isOptional
  798. } = _ref;
  799. // We need to compute the pathnameBase here using the raw splat value
  800. // instead of using params["*"] later because it will be decoded then
  801. if (paramName === "*") {
  802. let splatValue = captureGroups[index] || "";
  803. pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
  804. }
  805. const value = captureGroups[index];
  806. if (isOptional && !value) {
  807. memo[paramName] = undefined;
  808. } else {
  809. memo[paramName] = (value || "").replace(/%2F/g, "/");
  810. }
  811. return memo;
  812. }, {});
  813. return {
  814. params,
  815. pathname: matchedPathname,
  816. pathnameBase,
  817. pattern
  818. };
  819. }
  820. function compilePath(path, caseSensitive, end) {
  821. if (caseSensitive === void 0) {
  822. caseSensitive = false;
  823. }
  824. if (end === void 0) {
  825. end = true;
  826. }
  827. warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
  828. let params = [];
  829. let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
  830. .replace(/^\/*/, "/") // Make sure it has a leading /
  831. .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
  832. .replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
  833. params.push({
  834. paramName,
  835. isOptional: isOptional != null
  836. });
  837. return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
  838. });
  839. if (path.endsWith("*")) {
  840. params.push({
  841. paramName: "*"
  842. });
  843. regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
  844. : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
  845. } else if (end) {
  846. // When matching to the end, ignore trailing slashes
  847. regexpSource += "\\/*$";
  848. } else if (path !== "" && path !== "/") {
  849. // If our path is non-empty and contains anything beyond an initial slash,
  850. // then we have _some_ form of path in our regex, so we should expect to
  851. // match only if we find the end of this path segment. Look for an optional
  852. // non-captured trailing slash (to match a portion of the URL) or the end
  853. // of the path (if we've matched to the end). We used to do this with a
  854. // word boundary but that gives false positives on routes like
  855. // /user-preferences since `-` counts as a word boundary.
  856. regexpSource += "(?:(?=\\/|$))";
  857. } else ;
  858. let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
  859. return [matcher, params];
  860. }
  861. function decodePath(value) {
  862. try {
  863. return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
  864. } catch (error) {
  865. warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
  866. return value;
  867. }
  868. }
  869. /**
  870. * @private
  871. */
  872. function stripBasename(pathname, basename) {
  873. if (basename === "/") return pathname;
  874. if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
  875. return null;
  876. }
  877. // We want to leave trailing slash behavior in the user's control, so if they
  878. // specify a basename with a trailing slash, we should support it
  879. let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
  880. let nextChar = pathname.charAt(startIndex);
  881. if (nextChar && nextChar !== "/") {
  882. // pathname does not start with basename/
  883. return null;
  884. }
  885. return pathname.slice(startIndex) || "/";
  886. }
  887. const ABSOLUTE_URL_REGEX$1 = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
  888. const isAbsoluteUrl = url => ABSOLUTE_URL_REGEX$1.test(url);
  889. /**
  890. * Returns a resolved path object relative to the given pathname.
  891. *
  892. * @see https://reactrouter.com/v6/utils/resolve-path
  893. */
  894. function resolvePath(to, fromPathname) {
  895. if (fromPathname === void 0) {
  896. fromPathname = "/";
  897. }
  898. let {
  899. pathname: toPathname,
  900. search = "",
  901. hash = ""
  902. } = typeof to === "string" ? parsePath(to) : to;
  903. let pathname;
  904. if (toPathname) {
  905. if (isAbsoluteUrl(toPathname)) {
  906. pathname = toPathname;
  907. } else {
  908. if (toPathname.includes("//")) {
  909. let oldPathname = toPathname;
  910. toPathname = toPathname.replace(/\/\/+/g, "/");
  911. warning(false, "Pathnames cannot have embedded double slashes - normalizing " + (oldPathname + " -> " + toPathname));
  912. }
  913. if (toPathname.startsWith("/")) {
  914. pathname = resolvePathname(toPathname.substring(1), "/");
  915. } else {
  916. pathname = resolvePathname(toPathname, fromPathname);
  917. }
  918. }
  919. } else {
  920. pathname = fromPathname;
  921. }
  922. return {
  923. pathname,
  924. search: normalizeSearch(search),
  925. hash: normalizeHash(hash)
  926. };
  927. }
  928. function resolvePathname(relativePath, fromPathname) {
  929. let segments = fromPathname.replace(/\/+$/, "").split("/");
  930. let relativeSegments = relativePath.split("/");
  931. relativeSegments.forEach(segment => {
  932. if (segment === "..") {
  933. // Keep the root "" segment so the pathname starts at /
  934. if (segments.length > 1) segments.pop();
  935. } else if (segment !== ".") {
  936. segments.push(segment);
  937. }
  938. });
  939. return segments.length > 1 ? segments.join("/") : "/";
  940. }
  941. function getInvalidPathError(char, field, dest, path) {
  942. return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "]. Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
  943. }
  944. /**
  945. * @private
  946. *
  947. * When processing relative navigation we want to ignore ancestor routes that
  948. * do not contribute to the path, such that index/pathless layout routes don't
  949. * interfere.
  950. *
  951. * For example, when moving a route element into an index route and/or a
  952. * pathless layout route, relative link behavior contained within should stay
  953. * the same. Both of the following examples should link back to the root:
  954. *
  955. * <Route path="/">
  956. * <Route path="accounts" element={<Link to=".."}>
  957. * </Route>
  958. *
  959. * <Route path="/">
  960. * <Route path="accounts">
  961. * <Route element={<AccountsLayout />}> // <-- Does not contribute
  962. * <Route index element={<Link to=".."} /> // <-- Does not contribute
  963. * </Route
  964. * </Route>
  965. * </Route>
  966. */
  967. function getPathContributingMatches(matches) {
  968. return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
  969. }
  970. // Return the array of pathnames for the current route matches - used to
  971. // generate the routePathnames input for resolveTo()
  972. function getResolveToMatches(matches, v7_relativeSplatPath) {
  973. let pathMatches = getPathContributingMatches(matches);
  974. // When v7_relativeSplatPath is enabled, use the full pathname for the leaf
  975. // match so we include splat values for "." links. See:
  976. // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329
  977. if (v7_relativeSplatPath) {
  978. return pathMatches.map((match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
  979. }
  980. return pathMatches.map(match => match.pathnameBase);
  981. }
  982. /**
  983. * @private
  984. */
  985. function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
  986. if (isPathRelative === void 0) {
  987. isPathRelative = false;
  988. }
  989. let to;
  990. if (typeof toArg === "string") {
  991. to = parsePath(toArg);
  992. } else {
  993. to = _extends({}, toArg);
  994. invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
  995. invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
  996. invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
  997. }
  998. let isEmptyPath = toArg === "" || to.pathname === "";
  999. let toPathname = isEmptyPath ? "/" : to.pathname;
  1000. let from;
  1001. // Routing is relative to the current pathname if explicitly requested.
  1002. //
  1003. // If a pathname is explicitly provided in `to`, it should be relative to the
  1004. // route context. This is explained in `Note on `<Link to>` values` in our
  1005. // migration guide from v5 as a means of disambiguation between `to` values
  1006. // that begin with `/` and those that do not. However, this is problematic for
  1007. // `to` values that do not provide a pathname. `to` can simply be a search or
  1008. // hash string, in which case we should assume that the navigation is relative
  1009. // to the current location's pathname and *not* the route pathname.
  1010. if (toPathname == null) {
  1011. from = locationPathname;
  1012. } else {
  1013. let routePathnameIndex = routePathnames.length - 1;
  1014. // With relative="route" (the default), each leading .. segment means
  1015. // "go up one route" instead of "go up one URL segment". This is a key
  1016. // difference from how <a href> works and a major reason we call this a
  1017. // "to" value instead of a "href".
  1018. if (!isPathRelative && toPathname.startsWith("..")) {
  1019. let toSegments = toPathname.split("/");
  1020. while (toSegments[0] === "..") {
  1021. toSegments.shift();
  1022. routePathnameIndex -= 1;
  1023. }
  1024. to.pathname = toSegments.join("/");
  1025. }
  1026. from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
  1027. }
  1028. let path = resolvePath(to, from);
  1029. // Ensure the pathname has a trailing slash if the original "to" had one
  1030. let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
  1031. // Or if this was a link to the current path which has a trailing slash
  1032. let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
  1033. if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
  1034. path.pathname += "/";
  1035. }
  1036. return path;
  1037. }
  1038. /**
  1039. * @private
  1040. */
  1041. function getToPathname(to) {
  1042. // Empty strings should be treated the same as / paths
  1043. return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
  1044. }
  1045. /**
  1046. * @private
  1047. */
  1048. const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
  1049. /**
  1050. * @private
  1051. */
  1052. const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
  1053. /**
  1054. * @private
  1055. */
  1056. const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
  1057. /**
  1058. * @private
  1059. */
  1060. const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
  1061. /**
  1062. * This is a shortcut for creating `application/json` responses. Converts `data`
  1063. * to JSON and sets the `Content-Type` header.
  1064. *
  1065. * @deprecated The `json` method is deprecated in favor of returning raw objects.
  1066. * This method will be removed in v7.
  1067. */
  1068. const json = function json(data, init) {
  1069. if (init === void 0) {
  1070. init = {};
  1071. }
  1072. let responseInit = typeof init === "number" ? {
  1073. status: init
  1074. } : init;
  1075. let headers = new Headers(responseInit.headers);
  1076. if (!headers.has("Content-Type")) {
  1077. headers.set("Content-Type", "application/json; charset=utf-8");
  1078. }
  1079. return new Response(JSON.stringify(data), _extends({}, responseInit, {
  1080. headers
  1081. }));
  1082. };
  1083. class DataWithResponseInit {
  1084. constructor(data, init) {
  1085. this.type = "DataWithResponseInit";
  1086. this.data = data;
  1087. this.init = init || null;
  1088. }
  1089. }
  1090. /**
  1091. * Create "responses" that contain `status`/`headers` without forcing
  1092. * serialization into an actual `Response` - used by Remix single fetch
  1093. */
  1094. function data(data, init) {
  1095. return new DataWithResponseInit(data, typeof init === "number" ? {
  1096. status: init
  1097. } : init);
  1098. }
  1099. class AbortedDeferredError extends Error {}
  1100. class DeferredData {
  1101. constructor(data, responseInit) {
  1102. this.pendingKeysSet = new Set();
  1103. this.subscribers = new Set();
  1104. this.deferredKeys = [];
  1105. invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
  1106. // Set up an AbortController + Promise we can race against to exit early
  1107. // cancellation
  1108. let reject;
  1109. this.abortPromise = new Promise((_, r) => reject = r);
  1110. this.controller = new AbortController();
  1111. let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
  1112. this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
  1113. this.controller.signal.addEventListener("abort", onAbort);
  1114. this.data = Object.entries(data).reduce((acc, _ref2) => {
  1115. let [key, value] = _ref2;
  1116. return Object.assign(acc, {
  1117. [key]: this.trackPromise(key, value)
  1118. });
  1119. }, {});
  1120. if (this.done) {
  1121. // All incoming values were resolved
  1122. this.unlistenAbortSignal();
  1123. }
  1124. this.init = responseInit;
  1125. }
  1126. trackPromise(key, value) {
  1127. if (!(value instanceof Promise)) {
  1128. return value;
  1129. }
  1130. this.deferredKeys.push(key);
  1131. this.pendingKeysSet.add(key);
  1132. // We store a little wrapper promise that will be extended with
  1133. // _data/_error props upon resolve/reject
  1134. let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));
  1135. // Register rejection listeners to avoid uncaught promise rejections on
  1136. // errors or aborted deferred values
  1137. promise.catch(() => {});
  1138. Object.defineProperty(promise, "_tracked", {
  1139. get: () => true
  1140. });
  1141. return promise;
  1142. }
  1143. onSettle(promise, key, error, data) {
  1144. if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
  1145. this.unlistenAbortSignal();
  1146. Object.defineProperty(promise, "_error", {
  1147. get: () => error
  1148. });
  1149. return Promise.reject(error);
  1150. }
  1151. this.pendingKeysSet.delete(key);
  1152. if (this.done) {
  1153. // Nothing left to abort!
  1154. this.unlistenAbortSignal();
  1155. }
  1156. // If the promise was resolved/rejected with undefined, we'll throw an error as you
  1157. // should always resolve with a value or null
  1158. if (error === undefined && data === undefined) {
  1159. let undefinedError = new Error("Deferred data for key \"" + key + "\" resolved/rejected with `undefined`, " + "you must resolve/reject with a value or `null`.");
  1160. Object.defineProperty(promise, "_error", {
  1161. get: () => undefinedError
  1162. });
  1163. this.emit(false, key);
  1164. return Promise.reject(undefinedError);
  1165. }
  1166. if (data === undefined) {
  1167. Object.defineProperty(promise, "_error", {
  1168. get: () => error
  1169. });
  1170. this.emit(false, key);
  1171. return Promise.reject(error);
  1172. }
  1173. Object.defineProperty(promise, "_data", {
  1174. get: () => data
  1175. });
  1176. this.emit(false, key);
  1177. return data;
  1178. }
  1179. emit(aborted, settledKey) {
  1180. this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
  1181. }
  1182. subscribe(fn) {
  1183. this.subscribers.add(fn);
  1184. return () => this.subscribers.delete(fn);
  1185. }
  1186. cancel() {
  1187. this.controller.abort();
  1188. this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
  1189. this.emit(true);
  1190. }
  1191. async resolveData(signal) {
  1192. let aborted = false;
  1193. if (!this.done) {
  1194. let onAbort = () => this.cancel();
  1195. signal.addEventListener("abort", onAbort);
  1196. aborted = await new Promise(resolve => {
  1197. this.subscribe(aborted => {
  1198. signal.removeEventListener("abort", onAbort);
  1199. if (aborted || this.done) {
  1200. resolve(aborted);
  1201. }
  1202. });
  1203. });
  1204. }
  1205. return aborted;
  1206. }
  1207. get done() {
  1208. return this.pendingKeysSet.size === 0;
  1209. }
  1210. get unwrappedData() {
  1211. invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
  1212. return Object.entries(this.data).reduce((acc, _ref3) => {
  1213. let [key, value] = _ref3;
  1214. return Object.assign(acc, {
  1215. [key]: unwrapTrackedPromise(value)
  1216. });
  1217. }, {});
  1218. }
  1219. get pendingKeys() {
  1220. return Array.from(this.pendingKeysSet);
  1221. }
  1222. }
  1223. function isTrackedPromise(value) {
  1224. return value instanceof Promise && value._tracked === true;
  1225. }
  1226. function unwrapTrackedPromise(value) {
  1227. if (!isTrackedPromise(value)) {
  1228. return value;
  1229. }
  1230. if (value._error) {
  1231. throw value._error;
  1232. }
  1233. return value._data;
  1234. }
  1235. /**
  1236. * @deprecated The `defer` method is deprecated in favor of returning raw
  1237. * objects. This method will be removed in v7.
  1238. */
  1239. const defer = function defer(data, init) {
  1240. if (init === void 0) {
  1241. init = {};
  1242. }
  1243. let responseInit = typeof init === "number" ? {
  1244. status: init
  1245. } : init;
  1246. return new DeferredData(data, responseInit);
  1247. };
  1248. /**
  1249. * A redirect response. Sets the status code and the `Location` header.
  1250. * Defaults to "302 Found".
  1251. */
  1252. const redirect = function redirect(url, init) {
  1253. if (init === void 0) {
  1254. init = 302;
  1255. }
  1256. let responseInit = init;
  1257. if (typeof responseInit === "number") {
  1258. responseInit = {
  1259. status: responseInit
  1260. };
  1261. } else if (typeof responseInit.status === "undefined") {
  1262. responseInit.status = 302;
  1263. }
  1264. let headers = new Headers(responseInit.headers);
  1265. headers.set("Location", url);
  1266. return new Response(null, _extends({}, responseInit, {
  1267. headers
  1268. }));
  1269. };
  1270. /**
  1271. * A redirect response that will force a document reload to the new location.
  1272. * Sets the status code and the `Location` header.
  1273. * Defaults to "302 Found".
  1274. */
  1275. const redirectDocument = (url, init) => {
  1276. let response = redirect(url, init);
  1277. response.headers.set("X-Remix-Reload-Document", "true");
  1278. return response;
  1279. };
  1280. /**
  1281. * A redirect response that will perform a `history.replaceState` instead of a
  1282. * `history.pushState` for client-side navigation redirects.
  1283. * Sets the status code and the `Location` header.
  1284. * Defaults to "302 Found".
  1285. */
  1286. const replace = (url, init) => {
  1287. let response = redirect(url, init);
  1288. response.headers.set("X-Remix-Replace", "true");
  1289. return response;
  1290. };
  1291. /**
  1292. * @private
  1293. * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
  1294. *
  1295. * We don't export the class for public use since it's an implementation
  1296. * detail, but we export the interface above so folks can build their own
  1297. * abstractions around instances via isRouteErrorResponse()
  1298. */
  1299. class ErrorResponseImpl {
  1300. constructor(status, statusText, data, internal) {
  1301. if (internal === void 0) {
  1302. internal = false;
  1303. }
  1304. this.status = status;
  1305. this.statusText = statusText || "";
  1306. this.internal = internal;
  1307. if (data instanceof Error) {
  1308. this.data = data.toString();
  1309. this.error = data;
  1310. } else {
  1311. this.data = data;
  1312. }
  1313. }
  1314. }
  1315. /**
  1316. * Check if the given error is an ErrorResponse generated from a 4xx/5xx
  1317. * Response thrown from an action/loader
  1318. */
  1319. function isRouteErrorResponse(error) {
  1320. return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
  1321. }
  1322. const validMutationMethodsArr = ["post", "put", "patch", "delete"];
  1323. const validMutationMethods = new Set(validMutationMethodsArr);
  1324. const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
  1325. const validRequestMethods = new Set(validRequestMethodsArr);
  1326. const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
  1327. const redirectPreserveMethodStatusCodes = new Set([307, 308]);
  1328. const IDLE_NAVIGATION = {
  1329. state: "idle",
  1330. location: undefined,
  1331. formMethod: undefined,
  1332. formAction: undefined,
  1333. formEncType: undefined,
  1334. formData: undefined,
  1335. json: undefined,
  1336. text: undefined
  1337. };
  1338. const IDLE_FETCHER = {
  1339. state: "idle",
  1340. data: undefined,
  1341. formMethod: undefined,
  1342. formAction: undefined,
  1343. formEncType: undefined,
  1344. formData: undefined,
  1345. json: undefined,
  1346. text: undefined
  1347. };
  1348. const IDLE_BLOCKER = {
  1349. state: "unblocked",
  1350. proceed: undefined,
  1351. reset: undefined,
  1352. location: undefined
  1353. };
  1354. const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
  1355. const defaultMapRouteProperties = route => ({
  1356. hasErrorBoundary: Boolean(route.hasErrorBoundary)
  1357. });
  1358. const TRANSITIONS_STORAGE_KEY = "remix-router-transitions";
  1359. //#endregion
  1360. ////////////////////////////////////////////////////////////////////////////////
  1361. //#region createRouter
  1362. ////////////////////////////////////////////////////////////////////////////////
  1363. /**
  1364. * Create a router and listen to history POP navigations
  1365. */
  1366. function createRouter(init) {
  1367. const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
  1368. const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
  1369. const isServer = !isBrowser;
  1370. invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
  1371. let mapRouteProperties;
  1372. if (init.mapRouteProperties) {
  1373. mapRouteProperties = init.mapRouteProperties;
  1374. } else if (init.detectErrorBoundary) {
  1375. // If they are still using the deprecated version, wrap it with the new API
  1376. let detectErrorBoundary = init.detectErrorBoundary;
  1377. mapRouteProperties = route => ({
  1378. hasErrorBoundary: detectErrorBoundary(route)
  1379. });
  1380. } else {
  1381. mapRouteProperties = defaultMapRouteProperties;
  1382. }
  1383. // Routes keyed by ID
  1384. let manifest = {};
  1385. // Routes in tree format for matching
  1386. let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
  1387. let inFlightDataRoutes;
  1388. let basename = init.basename || "/";
  1389. let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
  1390. let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
  1391. // Config driven behavior flags
  1392. let future = _extends({
  1393. v7_fetcherPersist: false,
  1394. v7_normalizeFormMethod: false,
  1395. v7_partialHydration: false,
  1396. v7_prependBasename: false,
  1397. v7_relativeSplatPath: false,
  1398. v7_skipActionErrorRevalidation: false
  1399. }, init.future);
  1400. // Cleanup function for history
  1401. let unlistenHistory = null;
  1402. // Externally-provided functions to call on all state changes
  1403. let subscribers = new Set();
  1404. // Externally-provided object to hold scroll restoration locations during routing
  1405. let savedScrollPositions = null;
  1406. // Externally-provided function to get scroll restoration keys
  1407. let getScrollRestorationKey = null;
  1408. // Externally-provided function to get current scroll position
  1409. let getScrollPosition = null;
  1410. // One-time flag to control the initial hydration scroll restoration. Because
  1411. // we don't get the saved positions from <ScrollRestoration /> until _after_
  1412. // the initial render, we need to manually trigger a separate updateState to
  1413. // send along the restoreScrollPosition
  1414. // Set to true if we have `hydrationData` since we assume we were SSR'd and that
  1415. // SSR did the initial scroll restoration.
  1416. let initialScrollRestored = init.hydrationData != null;
  1417. let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
  1418. let initialMatchesIsFOW = false;
  1419. let initialErrors = null;
  1420. if (initialMatches == null && !patchRoutesOnNavigationImpl) {
  1421. // If we do not match a user-provided-route, fall back to the root
  1422. // to allow the error boundary to take over
  1423. let error = getInternalRouterError(404, {
  1424. pathname: init.history.location.pathname
  1425. });
  1426. let {
  1427. matches,
  1428. route
  1429. } = getShortCircuitMatches(dataRoutes);
  1430. initialMatches = matches;
  1431. initialErrors = {
  1432. [route.id]: error
  1433. };
  1434. }
  1435. // In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
  1436. // our initial match is a splat route, clear them out so we run through lazy
  1437. // discovery on hydration in case there's a more accurate lazy route match.
  1438. // In SSR apps (with `hydrationData`), we expect that the server will send
  1439. // up the proper matched routes so we don't want to run lazy discovery on
  1440. // initial hydration and want to hydrate into the splat route.
  1441. if (initialMatches && !init.hydrationData) {
  1442. let fogOfWar = checkFogOfWar(initialMatches, dataRoutes, init.history.location.pathname);
  1443. if (fogOfWar.active) {
  1444. initialMatches = null;
  1445. }
  1446. }
  1447. let initialized;
  1448. if (!initialMatches) {
  1449. initialized = false;
  1450. initialMatches = [];
  1451. // If partial hydration and fog of war is enabled, we will be running
  1452. // `patchRoutesOnNavigation` during hydration so include any partial matches as
  1453. // the initial matches so we can properly render `HydrateFallback`'s
  1454. if (future.v7_partialHydration) {
  1455. let fogOfWar = checkFogOfWar(null, dataRoutes, init.history.location.pathname);
  1456. if (fogOfWar.active && fogOfWar.matches) {
  1457. initialMatchesIsFOW = true;
  1458. initialMatches = fogOfWar.matches;
  1459. }
  1460. }
  1461. } else if (initialMatches.some(m => m.route.lazy)) {
  1462. // All initialMatches need to be loaded before we're ready. If we have lazy
  1463. // functions around still then we'll need to run them in initialize()
  1464. initialized = false;
  1465. } else if (!initialMatches.some(m => m.route.loader)) {
  1466. // If we've got no loaders to run, then we're good to go
  1467. initialized = true;
  1468. } else if (future.v7_partialHydration) {
  1469. // If partial hydration is enabled, we're initialized so long as we were
  1470. // provided with hydrationData for every route with a loader, and no loaders
  1471. // were marked for explicit hydration
  1472. let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
  1473. let errors = init.hydrationData ? init.hydrationData.errors : null;
  1474. // If errors exist, don't consider routes below the boundary
  1475. if (errors) {
  1476. let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
  1477. initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
  1478. } else {
  1479. initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
  1480. }
  1481. } else {
  1482. // Without partial hydration - we're initialized if we were provided any
  1483. // hydrationData - which is expected to be complete
  1484. initialized = init.hydrationData != null;
  1485. }
  1486. let router;
  1487. let state = {
  1488. historyAction: init.history.action,
  1489. location: init.history.location,
  1490. matches: initialMatches,
  1491. initialized,
  1492. navigation: IDLE_NAVIGATION,
  1493. // Don't restore on initial updateState() if we were SSR'd
  1494. restoreScrollPosition: init.hydrationData != null ? false : null,
  1495. preventScrollReset: false,
  1496. revalidation: "idle",
  1497. loaderData: init.hydrationData && init.hydrationData.loaderData || {},
  1498. actionData: init.hydrationData && init.hydrationData.actionData || null,
  1499. errors: init.hydrationData && init.hydrationData.errors || initialErrors,
  1500. fetchers: new Map(),
  1501. blockers: new Map()
  1502. };
  1503. // -- Stateful internal variables to manage navigations --
  1504. // Current navigation in progress (to be committed in completeNavigation)
  1505. let pendingAction = Action.Pop;
  1506. // Should the current navigation prevent the scroll reset if scroll cannot
  1507. // be restored?
  1508. let pendingPreventScrollReset = false;
  1509. // AbortController for the active navigation
  1510. let pendingNavigationController;
  1511. // Should the current navigation enable document.startViewTransition?
  1512. let pendingViewTransitionEnabled = false;
  1513. // Store applied view transitions so we can apply them on POP
  1514. let appliedViewTransitions = new Map();
  1515. // Cleanup function for persisting applied transitions to sessionStorage
  1516. let removePageHideEventListener = null;
  1517. // We use this to avoid touching history in completeNavigation if a
  1518. // revalidation is entirely uninterrupted
  1519. let isUninterruptedRevalidation = false;
  1520. // Use this internal flag to force revalidation of all loaders:
  1521. // - submissions (completed or interrupted)
  1522. // - useRevalidator()
  1523. // - X-Remix-Revalidate (from redirect)
  1524. let isRevalidationRequired = false;
  1525. // Use this internal array to capture routes that require revalidation due
  1526. // to a cancelled deferred on action submission
  1527. let cancelledDeferredRoutes = [];
  1528. // Use this internal array to capture fetcher loads that were cancelled by an
  1529. // action navigation and require revalidation
  1530. let cancelledFetcherLoads = new Set();
  1531. // AbortControllers for any in-flight fetchers
  1532. let fetchControllers = new Map();
  1533. // Track loads based on the order in which they started
  1534. let incrementingLoadId = 0;
  1535. // Track the outstanding pending navigation data load to be compared against
  1536. // the globally incrementing load when a fetcher load lands after a completed
  1537. // navigation
  1538. let pendingNavigationLoadId = -1;
  1539. // Fetchers that triggered data reloads as a result of their actions
  1540. let fetchReloadIds = new Map();
  1541. // Fetchers that triggered redirect navigations
  1542. let fetchRedirectIds = new Set();
  1543. // Most recent href/match for fetcher.load calls for fetchers
  1544. let fetchLoadMatches = new Map();
  1545. // Ref-count mounted fetchers so we know when it's ok to clean them up
  1546. let activeFetchers = new Map();
  1547. // Fetchers that have requested a delete when using v7_fetcherPersist,
  1548. // they'll be officially removed after they return to idle
  1549. let deletedFetchers = new Set();
  1550. // Store DeferredData instances for active route matches. When a
  1551. // route loader returns defer() we stick one in here. Then, when a nested
  1552. // promise resolves we update loaderData. If a new navigation starts we
  1553. // cancel active deferreds for eliminated routes.
  1554. let activeDeferreds = new Map();
  1555. // Store blocker functions in a separate Map outside of router state since
  1556. // we don't need to update UI state if they change
  1557. let blockerFunctions = new Map();
  1558. // Flag to ignore the next history update, so we can revert the URL change on
  1559. // a POP navigation that was blocked by the user without touching router state
  1560. let unblockBlockerHistoryUpdate = undefined;
  1561. // Initialize the router, all side effects should be kicked off from here.
  1562. // Implemented as a Fluent API for ease of:
  1563. // let router = createRouter(init).initialize();
  1564. function initialize() {
  1565. // If history informs us of a POP navigation, start the navigation but do not update
  1566. // state. We'll update our own state once the navigation completes
  1567. unlistenHistory = init.history.listen(_ref => {
  1568. let {
  1569. action: historyAction,
  1570. location,
  1571. delta
  1572. } = _ref;
  1573. // Ignore this event if it was just us resetting the URL from a
  1574. // blocked POP navigation
  1575. if (unblockBlockerHistoryUpdate) {
  1576. unblockBlockerHistoryUpdate();
  1577. unblockBlockerHistoryUpdate = undefined;
  1578. return;
  1579. }
  1580. warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
  1581. let blockerKey = shouldBlockNavigation({
  1582. currentLocation: state.location,
  1583. nextLocation: location,
  1584. historyAction
  1585. });
  1586. if (blockerKey && delta != null) {
  1587. // Restore the URL to match the current UI, but don't update router state
  1588. let nextHistoryUpdatePromise = new Promise(resolve => {
  1589. unblockBlockerHistoryUpdate = resolve;
  1590. });
  1591. init.history.go(delta * -1);
  1592. // Put the blocker into a blocked state
  1593. updateBlocker(blockerKey, {
  1594. state: "blocked",
  1595. location,
  1596. proceed() {
  1597. updateBlocker(blockerKey, {
  1598. state: "proceeding",
  1599. proceed: undefined,
  1600. reset: undefined,
  1601. location
  1602. });
  1603. // Re-do the same POP navigation we just blocked, after the url
  1604. // restoration is also complete. See:
  1605. // https://github.com/remix-run/react-router/issues/11613
  1606. nextHistoryUpdatePromise.then(() => init.history.go(delta));
  1607. },
  1608. reset() {
  1609. let blockers = new Map(state.blockers);
  1610. blockers.set(blockerKey, IDLE_BLOCKER);
  1611. updateState({
  1612. blockers
  1613. });
  1614. }
  1615. });
  1616. return;
  1617. }
  1618. return startNavigation(historyAction, location);
  1619. });
  1620. if (isBrowser) {
  1621. // FIXME: This feels gross. How can we cleanup the lines between
  1622. // scrollRestoration/appliedTransitions persistance?
  1623. restoreAppliedTransitions(routerWindow, appliedViewTransitions);
  1624. let _saveAppliedTransitions = () => persistAppliedTransitions(routerWindow, appliedViewTransitions);
  1625. routerWindow.addEventListener("pagehide", _saveAppliedTransitions);
  1626. removePageHideEventListener = () => routerWindow.removeEventListener("pagehide", _saveAppliedTransitions);
  1627. }
  1628. // Kick off initial data load if needed. Use Pop to avoid modifying history
  1629. // Note we don't do any handling of lazy here. For SPA's it'll get handled
  1630. // in the normal navigation flow. For SSR it's expected that lazy modules are
  1631. // resolved prior to router creation since we can't go into a fallbackElement
  1632. // UI for SSR'd apps
  1633. if (!state.initialized) {
  1634. startNavigation(Action.Pop, state.location, {
  1635. initialHydration: true
  1636. });
  1637. }
  1638. return router;
  1639. }
  1640. // Clean up a router and it's side effects
  1641. function dispose() {
  1642. if (unlistenHistory) {
  1643. unlistenHistory();
  1644. }
  1645. if (removePageHideEventListener) {
  1646. removePageHideEventListener();
  1647. }
  1648. subscribers.clear();
  1649. pendingNavigationController && pendingNavigationController.abort();
  1650. state.fetchers.forEach((_, key) => deleteFetcher(key));
  1651. state.blockers.forEach((_, key) => deleteBlocker(key));
  1652. }
  1653. // Subscribe to state updates for the router
  1654. function subscribe(fn) {
  1655. subscribers.add(fn);
  1656. return () => subscribers.delete(fn);
  1657. }
  1658. // Update our state and notify the calling context of the change
  1659. function updateState(newState, opts) {
  1660. if (opts === void 0) {
  1661. opts = {};
  1662. }
  1663. state = _extends({}, state, newState);
  1664. // Prep fetcher cleanup so we can tell the UI which fetcher data entries
  1665. // can be removed
  1666. let completedFetchers = [];
  1667. let deletedFetchersKeys = [];
  1668. if (future.v7_fetcherPersist) {
  1669. state.fetchers.forEach((fetcher, key) => {
  1670. if (fetcher.state === "idle") {
  1671. if (deletedFetchers.has(key)) {
  1672. // Unmounted from the UI and can be totally removed
  1673. deletedFetchersKeys.push(key);
  1674. } else {
  1675. // Returned to idle but still mounted in the UI, so semi-remains for
  1676. // revalidations and such
  1677. completedFetchers.push(key);
  1678. }
  1679. }
  1680. });
  1681. }
  1682. // Remove any lingering deleted fetchers that have already been removed
  1683. // from state.fetchers
  1684. deletedFetchers.forEach(key => {
  1685. if (!state.fetchers.has(key) && !fetchControllers.has(key)) {
  1686. deletedFetchersKeys.push(key);
  1687. }
  1688. });
  1689. // Iterate over a local copy so that if flushSync is used and we end up
  1690. // removing and adding a new subscriber due to the useCallback dependencies,
  1691. // we don't get ourselves into a loop calling the new subscriber immediately
  1692. [...subscribers].forEach(subscriber => subscriber(state, {
  1693. deletedFetchers: deletedFetchersKeys,
  1694. viewTransitionOpts: opts.viewTransitionOpts,
  1695. flushSync: opts.flushSync === true
  1696. }));
  1697. // Remove idle fetchers from state since we only care about in-flight fetchers.
  1698. if (future.v7_fetcherPersist) {
  1699. completedFetchers.forEach(key => state.fetchers.delete(key));
  1700. deletedFetchersKeys.forEach(key => deleteFetcher(key));
  1701. } else {
  1702. // We already called deleteFetcher() on these, can remove them from this
  1703. // Set now that we've handed the keys off to the data layer
  1704. deletedFetchersKeys.forEach(key => deletedFetchers.delete(key));
  1705. }
  1706. }
  1707. // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
  1708. // and setting state.[historyAction/location/matches] to the new route.
  1709. // - Location is a required param
  1710. // - Navigation will always be set to IDLE_NAVIGATION
  1711. // - Can pass any other state in newState
  1712. function completeNavigation(location, newState, _temp) {
  1713. var _location$state, _location$state2;
  1714. let {
  1715. flushSync
  1716. } = _temp === void 0 ? {} : _temp;
  1717. // Deduce if we're in a loading/actionReload state:
  1718. // - We have committed actionData in the store
  1719. // - The current navigation was a mutation submission
  1720. // - We're past the submitting state and into the loading state
  1721. // - The location being loaded is not the result of a redirect
  1722. let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
  1723. let actionData;
  1724. if (newState.actionData) {
  1725. if (Object.keys(newState.actionData).length > 0) {
  1726. actionData = newState.actionData;
  1727. } else {
  1728. // Empty actionData -> clear prior actionData due to an action error
  1729. actionData = null;
  1730. }
  1731. } else if (isActionReload) {
  1732. // Keep the current data if we're wrapping up the action reload
  1733. actionData = state.actionData;
  1734. } else {
  1735. // Clear actionData on any other completed navigations
  1736. actionData = null;
  1737. }
  1738. // Always preserve any existing loaderData from re-used routes
  1739. let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
  1740. // On a successful navigation we can assume we got through all blockers
  1741. // so we can start fresh
  1742. let blockers = state.blockers;
  1743. if (blockers.size > 0) {
  1744. blockers = new Map(blockers);
  1745. blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER));
  1746. }
  1747. // Always respect the user flag. Otherwise don't reset on mutation
  1748. // submission navigations unless they redirect
  1749. let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
  1750. // Commit any in-flight routes at the end of the HMR revalidation "navigation"
  1751. if (inFlightDataRoutes) {
  1752. dataRoutes = inFlightDataRoutes;
  1753. inFlightDataRoutes = undefined;
  1754. }
  1755. if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
  1756. init.history.push(location, location.state);
  1757. } else if (pendingAction === Action.Replace) {
  1758. init.history.replace(location, location.state);
  1759. }
  1760. let viewTransitionOpts;
  1761. // On POP, enable transitions if they were enabled on the original navigation
  1762. if (pendingAction === Action.Pop) {
  1763. // Forward takes precedence so they behave like the original navigation
  1764. let priorPaths = appliedViewTransitions.get(state.location.pathname);
  1765. if (priorPaths && priorPaths.has(location.pathname)) {
  1766. viewTransitionOpts = {
  1767. currentLocation: state.location,
  1768. nextLocation: location
  1769. };
  1770. } else if (appliedViewTransitions.has(location.pathname)) {
  1771. // If we don't have a previous forward nav, assume we're popping back to
  1772. // the new location and enable if that location previously enabled
  1773. viewTransitionOpts = {
  1774. currentLocation: location,
  1775. nextLocation: state.location
  1776. };
  1777. }
  1778. } else if (pendingViewTransitionEnabled) {
  1779. // Store the applied transition on PUSH/REPLACE
  1780. let toPaths = appliedViewTransitions.get(state.location.pathname);
  1781. if (toPaths) {
  1782. toPaths.add(location.pathname);
  1783. } else {
  1784. toPaths = new Set([location.pathname]);
  1785. appliedViewTransitions.set(state.location.pathname, toPaths);
  1786. }
  1787. viewTransitionOpts = {
  1788. currentLocation: state.location,
  1789. nextLocation: location
  1790. };
  1791. }
  1792. updateState(_extends({}, newState, {
  1793. actionData,
  1794. loaderData,
  1795. historyAction: pendingAction,
  1796. location,
  1797. initialized: true,
  1798. navigation: IDLE_NAVIGATION,
  1799. revalidation: "idle",
  1800. restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
  1801. preventScrollReset,
  1802. blockers
  1803. }), {
  1804. viewTransitionOpts,
  1805. flushSync: flushSync === true
  1806. });
  1807. // Reset stateful navigation vars
  1808. pendingAction = Action.Pop;
  1809. pendingPreventScrollReset = false;
  1810. pendingViewTransitionEnabled = false;
  1811. isUninterruptedRevalidation = false;
  1812. isRevalidationRequired = false;
  1813. cancelledDeferredRoutes = [];
  1814. }
  1815. // Trigger a navigation event, which can either be a numerical POP or a PUSH
  1816. // replace with an optional submission
  1817. async function navigate(to, opts) {
  1818. if (typeof to === "number") {
  1819. init.history.go(to);
  1820. return;
  1821. }
  1822. let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, future.v7_relativeSplatPath, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
  1823. let {
  1824. path,
  1825. submission,
  1826. error
  1827. } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
  1828. let currentLocation = state.location;
  1829. let nextLocation = createLocation(state.location, path, opts && opts.state);
  1830. // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
  1831. // URL from window.location, so we need to encode it here so the behavior
  1832. // remains the same as POP and non-data-router usages. new URL() does all
  1833. // the same encoding we'd get from a history.pushState/window.location read
  1834. // without having to touch history
  1835. nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
  1836. let userReplace = opts && opts.replace != null ? opts.replace : undefined;
  1837. let historyAction = Action.Push;
  1838. if (userReplace === true) {
  1839. historyAction = Action.Replace;
  1840. } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
  1841. // By default on submissions to the current location we REPLACE so that
  1842. // users don't have to double-click the back button to get to the prior
  1843. // location. If the user redirects to a different location from the
  1844. // action/loader this will be ignored and the redirect will be a PUSH
  1845. historyAction = Action.Replace;
  1846. }
  1847. let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
  1848. let flushSync = (opts && opts.flushSync) === true;
  1849. let blockerKey = shouldBlockNavigation({
  1850. currentLocation,
  1851. nextLocation,
  1852. historyAction
  1853. });
  1854. if (blockerKey) {
  1855. // Put the blocker into a blocked state
  1856. updateBlocker(blockerKey, {
  1857. state: "blocked",
  1858. location: nextLocation,
  1859. proceed() {
  1860. updateBlocker(blockerKey, {
  1861. state: "proceeding",
  1862. proceed: undefined,
  1863. reset: undefined,
  1864. location: nextLocation
  1865. });
  1866. // Send the same navigation through
  1867. navigate(to, opts);
  1868. },
  1869. reset() {
  1870. let blockers = new Map(state.blockers);
  1871. blockers.set(blockerKey, IDLE_BLOCKER);
  1872. updateState({
  1873. blockers
  1874. });
  1875. }
  1876. });
  1877. return;
  1878. }
  1879. return await startNavigation(historyAction, nextLocation, {
  1880. submission,
  1881. // Send through the formData serialization error if we have one so we can
  1882. // render at the right error boundary after we match routes
  1883. pendingError: error,
  1884. preventScrollReset,
  1885. replace: opts && opts.replace,
  1886. enableViewTransition: opts && opts.viewTransition,
  1887. flushSync
  1888. });
  1889. }
  1890. // Revalidate all current loaders. If a navigation is in progress or if this
  1891. // is interrupted by a navigation, allow this to "succeed" by calling all
  1892. // loaders during the next loader round
  1893. function revalidate() {
  1894. interruptActiveLoads();
  1895. updateState({
  1896. revalidation: "loading"
  1897. });
  1898. // If we're currently submitting an action, we don't need to start a new
  1899. // navigation, we'll just let the follow up loader execution call all loaders
  1900. if (state.navigation.state === "submitting") {
  1901. return;
  1902. }
  1903. // If we're currently in an idle state, start a new navigation for the current
  1904. // action/location and mark it as uninterrupted, which will skip the history
  1905. // update in completeNavigation
  1906. if (state.navigation.state === "idle") {
  1907. startNavigation(state.historyAction, state.location, {
  1908. startUninterruptedRevalidation: true
  1909. });
  1910. return;
  1911. }
  1912. // Otherwise, if we're currently in a loading state, just start a new
  1913. // navigation to the navigation.location but do not trigger an uninterrupted
  1914. // revalidation so that history correctly updates once the navigation completes
  1915. startNavigation(pendingAction || state.historyAction, state.navigation.location, {
  1916. overrideNavigation: state.navigation,
  1917. // Proxy through any rending view transition
  1918. enableViewTransition: pendingViewTransitionEnabled === true
  1919. });
  1920. }
  1921. // Start a navigation to the given action/location. Can optionally provide a
  1922. // overrideNavigation which will override the normalLoad in the case of a redirect
  1923. // navigation
  1924. async function startNavigation(historyAction, location, opts) {
  1925. // Abort any in-progress navigations and start a new one. Unset any ongoing
  1926. // uninterrupted revalidations unless told otherwise, since we want this
  1927. // new navigation to update history normally
  1928. pendingNavigationController && pendingNavigationController.abort();
  1929. pendingNavigationController = null;
  1930. pendingAction = historyAction;
  1931. isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
  1932. // Save the current scroll position every time we start a new navigation,
  1933. // and track whether we should reset scroll on completion
  1934. saveScrollPosition(state.location, state.matches);
  1935. pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
  1936. pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
  1937. let routesToUse = inFlightDataRoutes || dataRoutes;
  1938. let loadingNavigation = opts && opts.overrideNavigation;
  1939. let matches = opts != null && opts.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ?
  1940. // `matchRoutes()` has already been called if we're in here via `router.initialize()`
  1941. state.matches : matchRoutes(routesToUse, location, basename);
  1942. let flushSync = (opts && opts.flushSync) === true;
  1943. // Short circuit if it's only a hash change and not a revalidation or
  1944. // mutation submission.
  1945. //
  1946. // Ignore on initial page loads because since the initial hydration will always
  1947. // be "same hash". For example, on /page#hash and submit a <Form method="post">
  1948. // which will default to a navigation to /page
  1949. if (matches && state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
  1950. completeNavigation(location, {
  1951. matches
  1952. }, {
  1953. flushSync
  1954. });
  1955. return;
  1956. }
  1957. let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
  1958. if (fogOfWar.active && fogOfWar.matches) {
  1959. matches = fogOfWar.matches;
  1960. }
  1961. // Short circuit with a 404 on the root error boundary if we match nothing
  1962. if (!matches) {
  1963. let {
  1964. error,
  1965. notFoundMatches,
  1966. route
  1967. } = handleNavigational404(location.pathname);
  1968. completeNavigation(location, {
  1969. matches: notFoundMatches,
  1970. loaderData: {},
  1971. errors: {
  1972. [route.id]: error
  1973. }
  1974. }, {
  1975. flushSync
  1976. });
  1977. return;
  1978. }
  1979. // Create a controller/Request for this navigation
  1980. pendingNavigationController = new AbortController();
  1981. let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
  1982. let pendingActionResult;
  1983. if (opts && opts.pendingError) {
  1984. // If we have a pendingError, it means the user attempted a GET submission
  1985. // with binary FormData so assign here and skip to handleLoaders. That
  1986. // way we handle calling loaders above the boundary etc. It's not really
  1987. // different from an actionError in that sense.
  1988. pendingActionResult = [findNearestBoundary(matches).route.id, {
  1989. type: ResultType.error,
  1990. error: opts.pendingError
  1991. }];
  1992. } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
  1993. // Call action if we received an action submission
  1994. let actionResult = await handleAction(request, location, opts.submission, matches, fogOfWar.active, {
  1995. replace: opts.replace,
  1996. flushSync
  1997. });
  1998. if (actionResult.shortCircuited) {
  1999. return;
  2000. }
  2001. // If we received a 404 from handleAction, it's because we couldn't lazily
  2002. // discover the destination route so we don't want to call loaders
  2003. if (actionResult.pendingActionResult) {
  2004. let [routeId, result] = actionResult.pendingActionResult;
  2005. if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) {
  2006. pendingNavigationController = null;
  2007. completeNavigation(location, {
  2008. matches: actionResult.matches,
  2009. loaderData: {},
  2010. errors: {
  2011. [routeId]: result.error
  2012. }
  2013. });
  2014. return;
  2015. }
  2016. }
  2017. matches = actionResult.matches || matches;
  2018. pendingActionResult = actionResult.pendingActionResult;
  2019. loadingNavigation = getLoadingNavigation(location, opts.submission);
  2020. flushSync = false;
  2021. // No need to do fog of war matching again on loader execution
  2022. fogOfWar.active = false;
  2023. // Create a GET request for the loaders
  2024. request = createClientSideRequest(init.history, request.url, request.signal);
  2025. }
  2026. // Call loaders
  2027. let {
  2028. shortCircuited,
  2029. matches: updatedMatches,
  2030. loaderData,
  2031. errors
  2032. } = await handleLoaders(request, location, matches, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
  2033. if (shortCircuited) {
  2034. return;
  2035. }
  2036. // Clean up now that the action/loaders have completed. Don't clean up if
  2037. // we short circuited because pendingNavigationController will have already
  2038. // been assigned to a new controller for the next navigation
  2039. pendingNavigationController = null;
  2040. completeNavigation(location, _extends({
  2041. matches: updatedMatches || matches
  2042. }, getActionDataForCommit(pendingActionResult), {
  2043. loaderData,
  2044. errors
  2045. }));
  2046. }
  2047. // Call the action matched by the leaf route for this navigation and handle
  2048. // redirects/errors
  2049. async function handleAction(request, location, submission, matches, isFogOfWar, opts) {
  2050. if (opts === void 0) {
  2051. opts = {};
  2052. }
  2053. interruptActiveLoads();
  2054. // Put us in a submitting state
  2055. let navigation = getSubmittingNavigation(location, submission);
  2056. updateState({
  2057. navigation
  2058. }, {
  2059. flushSync: opts.flushSync === true
  2060. });
  2061. if (isFogOfWar) {
  2062. let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
  2063. if (discoverResult.type === "aborted") {
  2064. return {
  2065. shortCircuited: true
  2066. };
  2067. } else if (discoverResult.type === "error") {
  2068. let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
  2069. return {
  2070. matches: discoverResult.partialMatches,
  2071. pendingActionResult: [boundaryId, {
  2072. type: ResultType.error,
  2073. error: discoverResult.error
  2074. }]
  2075. };
  2076. } else if (!discoverResult.matches) {
  2077. let {
  2078. notFoundMatches,
  2079. error,
  2080. route
  2081. } = handleNavigational404(location.pathname);
  2082. return {
  2083. matches: notFoundMatches,
  2084. pendingActionResult: [route.id, {
  2085. type: ResultType.error,
  2086. error
  2087. }]
  2088. };
  2089. } else {
  2090. matches = discoverResult.matches;
  2091. }
  2092. }
  2093. // Call our action and get the result
  2094. let result;
  2095. let actionMatch = getTargetMatch(matches, location);
  2096. if (!actionMatch.route.action && !actionMatch.route.lazy) {
  2097. result = {
  2098. type: ResultType.error,
  2099. error: getInternalRouterError(405, {
  2100. method: request.method,
  2101. pathname: location.pathname,
  2102. routeId: actionMatch.route.id
  2103. })
  2104. };
  2105. } else {
  2106. let results = await callDataStrategy("action", state, request, [actionMatch], matches, null);
  2107. result = results[actionMatch.route.id];
  2108. if (request.signal.aborted) {
  2109. return {
  2110. shortCircuited: true
  2111. };
  2112. }
  2113. }
  2114. if (isRedirectResult(result)) {
  2115. let replace;
  2116. if (opts && opts.replace != null) {
  2117. replace = opts.replace;
  2118. } else {
  2119. // If the user didn't explicity indicate replace behavior, replace if
  2120. // we redirected to the exact same location we're currently at to avoid
  2121. // double back-buttons
  2122. let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename, init.history);
  2123. replace = location === state.location.pathname + state.location.search;
  2124. }
  2125. await startRedirectNavigation(request, result, true, {
  2126. submission,
  2127. replace
  2128. });
  2129. return {
  2130. shortCircuited: true
  2131. };
  2132. }
  2133. if (isDeferredResult(result)) {
  2134. throw getInternalRouterError(400, {
  2135. type: "defer-action"
  2136. });
  2137. }
  2138. if (isErrorResult(result)) {
  2139. // Store off the pending error - we use it to determine which loaders
  2140. // to call and will commit it when we complete the navigation
  2141. let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
  2142. // By default, all submissions to the current location are REPLACE
  2143. // navigations, but if the action threw an error that'll be rendered in
  2144. // an errorElement, we fall back to PUSH so that the user can use the
  2145. // back button to get back to the pre-submission form location to try
  2146. // again
  2147. if ((opts && opts.replace) !== true) {
  2148. pendingAction = Action.Push;
  2149. }
  2150. return {
  2151. matches,
  2152. pendingActionResult: [boundaryMatch.route.id, result]
  2153. };
  2154. }
  2155. return {
  2156. matches,
  2157. pendingActionResult: [actionMatch.route.id, result]
  2158. };
  2159. }
  2160. // Call all applicable loaders for the given matches, handling redirects,
  2161. // errors, etc.
  2162. async function handleLoaders(request, location, matches, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
  2163. // Figure out the right navigation we want to use for data loading
  2164. let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
  2165. // If this was a redirect from an action we don't have a "submission" but
  2166. // we have it on the loading navigation so use that if available
  2167. let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
  2168. // If this is an uninterrupted revalidation, we remain in our current idle
  2169. // state. If not, we need to switch to our loading state and load data,
  2170. // preserving any new action data or existing action data (in the case of
  2171. // a revalidation interrupting an actionReload)
  2172. // If we have partialHydration enabled, then don't update the state for the
  2173. // initial data load since it's not a "navigation"
  2174. let shouldUpdateNavigationState = !isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration);
  2175. // When fog of war is enabled, we enter our `loading` state earlier so we
  2176. // can discover new routes during the `loading` state. We skip this if
  2177. // we've already run actions since we would have done our matching already.
  2178. // If the children() function threw then, we want to proceed with the
  2179. // partial matches it discovered.
  2180. if (isFogOfWar) {
  2181. if (shouldUpdateNavigationState) {
  2182. let actionData = getUpdatedActionData(pendingActionResult);
  2183. updateState(_extends({
  2184. navigation: loadingNavigation
  2185. }, actionData !== undefined ? {
  2186. actionData
  2187. } : {}), {
  2188. flushSync
  2189. });
  2190. }
  2191. let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
  2192. if (discoverResult.type === "aborted") {
  2193. return {
  2194. shortCircuited: true
  2195. };
  2196. } else if (discoverResult.type === "error") {
  2197. let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
  2198. return {
  2199. matches: discoverResult.partialMatches,
  2200. loaderData: {},
  2201. errors: {
  2202. [boundaryId]: discoverResult.error
  2203. }
  2204. };
  2205. } else if (!discoverResult.matches) {
  2206. let {
  2207. error,
  2208. notFoundMatches,
  2209. route
  2210. } = handleNavigational404(location.pathname);
  2211. return {
  2212. matches: notFoundMatches,
  2213. loaderData: {},
  2214. errors: {
  2215. [route.id]: error
  2216. }
  2217. };
  2218. } else {
  2219. matches = discoverResult.matches;
  2220. }
  2221. }
  2222. let routesToUse = inFlightDataRoutes || dataRoutes;
  2223. let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, future.v7_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult);
  2224. // Cancel pending deferreds for no-longer-matched routes or routes we're
  2225. // about to reload. Note that if this is an action reload we would have
  2226. // already cancelled all pending deferreds so this would be a no-op
  2227. cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
  2228. pendingNavigationLoadId = ++incrementingLoadId;
  2229. // Short circuit if we have no loaders to run
  2230. if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
  2231. let updatedFetchers = markFetchRedirectsDone();
  2232. completeNavigation(location, _extends({
  2233. matches,
  2234. loaderData: {},
  2235. // Commit pending error if we're short circuiting
  2236. errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
  2237. [pendingActionResult[0]]: pendingActionResult[1].error
  2238. } : null
  2239. }, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
  2240. fetchers: new Map(state.fetchers)
  2241. } : {}), {
  2242. flushSync
  2243. });
  2244. return {
  2245. shortCircuited: true
  2246. };
  2247. }
  2248. if (shouldUpdateNavigationState) {
  2249. let updates = {};
  2250. if (!isFogOfWar) {
  2251. // Only update navigation/actionNData if we didn't already do it above
  2252. updates.navigation = loadingNavigation;
  2253. let actionData = getUpdatedActionData(pendingActionResult);
  2254. if (actionData !== undefined) {
  2255. updates.actionData = actionData;
  2256. }
  2257. }
  2258. if (revalidatingFetchers.length > 0) {
  2259. updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
  2260. }
  2261. updateState(updates, {
  2262. flushSync
  2263. });
  2264. }
  2265. revalidatingFetchers.forEach(rf => {
  2266. abortFetcher(rf.key);
  2267. if (rf.controller) {
  2268. // Fetchers use an independent AbortController so that aborting a fetcher
  2269. // (via deleteFetcher) does not abort the triggering navigation that
  2270. // triggered the revalidation
  2271. fetchControllers.set(rf.key, rf.controller);
  2272. }
  2273. });
  2274. // Proxy navigation abort through to revalidation fetchers
  2275. let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
  2276. if (pendingNavigationController) {
  2277. pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
  2278. }
  2279. let {
  2280. loaderResults,
  2281. fetcherResults
  2282. } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, request);
  2283. if (request.signal.aborted) {
  2284. return {
  2285. shortCircuited: true
  2286. };
  2287. }
  2288. // Clean up _after_ loaders have completed. Don't clean up if we short
  2289. // circuited because fetchControllers would have been aborted and
  2290. // reassigned to new controllers for the next navigation
  2291. if (pendingNavigationController) {
  2292. pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
  2293. }
  2294. revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
  2295. // If any loaders returned a redirect Response, start a new REPLACE navigation
  2296. let redirect = findRedirect(loaderResults);
  2297. if (redirect) {
  2298. await startRedirectNavigation(request, redirect.result, true, {
  2299. replace
  2300. });
  2301. return {
  2302. shortCircuited: true
  2303. };
  2304. }
  2305. redirect = findRedirect(fetcherResults);
  2306. if (redirect) {
  2307. // If this redirect came from a fetcher make sure we mark it in
  2308. // fetchRedirectIds so it doesn't get revalidated on the next set of
  2309. // loader executions
  2310. fetchRedirectIds.add(redirect.key);
  2311. await startRedirectNavigation(request, redirect.result, true, {
  2312. replace
  2313. });
  2314. return {
  2315. shortCircuited: true
  2316. };
  2317. }
  2318. // Process and commit output from loaders
  2319. let {
  2320. loaderData,
  2321. errors
  2322. } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
  2323. // Wire up subscribers to update loaderData as promises settle
  2324. activeDeferreds.forEach((deferredData, routeId) => {
  2325. deferredData.subscribe(aborted => {
  2326. // Note: No need to updateState here since the TrackedPromise on
  2327. // loaderData is stable across resolve/reject
  2328. // Remove this instance if we were aborted or if promises have settled
  2329. if (aborted || deferredData.done) {
  2330. activeDeferreds.delete(routeId);
  2331. }
  2332. });
  2333. });
  2334. // Preserve SSR errors during partial hydration
  2335. if (future.v7_partialHydration && initialHydration && state.errors) {
  2336. errors = _extends({}, state.errors, errors);
  2337. }
  2338. let updatedFetchers = markFetchRedirectsDone();
  2339. let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
  2340. let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
  2341. return _extends({
  2342. matches,
  2343. loaderData,
  2344. errors
  2345. }, shouldUpdateFetchers ? {
  2346. fetchers: new Map(state.fetchers)
  2347. } : {});
  2348. }
  2349. function getUpdatedActionData(pendingActionResult) {
  2350. if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
  2351. // This is cast to `any` currently because `RouteData`uses any and it
  2352. // would be a breaking change to use any.
  2353. // TODO: v7 - change `RouteData` to use `unknown` instead of `any`
  2354. return {
  2355. [pendingActionResult[0]]: pendingActionResult[1].data
  2356. };
  2357. } else if (state.actionData) {
  2358. if (Object.keys(state.actionData).length === 0) {
  2359. return null;
  2360. } else {
  2361. return state.actionData;
  2362. }
  2363. }
  2364. }
  2365. function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
  2366. revalidatingFetchers.forEach(rf => {
  2367. let fetcher = state.fetchers.get(rf.key);
  2368. let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
  2369. state.fetchers.set(rf.key, revalidatingFetcher);
  2370. });
  2371. return new Map(state.fetchers);
  2372. }
  2373. // Trigger a fetcher load/submit for the given fetcher key
  2374. function fetch(key, routeId, href, opts) {
  2375. if (isServer) {
  2376. throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
  2377. }
  2378. abortFetcher(key);
  2379. let flushSync = (opts && opts.flushSync) === true;
  2380. let routesToUse = inFlightDataRoutes || dataRoutes;
  2381. let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
  2382. let matches = matchRoutes(routesToUse, normalizedPath, basename);
  2383. let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
  2384. if (fogOfWar.active && fogOfWar.matches) {
  2385. matches = fogOfWar.matches;
  2386. }
  2387. if (!matches) {
  2388. setFetcherError(key, routeId, getInternalRouterError(404, {
  2389. pathname: normalizedPath
  2390. }), {
  2391. flushSync
  2392. });
  2393. return;
  2394. }
  2395. let {
  2396. path,
  2397. submission,
  2398. error
  2399. } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
  2400. if (error) {
  2401. setFetcherError(key, routeId, error, {
  2402. flushSync
  2403. });
  2404. return;
  2405. }
  2406. let match = getTargetMatch(matches, path);
  2407. let preventScrollReset = (opts && opts.preventScrollReset) === true;
  2408. if (submission && isMutationMethod(submission.formMethod)) {
  2409. handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
  2410. return;
  2411. }
  2412. // Store off the match so we can call it's shouldRevalidate on subsequent
  2413. // revalidations
  2414. fetchLoadMatches.set(key, {
  2415. routeId,
  2416. path
  2417. });
  2418. handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
  2419. }
  2420. // Call the action for the matched fetcher.submit(), and then handle redirects,
  2421. // errors, and revalidation
  2422. async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
  2423. interruptActiveLoads();
  2424. fetchLoadMatches.delete(key);
  2425. function detectAndHandle405Error(m) {
  2426. if (!m.route.action && !m.route.lazy) {
  2427. let error = getInternalRouterError(405, {
  2428. method: submission.formMethod,
  2429. pathname: path,
  2430. routeId: routeId
  2431. });
  2432. setFetcherError(key, routeId, error, {
  2433. flushSync
  2434. });
  2435. return true;
  2436. }
  2437. return false;
  2438. }
  2439. if (!isFogOfWar && detectAndHandle405Error(match)) {
  2440. return;
  2441. }
  2442. // Put this fetcher into it's submitting state
  2443. let existingFetcher = state.fetchers.get(key);
  2444. updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
  2445. flushSync
  2446. });
  2447. let abortController = new AbortController();
  2448. let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
  2449. if (isFogOfWar) {
  2450. let discoverResult = await discoverRoutes(requestMatches, new URL(fetchRequest.url).pathname, fetchRequest.signal, key);
  2451. if (discoverResult.type === "aborted") {
  2452. return;
  2453. } else if (discoverResult.type === "error") {
  2454. setFetcherError(key, routeId, discoverResult.error, {
  2455. flushSync
  2456. });
  2457. return;
  2458. } else if (!discoverResult.matches) {
  2459. setFetcherError(key, routeId, getInternalRouterError(404, {
  2460. pathname: path
  2461. }), {
  2462. flushSync
  2463. });
  2464. return;
  2465. } else {
  2466. requestMatches = discoverResult.matches;
  2467. match = getTargetMatch(requestMatches, path);
  2468. if (detectAndHandle405Error(match)) {
  2469. return;
  2470. }
  2471. }
  2472. }
  2473. // Call the action for the fetcher
  2474. fetchControllers.set(key, abortController);
  2475. let originatingLoadId = incrementingLoadId;
  2476. let actionResults = await callDataStrategy("action", state, fetchRequest, [match], requestMatches, key);
  2477. let actionResult = actionResults[match.route.id];
  2478. if (fetchRequest.signal.aborted) {
  2479. // We can delete this so long as we weren't aborted by our own fetcher
  2480. // re-submit which would have put _new_ controller is in fetchControllers
  2481. if (fetchControllers.get(key) === abortController) {
  2482. fetchControllers.delete(key);
  2483. }
  2484. return;
  2485. }
  2486. // When using v7_fetcherPersist, we don't want errors bubbling up to the UI
  2487. // or redirects processed for unmounted fetchers so we just revert them to
  2488. // idle
  2489. if (future.v7_fetcherPersist && deletedFetchers.has(key)) {
  2490. if (isRedirectResult(actionResult) || isErrorResult(actionResult)) {
  2491. updateFetcherState(key, getDoneFetcher(undefined));
  2492. return;
  2493. }
  2494. // Let SuccessResult's fall through for revalidation
  2495. } else {
  2496. if (isRedirectResult(actionResult)) {
  2497. fetchControllers.delete(key);
  2498. if (pendingNavigationLoadId > originatingLoadId) {
  2499. // A new navigation was kicked off after our action started, so that
  2500. // should take precedence over this redirect navigation. We already
  2501. // set isRevalidationRequired so all loaders for the new route should
  2502. // fire unless opted out via shouldRevalidate
  2503. updateFetcherState(key, getDoneFetcher(undefined));
  2504. return;
  2505. } else {
  2506. fetchRedirectIds.add(key);
  2507. updateFetcherState(key, getLoadingFetcher(submission));
  2508. return startRedirectNavigation(fetchRequest, actionResult, false, {
  2509. fetcherSubmission: submission,
  2510. preventScrollReset
  2511. });
  2512. }
  2513. }
  2514. // Process any non-redirect errors thrown
  2515. if (isErrorResult(actionResult)) {
  2516. setFetcherError(key, routeId, actionResult.error);
  2517. return;
  2518. }
  2519. }
  2520. if (isDeferredResult(actionResult)) {
  2521. throw getInternalRouterError(400, {
  2522. type: "defer-action"
  2523. });
  2524. }
  2525. // Start the data load for current matches, or the next location if we're
  2526. // in the middle of a navigation
  2527. let nextLocation = state.navigation.location || state.location;
  2528. let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
  2529. let routesToUse = inFlightDataRoutes || dataRoutes;
  2530. let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;
  2531. invariant(matches, "Didn't find any matches after fetcher action");
  2532. let loadId = ++incrementingLoadId;
  2533. fetchReloadIds.set(key, loadId);
  2534. let loadFetcher = getLoadingFetcher(submission, actionResult.data);
  2535. state.fetchers.set(key, loadFetcher);
  2536. let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, future.v7_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, [match.route.id, actionResult]);
  2537. // Put all revalidating fetchers into the loading state, except for the
  2538. // current fetcher which we want to keep in it's current loading state which
  2539. // contains it's action submission info + action data
  2540. revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
  2541. let staleKey = rf.key;
  2542. let existingFetcher = state.fetchers.get(staleKey);
  2543. let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
  2544. state.fetchers.set(staleKey, revalidatingFetcher);
  2545. abortFetcher(staleKey);
  2546. if (rf.controller) {
  2547. fetchControllers.set(staleKey, rf.controller);
  2548. }
  2549. });
  2550. updateState({
  2551. fetchers: new Map(state.fetchers)
  2552. });
  2553. let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
  2554. abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
  2555. let {
  2556. loaderResults,
  2557. fetcherResults
  2558. } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
  2559. if (abortController.signal.aborted) {
  2560. return;
  2561. }
  2562. abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
  2563. fetchReloadIds.delete(key);
  2564. fetchControllers.delete(key);
  2565. revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
  2566. let redirect = findRedirect(loaderResults);
  2567. if (redirect) {
  2568. return startRedirectNavigation(revalidationRequest, redirect.result, false, {
  2569. preventScrollReset
  2570. });
  2571. }
  2572. redirect = findRedirect(fetcherResults);
  2573. if (redirect) {
  2574. // If this redirect came from a fetcher make sure we mark it in
  2575. // fetchRedirectIds so it doesn't get revalidated on the next set of
  2576. // loader executions
  2577. fetchRedirectIds.add(redirect.key);
  2578. return startRedirectNavigation(revalidationRequest, redirect.result, false, {
  2579. preventScrollReset
  2580. });
  2581. }
  2582. // Process and commit output from loaders
  2583. let {
  2584. loaderData,
  2585. errors
  2586. } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
  2587. // Since we let revalidations complete even if the submitting fetcher was
  2588. // deleted, only put it back to idle if it hasn't been deleted
  2589. if (state.fetchers.has(key)) {
  2590. let doneFetcher = getDoneFetcher(actionResult.data);
  2591. state.fetchers.set(key, doneFetcher);
  2592. }
  2593. abortStaleFetchLoads(loadId);
  2594. // If we are currently in a navigation loading state and this fetcher is
  2595. // more recent than the navigation, we want the newer data so abort the
  2596. // navigation and complete it with the fetcher data
  2597. if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
  2598. invariant(pendingAction, "Expected pending action");
  2599. pendingNavigationController && pendingNavigationController.abort();
  2600. completeNavigation(state.navigation.location, {
  2601. matches,
  2602. loaderData,
  2603. errors,
  2604. fetchers: new Map(state.fetchers)
  2605. });
  2606. } else {
  2607. // otherwise just update with the fetcher data, preserving any existing
  2608. // loaderData for loaders that did not need to reload. We have to
  2609. // manually merge here since we aren't going through completeNavigation
  2610. updateState({
  2611. errors,
  2612. loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
  2613. fetchers: new Map(state.fetchers)
  2614. });
  2615. isRevalidationRequired = false;
  2616. }
  2617. }
  2618. // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
  2619. async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
  2620. let existingFetcher = state.fetchers.get(key);
  2621. updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
  2622. flushSync
  2623. });
  2624. let abortController = new AbortController();
  2625. let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
  2626. if (isFogOfWar) {
  2627. let discoverResult = await discoverRoutes(matches, new URL(fetchRequest.url).pathname, fetchRequest.signal, key);
  2628. if (discoverResult.type === "aborted") {
  2629. return;
  2630. } else if (discoverResult.type === "error") {
  2631. setFetcherError(key, routeId, discoverResult.error, {
  2632. flushSync
  2633. });
  2634. return;
  2635. } else if (!discoverResult.matches) {
  2636. setFetcherError(key, routeId, getInternalRouterError(404, {
  2637. pathname: path
  2638. }), {
  2639. flushSync
  2640. });
  2641. return;
  2642. } else {
  2643. matches = discoverResult.matches;
  2644. match = getTargetMatch(matches, path);
  2645. }
  2646. }
  2647. // Call the loader for this fetcher route match
  2648. fetchControllers.set(key, abortController);
  2649. let originatingLoadId = incrementingLoadId;
  2650. let results = await callDataStrategy("loader", state, fetchRequest, [match], matches, key);
  2651. let result = results[match.route.id];
  2652. // Deferred isn't supported for fetcher loads, await everything and treat it
  2653. // as a normal load. resolveDeferredData will return undefined if this
  2654. // fetcher gets aborted, so we just leave result untouched and short circuit
  2655. // below if that happens
  2656. if (isDeferredResult(result)) {
  2657. result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
  2658. }
  2659. // We can delete this so long as we weren't aborted by our our own fetcher
  2660. // re-load which would have put _new_ controller is in fetchControllers
  2661. if (fetchControllers.get(key) === abortController) {
  2662. fetchControllers.delete(key);
  2663. }
  2664. if (fetchRequest.signal.aborted) {
  2665. return;
  2666. }
  2667. // We don't want errors bubbling up or redirects followed for unmounted
  2668. // fetchers, so short circuit here if it was removed from the UI
  2669. if (deletedFetchers.has(key)) {
  2670. updateFetcherState(key, getDoneFetcher(undefined));
  2671. return;
  2672. }
  2673. // If the loader threw a redirect Response, start a new REPLACE navigation
  2674. if (isRedirectResult(result)) {
  2675. if (pendingNavigationLoadId > originatingLoadId) {
  2676. // A new navigation was kicked off after our loader started, so that
  2677. // should take precedence over this redirect navigation
  2678. updateFetcherState(key, getDoneFetcher(undefined));
  2679. return;
  2680. } else {
  2681. fetchRedirectIds.add(key);
  2682. await startRedirectNavigation(fetchRequest, result, false, {
  2683. preventScrollReset
  2684. });
  2685. return;
  2686. }
  2687. }
  2688. // Process any non-redirect errors thrown
  2689. if (isErrorResult(result)) {
  2690. setFetcherError(key, routeId, result.error);
  2691. return;
  2692. }
  2693. invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
  2694. // Put the fetcher back into an idle state
  2695. updateFetcherState(key, getDoneFetcher(result.data));
  2696. }
  2697. /**
  2698. * Utility function to handle redirects returned from an action or loader.
  2699. * Normally, a redirect "replaces" the navigation that triggered it. So, for
  2700. * example:
  2701. *
  2702. * - user is on /a
  2703. * - user clicks a link to /b
  2704. * - loader for /b redirects to /c
  2705. *
  2706. * In a non-JS app the browser would track the in-flight navigation to /b and
  2707. * then replace it with /c when it encountered the redirect response. In
  2708. * the end it would only ever update the URL bar with /c.
  2709. *
  2710. * In client-side routing using pushState/replaceState, we aim to emulate
  2711. * this behavior and we also do not update history until the end of the
  2712. * navigation (including processed redirects). This means that we never
  2713. * actually touch history until we've processed redirects, so we just use
  2714. * the history action from the original navigation (PUSH or REPLACE).
  2715. */
  2716. async function startRedirectNavigation(request, redirect, isNavigation, _temp2) {
  2717. let {
  2718. submission,
  2719. fetcherSubmission,
  2720. preventScrollReset,
  2721. replace
  2722. } = _temp2 === void 0 ? {} : _temp2;
  2723. if (redirect.response.headers.has("X-Remix-Revalidate")) {
  2724. isRevalidationRequired = true;
  2725. }
  2726. let location = redirect.response.headers.get("Location");
  2727. invariant(location, "Expected a Location header on the redirect Response");
  2728. location = normalizeRedirectLocation(location, new URL(request.url), basename, init.history);
  2729. let redirectLocation = createLocation(state.location, location, {
  2730. _isRedirect: true
  2731. });
  2732. if (isBrowser) {
  2733. let isDocumentReload = false;
  2734. if (redirect.response.headers.has("X-Remix-Reload-Document")) {
  2735. // Hard reload if the response contained X-Remix-Reload-Document
  2736. isDocumentReload = true;
  2737. } else if (ABSOLUTE_URL_REGEX.test(location)) {
  2738. const url = init.history.createURL(location);
  2739. isDocumentReload =
  2740. // Hard reload if it's an absolute URL to a new origin
  2741. url.origin !== routerWindow.location.origin ||
  2742. // Hard reload if it's an absolute URL that does not match our basename
  2743. stripBasename(url.pathname, basename) == null;
  2744. }
  2745. if (isDocumentReload) {
  2746. if (replace) {
  2747. routerWindow.location.replace(location);
  2748. } else {
  2749. routerWindow.location.assign(location);
  2750. }
  2751. return;
  2752. }
  2753. }
  2754. // There's no need to abort on redirects, since we don't detect the
  2755. // redirect until the action/loaders have settled
  2756. pendingNavigationController = null;
  2757. let redirectHistoryAction = replace === true || redirect.response.headers.has("X-Remix-Replace") ? Action.Replace : Action.Push;
  2758. // Use the incoming submission if provided, fallback on the active one in
  2759. // state.navigation
  2760. let {
  2761. formMethod,
  2762. formAction,
  2763. formEncType
  2764. } = state.navigation;
  2765. if (!submission && !fetcherSubmission && formMethod && formAction && formEncType) {
  2766. submission = getSubmissionFromNavigation(state.navigation);
  2767. }
  2768. // If this was a 307/308 submission we want to preserve the HTTP method and
  2769. // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
  2770. // redirected location
  2771. let activeSubmission = submission || fetcherSubmission;
  2772. if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
  2773. await startNavigation(redirectHistoryAction, redirectLocation, {
  2774. submission: _extends({}, activeSubmission, {
  2775. formAction: location
  2776. }),
  2777. // Preserve these flags across redirects
  2778. preventScrollReset: preventScrollReset || pendingPreventScrollReset,
  2779. enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
  2780. });
  2781. } else {
  2782. // If we have a navigation submission, we will preserve it through the
  2783. // redirect navigation
  2784. let overrideNavigation = getLoadingNavigation(redirectLocation, submission);
  2785. await startNavigation(redirectHistoryAction, redirectLocation, {
  2786. overrideNavigation,
  2787. // Send fetcher submissions through for shouldRevalidate
  2788. fetcherSubmission,
  2789. // Preserve these flags across redirects
  2790. preventScrollReset: preventScrollReset || pendingPreventScrollReset,
  2791. enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
  2792. });
  2793. }
  2794. }
  2795. // Utility wrapper for calling dataStrategy client-side without having to
  2796. // pass around the manifest, mapRouteProperties, etc.
  2797. async function callDataStrategy(type, state, request, matchesToLoad, matches, fetcherKey) {
  2798. let results;
  2799. let dataResults = {};
  2800. try {
  2801. results = await callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties);
  2802. } catch (e) {
  2803. // If the outer dataStrategy method throws, just return the error for all
  2804. // matches - and it'll naturally bubble to the root
  2805. matchesToLoad.forEach(m => {
  2806. dataResults[m.route.id] = {
  2807. type: ResultType.error,
  2808. error: e
  2809. };
  2810. });
  2811. return dataResults;
  2812. }
  2813. for (let [routeId, result] of Object.entries(results)) {
  2814. if (isRedirectDataStrategyResultResult(result)) {
  2815. let response = result.result;
  2816. dataResults[routeId] = {
  2817. type: ResultType.redirect,
  2818. response: normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, future.v7_relativeSplatPath)
  2819. };
  2820. } else {
  2821. dataResults[routeId] = await convertDataStrategyResultToDataResult(result);
  2822. }
  2823. }
  2824. return dataResults;
  2825. }
  2826. async function callLoadersAndMaybeResolveData(state, matches, matchesToLoad, fetchersToLoad, request) {
  2827. let currentMatches = state.matches;
  2828. // Kick off loaders and fetchers in parallel
  2829. let loaderResultsPromise = callDataStrategy("loader", state, request, matchesToLoad, matches, null);
  2830. let fetcherResultsPromise = Promise.all(fetchersToLoad.map(async f => {
  2831. if (f.matches && f.match && f.controller) {
  2832. let results = await callDataStrategy("loader", state, createClientSideRequest(init.history, f.path, f.controller.signal), [f.match], f.matches, f.key);
  2833. let result = results[f.match.route.id];
  2834. // Fetcher results are keyed by fetcher key from here on out, not routeId
  2835. return {
  2836. [f.key]: result
  2837. };
  2838. } else {
  2839. return Promise.resolve({
  2840. [f.key]: {
  2841. type: ResultType.error,
  2842. error: getInternalRouterError(404, {
  2843. pathname: f.path
  2844. })
  2845. }
  2846. });
  2847. }
  2848. }));
  2849. let loaderResults = await loaderResultsPromise;
  2850. let fetcherResults = (await fetcherResultsPromise).reduce((acc, r) => Object.assign(acc, r), {});
  2851. await Promise.all([resolveNavigationDeferredResults(matches, loaderResults, request.signal, currentMatches, state.loaderData), resolveFetcherDeferredResults(matches, fetcherResults, fetchersToLoad)]);
  2852. return {
  2853. loaderResults,
  2854. fetcherResults
  2855. };
  2856. }
  2857. function interruptActiveLoads() {
  2858. // Every interruption triggers a revalidation
  2859. isRevalidationRequired = true;
  2860. // Cancel pending route-level deferreds and mark cancelled routes for
  2861. // revalidation
  2862. cancelledDeferredRoutes.push(...cancelActiveDeferreds());
  2863. // Abort in-flight fetcher loads
  2864. fetchLoadMatches.forEach((_, key) => {
  2865. if (fetchControllers.has(key)) {
  2866. cancelledFetcherLoads.add(key);
  2867. }
  2868. abortFetcher(key);
  2869. });
  2870. }
  2871. function updateFetcherState(key, fetcher, opts) {
  2872. if (opts === void 0) {
  2873. opts = {};
  2874. }
  2875. state.fetchers.set(key, fetcher);
  2876. updateState({
  2877. fetchers: new Map(state.fetchers)
  2878. }, {
  2879. flushSync: (opts && opts.flushSync) === true
  2880. });
  2881. }
  2882. function setFetcherError(key, routeId, error, opts) {
  2883. if (opts === void 0) {
  2884. opts = {};
  2885. }
  2886. let boundaryMatch = findNearestBoundary(state.matches, routeId);
  2887. deleteFetcher(key);
  2888. updateState({
  2889. errors: {
  2890. [boundaryMatch.route.id]: error
  2891. },
  2892. fetchers: new Map(state.fetchers)
  2893. }, {
  2894. flushSync: (opts && opts.flushSync) === true
  2895. });
  2896. }
  2897. function getFetcher(key) {
  2898. activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
  2899. // If this fetcher was previously marked for deletion, unmark it since we
  2900. // have a new instance
  2901. if (deletedFetchers.has(key)) {
  2902. deletedFetchers.delete(key);
  2903. }
  2904. return state.fetchers.get(key) || IDLE_FETCHER;
  2905. }
  2906. function deleteFetcher(key) {
  2907. let fetcher = state.fetchers.get(key);
  2908. // Don't abort the controller if this is a deletion of a fetcher.submit()
  2909. // in it's loading phase since - we don't want to abort the corresponding
  2910. // revalidation and want them to complete and land
  2911. if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
  2912. abortFetcher(key);
  2913. }
  2914. fetchLoadMatches.delete(key);
  2915. fetchReloadIds.delete(key);
  2916. fetchRedirectIds.delete(key);
  2917. // If we opted into the flag we can clear this now since we're calling
  2918. // deleteFetcher() at the end of updateState() and we've already handed the
  2919. // deleted fetcher keys off to the data layer.
  2920. // If not, we're eagerly calling deleteFetcher() and we need to keep this
  2921. // Set populated until the next updateState call, and we'll clear
  2922. // `deletedFetchers` then
  2923. if (future.v7_fetcherPersist) {
  2924. deletedFetchers.delete(key);
  2925. }
  2926. cancelledFetcherLoads.delete(key);
  2927. state.fetchers.delete(key);
  2928. }
  2929. function deleteFetcherAndUpdateState(key) {
  2930. let count = (activeFetchers.get(key) || 0) - 1;
  2931. if (count <= 0) {
  2932. activeFetchers.delete(key);
  2933. deletedFetchers.add(key);
  2934. if (!future.v7_fetcherPersist) {
  2935. deleteFetcher(key);
  2936. }
  2937. } else {
  2938. activeFetchers.set(key, count);
  2939. }
  2940. updateState({
  2941. fetchers: new Map(state.fetchers)
  2942. });
  2943. }
  2944. function abortFetcher(key) {
  2945. let controller = fetchControllers.get(key);
  2946. if (controller) {
  2947. controller.abort();
  2948. fetchControllers.delete(key);
  2949. }
  2950. }
  2951. function markFetchersDone(keys) {
  2952. for (let key of keys) {
  2953. let fetcher = getFetcher(key);
  2954. let doneFetcher = getDoneFetcher(fetcher.data);
  2955. state.fetchers.set(key, doneFetcher);
  2956. }
  2957. }
  2958. function markFetchRedirectsDone() {
  2959. let doneKeys = [];
  2960. let updatedFetchers = false;
  2961. for (let key of fetchRedirectIds) {
  2962. let fetcher = state.fetchers.get(key);
  2963. invariant(fetcher, "Expected fetcher: " + key);
  2964. if (fetcher.state === "loading") {
  2965. fetchRedirectIds.delete(key);
  2966. doneKeys.push(key);
  2967. updatedFetchers = true;
  2968. }
  2969. }
  2970. markFetchersDone(doneKeys);
  2971. return updatedFetchers;
  2972. }
  2973. function abortStaleFetchLoads(landedId) {
  2974. let yeetedKeys = [];
  2975. for (let [key, id] of fetchReloadIds) {
  2976. if (id < landedId) {
  2977. let fetcher = state.fetchers.get(key);
  2978. invariant(fetcher, "Expected fetcher: " + key);
  2979. if (fetcher.state === "loading") {
  2980. abortFetcher(key);
  2981. fetchReloadIds.delete(key);
  2982. yeetedKeys.push(key);
  2983. }
  2984. }
  2985. }
  2986. markFetchersDone(yeetedKeys);
  2987. return yeetedKeys.length > 0;
  2988. }
  2989. function getBlocker(key, fn) {
  2990. let blocker = state.blockers.get(key) || IDLE_BLOCKER;
  2991. if (blockerFunctions.get(key) !== fn) {
  2992. blockerFunctions.set(key, fn);
  2993. }
  2994. return blocker;
  2995. }
  2996. function deleteBlocker(key) {
  2997. state.blockers.delete(key);
  2998. blockerFunctions.delete(key);
  2999. }
  3000. // Utility function to update blockers, ensuring valid state transitions
  3001. function updateBlocker(key, newBlocker) {
  3002. let blocker = state.blockers.get(key) || IDLE_BLOCKER;
  3003. // Poor mans state machine :)
  3004. // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
  3005. invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
  3006. let blockers = new Map(state.blockers);
  3007. blockers.set(key, newBlocker);
  3008. updateState({
  3009. blockers
  3010. });
  3011. }
  3012. function shouldBlockNavigation(_ref2) {
  3013. let {
  3014. currentLocation,
  3015. nextLocation,
  3016. historyAction
  3017. } = _ref2;
  3018. if (blockerFunctions.size === 0) {
  3019. return;
  3020. }
  3021. // We ony support a single active blocker at the moment since we don't have
  3022. // any compelling use cases for multi-blocker yet
  3023. if (blockerFunctions.size > 1) {
  3024. warning(false, "A router only supports one blocker at a time");
  3025. }
  3026. let entries = Array.from(blockerFunctions.entries());
  3027. let [blockerKey, blockerFunction] = entries[entries.length - 1];
  3028. let blocker = state.blockers.get(blockerKey);
  3029. if (blocker && blocker.state === "proceeding") {
  3030. // If the blocker is currently proceeding, we don't need to re-check
  3031. // it and can let this navigation continue
  3032. return;
  3033. }
  3034. // At this point, we know we're unblocked/blocked so we need to check the
  3035. // user-provided blocker function
  3036. if (blockerFunction({
  3037. currentLocation,
  3038. nextLocation,
  3039. historyAction
  3040. })) {
  3041. return blockerKey;
  3042. }
  3043. }
  3044. function handleNavigational404(pathname) {
  3045. let error = getInternalRouterError(404, {
  3046. pathname
  3047. });
  3048. let routesToUse = inFlightDataRoutes || dataRoutes;
  3049. let {
  3050. matches,
  3051. route
  3052. } = getShortCircuitMatches(routesToUse);
  3053. // Cancel all pending deferred on 404s since we don't keep any routes
  3054. cancelActiveDeferreds();
  3055. return {
  3056. notFoundMatches: matches,
  3057. route,
  3058. error
  3059. };
  3060. }
  3061. function cancelActiveDeferreds(predicate) {
  3062. let cancelledRouteIds = [];
  3063. activeDeferreds.forEach((dfd, routeId) => {
  3064. if (!predicate || predicate(routeId)) {
  3065. // Cancel the deferred - but do not remove from activeDeferreds here -
  3066. // we rely on the subscribers to do that so our tests can assert proper
  3067. // cleanup via _internalActiveDeferreds
  3068. dfd.cancel();
  3069. cancelledRouteIds.push(routeId);
  3070. activeDeferreds.delete(routeId);
  3071. }
  3072. });
  3073. return cancelledRouteIds;
  3074. }
  3075. // Opt in to capturing and reporting scroll positions during navigations,
  3076. // used by the <ScrollRestoration> component
  3077. function enableScrollRestoration(positions, getPosition, getKey) {
  3078. savedScrollPositions = positions;
  3079. getScrollPosition = getPosition;
  3080. getScrollRestorationKey = getKey || null;
  3081. // Perform initial hydration scroll restoration, since we miss the boat on
  3082. // the initial updateState() because we've not yet rendered <ScrollRestoration/>
  3083. // and therefore have no savedScrollPositions available
  3084. if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
  3085. initialScrollRestored = true;
  3086. let y = getSavedScrollPosition(state.location, state.matches);
  3087. if (y != null) {
  3088. updateState({
  3089. restoreScrollPosition: y
  3090. });
  3091. }
  3092. }
  3093. return () => {
  3094. savedScrollPositions = null;
  3095. getScrollPosition = null;
  3096. getScrollRestorationKey = null;
  3097. };
  3098. }
  3099. function getScrollKey(location, matches) {
  3100. if (getScrollRestorationKey) {
  3101. let key = getScrollRestorationKey(location, matches.map(m => convertRouteMatchToUiMatch(m, state.loaderData)));
  3102. return key || location.key;
  3103. }
  3104. return location.key;
  3105. }
  3106. function saveScrollPosition(location, matches) {
  3107. if (savedScrollPositions && getScrollPosition) {
  3108. let key = getScrollKey(location, matches);
  3109. savedScrollPositions[key] = getScrollPosition();
  3110. }
  3111. }
  3112. function getSavedScrollPosition(location, matches) {
  3113. if (savedScrollPositions) {
  3114. let key = getScrollKey(location, matches);
  3115. let y = savedScrollPositions[key];
  3116. if (typeof y === "number") {
  3117. return y;
  3118. }
  3119. }
  3120. return null;
  3121. }
  3122. function checkFogOfWar(matches, routesToUse, pathname) {
  3123. if (patchRoutesOnNavigationImpl) {
  3124. if (!matches) {
  3125. let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
  3126. return {
  3127. active: true,
  3128. matches: fogMatches || []
  3129. };
  3130. } else {
  3131. if (Object.keys(matches[0].params).length > 0) {
  3132. // If we matched a dynamic param or a splat, it might only be because
  3133. // we haven't yet discovered other routes that would match with a
  3134. // higher score. Call patchRoutesOnNavigation just to be sure
  3135. let partialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
  3136. return {
  3137. active: true,
  3138. matches: partialMatches
  3139. };
  3140. }
  3141. }
  3142. }
  3143. return {
  3144. active: false,
  3145. matches: null
  3146. };
  3147. }
  3148. async function discoverRoutes(matches, pathname, signal, fetcherKey) {
  3149. if (!patchRoutesOnNavigationImpl) {
  3150. return {
  3151. type: "success",
  3152. matches
  3153. };
  3154. }
  3155. let partialMatches = matches;
  3156. while (true) {
  3157. let isNonHMR = inFlightDataRoutes == null;
  3158. let routesToUse = inFlightDataRoutes || dataRoutes;
  3159. let localManifest = manifest;
  3160. try {
  3161. await patchRoutesOnNavigationImpl({
  3162. signal,
  3163. path: pathname,
  3164. matches: partialMatches,
  3165. fetcherKey,
  3166. patch: (routeId, children) => {
  3167. if (signal.aborted) return;
  3168. patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
  3169. }
  3170. });
  3171. } catch (e) {
  3172. return {
  3173. type: "error",
  3174. error: e,
  3175. partialMatches
  3176. };
  3177. } finally {
  3178. // If we are not in the middle of an HMR revalidation and we changed the
  3179. // routes, provide a new identity so when we `updateState` at the end of
  3180. // this navigation/fetch `router.routes` will be a new identity and
  3181. // trigger a re-run of memoized `router.routes` dependencies.
  3182. // HMR will already update the identity and reflow when it lands
  3183. // `inFlightDataRoutes` in `completeNavigation`
  3184. if (isNonHMR && !signal.aborted) {
  3185. dataRoutes = [...dataRoutes];
  3186. }
  3187. }
  3188. if (signal.aborted) {
  3189. return {
  3190. type: "aborted"
  3191. };
  3192. }
  3193. let newMatches = matchRoutes(routesToUse, pathname, basename);
  3194. if (newMatches) {
  3195. return {
  3196. type: "success",
  3197. matches: newMatches
  3198. };
  3199. }
  3200. let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
  3201. // Avoid loops if the second pass results in the same partial matches
  3202. if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every((m, i) => m.route.id === newPartialMatches[i].route.id)) {
  3203. return {
  3204. type: "success",
  3205. matches: null
  3206. };
  3207. }
  3208. partialMatches = newPartialMatches;
  3209. }
  3210. }
  3211. function _internalSetRoutes(newRoutes) {
  3212. manifest = {};
  3213. inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
  3214. }
  3215. function patchRoutes(routeId, children) {
  3216. let isNonHMR = inFlightDataRoutes == null;
  3217. let routesToUse = inFlightDataRoutes || dataRoutes;
  3218. patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties);
  3219. // If we are not in the middle of an HMR revalidation and we changed the
  3220. // routes, provide a new identity and trigger a reflow via `updateState`
  3221. // to re-run memoized `router.routes` dependencies.
  3222. // HMR will already update the identity and reflow when it lands
  3223. // `inFlightDataRoutes` in `completeNavigation`
  3224. if (isNonHMR) {
  3225. dataRoutes = [...dataRoutes];
  3226. updateState({});
  3227. }
  3228. }
  3229. router = {
  3230. get basename() {
  3231. return basename;
  3232. },
  3233. get future() {
  3234. return future;
  3235. },
  3236. get state() {
  3237. return state;
  3238. },
  3239. get routes() {
  3240. return dataRoutes;
  3241. },
  3242. get window() {
  3243. return routerWindow;
  3244. },
  3245. initialize,
  3246. subscribe,
  3247. enableScrollRestoration,
  3248. navigate,
  3249. fetch,
  3250. revalidate,
  3251. // Passthrough to history-aware createHref used by useHref so we get proper
  3252. // hash-aware URLs in DOM paths
  3253. createHref: to => init.history.createHref(to),
  3254. encodeLocation: to => init.history.encodeLocation(to),
  3255. getFetcher,
  3256. deleteFetcher: deleteFetcherAndUpdateState,
  3257. dispose,
  3258. getBlocker,
  3259. deleteBlocker,
  3260. patchRoutes,
  3261. _internalFetchControllers: fetchControllers,
  3262. _internalActiveDeferreds: activeDeferreds,
  3263. // TODO: Remove setRoutes, it's temporary to avoid dealing with
  3264. // updating the tree while validating the update algorithm.
  3265. _internalSetRoutes
  3266. };
  3267. return router;
  3268. }
  3269. //#endregion
  3270. ////////////////////////////////////////////////////////////////////////////////
  3271. //#region createStaticHandler
  3272. ////////////////////////////////////////////////////////////////////////////////
  3273. const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
  3274. function createStaticHandler(routes, opts) {
  3275. invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
  3276. let manifest = {};
  3277. let basename = (opts ? opts.basename : null) || "/";
  3278. let mapRouteProperties;
  3279. if (opts != null && opts.mapRouteProperties) {
  3280. mapRouteProperties = opts.mapRouteProperties;
  3281. } else if (opts != null && opts.detectErrorBoundary) {
  3282. // If they are still using the deprecated version, wrap it with the new API
  3283. let detectErrorBoundary = opts.detectErrorBoundary;
  3284. mapRouteProperties = route => ({
  3285. hasErrorBoundary: detectErrorBoundary(route)
  3286. });
  3287. } else {
  3288. mapRouteProperties = defaultMapRouteProperties;
  3289. }
  3290. // Config driven behavior flags
  3291. let future = _extends({
  3292. v7_relativeSplatPath: false,
  3293. v7_throwAbortReason: false
  3294. }, opts ? opts.future : null);
  3295. let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
  3296. /**
  3297. * The query() method is intended for document requests, in which we want to
  3298. * call an optional action and potentially multiple loaders for all nested
  3299. * routes. It returns a StaticHandlerContext object, which is very similar
  3300. * to the router state (location, loaderData, actionData, errors, etc.) and
  3301. * also adds SSR-specific information such as the statusCode and headers
  3302. * from action/loaders Responses.
  3303. *
  3304. * It _should_ never throw and should report all errors through the
  3305. * returned context.errors object, properly associating errors to their error
  3306. * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
  3307. * used to emulate React error boundaries during SSr by performing a second
  3308. * pass only down to the boundaryId.
  3309. *
  3310. * The one exception where we do not return a StaticHandlerContext is when a
  3311. * redirect response is returned or thrown from any action/loader. We
  3312. * propagate that out and return the raw Response so the HTTP server can
  3313. * return it directly.
  3314. *
  3315. * - `opts.requestContext` is an optional server context that will be passed
  3316. * to actions/loaders in the `context` parameter
  3317. * - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
  3318. * the bubbling of errors which allows single-fetch-type implementations
  3319. * where the client will handle the bubbling and we may need to return data
  3320. * for the handling route
  3321. */
  3322. async function query(request, _temp3) {
  3323. let {
  3324. requestContext,
  3325. skipLoaderErrorBubbling,
  3326. dataStrategy
  3327. } = _temp3 === void 0 ? {} : _temp3;
  3328. let url = new URL(request.url);
  3329. let method = request.method;
  3330. let location = createLocation("", createPath(url), null, "default");
  3331. let matches = matchRoutes(dataRoutes, location, basename);
  3332. // SSR supports HEAD requests while SPA doesn't
  3333. if (!isValidMethod(method) && method !== "HEAD") {
  3334. let error = getInternalRouterError(405, {
  3335. method
  3336. });
  3337. let {
  3338. matches: methodNotAllowedMatches,
  3339. route
  3340. } = getShortCircuitMatches(dataRoutes);
  3341. return {
  3342. basename,
  3343. location,
  3344. matches: methodNotAllowedMatches,
  3345. loaderData: {},
  3346. actionData: null,
  3347. errors: {
  3348. [route.id]: error
  3349. },
  3350. statusCode: error.status,
  3351. loaderHeaders: {},
  3352. actionHeaders: {},
  3353. activeDeferreds: null
  3354. };
  3355. } else if (!matches) {
  3356. let error = getInternalRouterError(404, {
  3357. pathname: location.pathname
  3358. });
  3359. let {
  3360. matches: notFoundMatches,
  3361. route
  3362. } = getShortCircuitMatches(dataRoutes);
  3363. return {
  3364. basename,
  3365. location,
  3366. matches: notFoundMatches,
  3367. loaderData: {},
  3368. actionData: null,
  3369. errors: {
  3370. [route.id]: error
  3371. },
  3372. statusCode: error.status,
  3373. loaderHeaders: {},
  3374. actionHeaders: {},
  3375. activeDeferreds: null
  3376. };
  3377. }
  3378. let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, skipLoaderErrorBubbling === true, null);
  3379. if (isResponse(result)) {
  3380. return result;
  3381. }
  3382. // When returning StaticHandlerContext, we patch back in the location here
  3383. // since we need it for React Context. But this helps keep our submit and
  3384. // loadRouteData operating on a Request instead of a Location
  3385. return _extends({
  3386. location,
  3387. basename
  3388. }, result);
  3389. }
  3390. /**
  3391. * The queryRoute() method is intended for targeted route requests, either
  3392. * for fetch ?_data requests or resource route requests. In this case, we
  3393. * are only ever calling a single action or loader, and we are returning the
  3394. * returned value directly. In most cases, this will be a Response returned
  3395. * from the action/loader, but it may be a primitive or other value as well -
  3396. * and in such cases the calling context should handle that accordingly.
  3397. *
  3398. * We do respect the throw/return differentiation, so if an action/loader
  3399. * throws, then this method will throw the value. This is important so we
  3400. * can do proper boundary identification in Remix where a thrown Response
  3401. * must go to the Catch Boundary but a returned Response is happy-path.
  3402. *
  3403. * One thing to note is that any Router-initiated Errors that make sense
  3404. * to associate with a status code will be thrown as an ErrorResponse
  3405. * instance which include the raw Error, such that the calling context can
  3406. * serialize the error as they see fit while including the proper response
  3407. * code. Examples here are 404 and 405 errors that occur prior to reaching
  3408. * any user-defined loaders.
  3409. *
  3410. * - `opts.routeId` allows you to specify the specific route handler to call.
  3411. * If not provided the handler will determine the proper route by matching
  3412. * against `request.url`
  3413. * - `opts.requestContext` is an optional server context that will be passed
  3414. * to actions/loaders in the `context` parameter
  3415. */
  3416. async function queryRoute(request, _temp4) {
  3417. let {
  3418. routeId,
  3419. requestContext,
  3420. dataStrategy
  3421. } = _temp4 === void 0 ? {} : _temp4;
  3422. let url = new URL(request.url);
  3423. let method = request.method;
  3424. let location = createLocation("", createPath(url), null, "default");
  3425. let matches = matchRoutes(dataRoutes, location, basename);
  3426. // SSR supports HEAD requests while SPA doesn't
  3427. if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
  3428. throw getInternalRouterError(405, {
  3429. method
  3430. });
  3431. } else if (!matches) {
  3432. throw getInternalRouterError(404, {
  3433. pathname: location.pathname
  3434. });
  3435. }
  3436. let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
  3437. if (routeId && !match) {
  3438. throw getInternalRouterError(403, {
  3439. pathname: location.pathname,
  3440. routeId
  3441. });
  3442. } else if (!match) {
  3443. // This should never hit I don't think?
  3444. throw getInternalRouterError(404, {
  3445. pathname: location.pathname
  3446. });
  3447. }
  3448. let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, false, match);
  3449. if (isResponse(result)) {
  3450. return result;
  3451. }
  3452. let error = result.errors ? Object.values(result.errors)[0] : undefined;
  3453. if (error !== undefined) {
  3454. // If we got back result.errors, that means the loader/action threw
  3455. // _something_ that wasn't a Response, but it's not guaranteed/required
  3456. // to be an `instanceof Error` either, so we have to use throw here to
  3457. // preserve the "error" state outside of queryImpl.
  3458. throw error;
  3459. }
  3460. // Pick off the right state value to return
  3461. if (result.actionData) {
  3462. return Object.values(result.actionData)[0];
  3463. }
  3464. if (result.loaderData) {
  3465. var _result$activeDeferre;
  3466. let data = Object.values(result.loaderData)[0];
  3467. if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
  3468. data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
  3469. }
  3470. return data;
  3471. }
  3472. return undefined;
  3473. }
  3474. async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch) {
  3475. invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
  3476. try {
  3477. if (isMutationMethod(request.method.toLowerCase())) {
  3478. let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
  3479. return result;
  3480. }
  3481. let result = await loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch);
  3482. return isResponse(result) ? result : _extends({}, result, {
  3483. actionData: null,
  3484. actionHeaders: {}
  3485. });
  3486. } catch (e) {
  3487. // If the user threw/returned a Response in callLoaderOrAction for a
  3488. // `queryRoute` call, we throw the `DataStrategyResult` to bail out early
  3489. // and then return or throw the raw Response here accordingly
  3490. if (isDataStrategyResult(e) && isResponse(e.result)) {
  3491. if (e.type === ResultType.error) {
  3492. throw e.result;
  3493. }
  3494. return e.result;
  3495. }
  3496. // Redirects are always returned since they don't propagate to catch
  3497. // boundaries
  3498. if (isRedirectResponse(e)) {
  3499. return e;
  3500. }
  3501. throw e;
  3502. }
  3503. }
  3504. async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
  3505. let result;
  3506. if (!actionMatch.route.action && !actionMatch.route.lazy) {
  3507. let error = getInternalRouterError(405, {
  3508. method: request.method,
  3509. pathname: new URL(request.url).pathname,
  3510. routeId: actionMatch.route.id
  3511. });
  3512. if (isRouteRequest) {
  3513. throw error;
  3514. }
  3515. result = {
  3516. type: ResultType.error,
  3517. error
  3518. };
  3519. } else {
  3520. let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, dataStrategy);
  3521. result = results[actionMatch.route.id];
  3522. if (request.signal.aborted) {
  3523. throwStaticHandlerAbortedError(request, isRouteRequest, future);
  3524. }
  3525. }
  3526. if (isRedirectResult(result)) {
  3527. // Uhhhh - this should never happen, we should always throw these from
  3528. // callLoaderOrAction, but the type narrowing here keeps TS happy and we
  3529. // can get back on the "throw all redirect responses" train here should
  3530. // this ever happen :/
  3531. throw new Response(null, {
  3532. status: result.response.status,
  3533. headers: {
  3534. Location: result.response.headers.get("Location")
  3535. }
  3536. });
  3537. }
  3538. if (isDeferredResult(result)) {
  3539. let error = getInternalRouterError(400, {
  3540. type: "defer-action"
  3541. });
  3542. if (isRouteRequest) {
  3543. throw error;
  3544. }
  3545. result = {
  3546. type: ResultType.error,
  3547. error
  3548. };
  3549. }
  3550. if (isRouteRequest) {
  3551. // Note: This should only be non-Response values if we get here, since
  3552. // isRouteRequest should throw any Response received in callLoaderOrAction
  3553. if (isErrorResult(result)) {
  3554. throw result.error;
  3555. }
  3556. return {
  3557. matches: [actionMatch],
  3558. loaderData: {},
  3559. actionData: {
  3560. [actionMatch.route.id]: result.data
  3561. },
  3562. errors: null,
  3563. // Note: statusCode + headers are unused here since queryRoute will
  3564. // return the raw Response or value
  3565. statusCode: 200,
  3566. loaderHeaders: {},
  3567. actionHeaders: {},
  3568. activeDeferreds: null
  3569. };
  3570. }
  3571. // Create a GET request for the loaders
  3572. let loaderRequest = new Request(request.url, {
  3573. headers: request.headers,
  3574. redirect: request.redirect,
  3575. signal: request.signal
  3576. });
  3577. if (isErrorResult(result)) {
  3578. // Store off the pending error - we use it to determine which loaders
  3579. // to call and will commit it when we complete the navigation
  3580. let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
  3581. let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
  3582. // action status codes take precedence over loader status codes
  3583. return _extends({}, context, {
  3584. statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
  3585. actionData: null,
  3586. actionHeaders: _extends({}, result.headers ? {
  3587. [actionMatch.route.id]: result.headers
  3588. } : {})
  3589. });
  3590. }
  3591. let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null);
  3592. return _extends({}, context, {
  3593. actionData: {
  3594. [actionMatch.route.id]: result.data
  3595. }
  3596. }, result.statusCode ? {
  3597. statusCode: result.statusCode
  3598. } : {}, {
  3599. actionHeaders: result.headers ? {
  3600. [actionMatch.route.id]: result.headers
  3601. } : {}
  3602. });
  3603. }
  3604. async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
  3605. let isRouteRequest = routeMatch != null;
  3606. // Short circuit if we have no loaders to run (queryRoute())
  3607. if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
  3608. throw getInternalRouterError(400, {
  3609. method: request.method,
  3610. pathname: new URL(request.url).pathname,
  3611. routeId: routeMatch == null ? void 0 : routeMatch.route.id
  3612. });
  3613. }
  3614. let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
  3615. let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
  3616. // Short circuit if we have no loaders to run (query())
  3617. if (matchesToLoad.length === 0) {
  3618. return {
  3619. matches,
  3620. // Add a null for all matched routes for proper revalidation on the client
  3621. loaderData: matches.reduce((acc, m) => Object.assign(acc, {
  3622. [m.route.id]: null
  3623. }), {}),
  3624. errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
  3625. [pendingActionResult[0]]: pendingActionResult[1].error
  3626. } : null,
  3627. statusCode: 200,
  3628. loaderHeaders: {},
  3629. activeDeferreds: null
  3630. };
  3631. }
  3632. let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy);
  3633. if (request.signal.aborted) {
  3634. throwStaticHandlerAbortedError(request, isRouteRequest, future);
  3635. }
  3636. // Process and commit output from loaders
  3637. let activeDeferreds = new Map();
  3638. let context = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
  3639. // Add a null for any non-loader matches for proper revalidation on the client
  3640. let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
  3641. matches.forEach(match => {
  3642. if (!executedLoaders.has(match.route.id)) {
  3643. context.loaderData[match.route.id] = null;
  3644. }
  3645. });
  3646. return _extends({}, context, {
  3647. matches,
  3648. activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
  3649. });
  3650. }
  3651. // Utility wrapper for calling dataStrategy server-side without having to
  3652. // pass around the manifest, mapRouteProperties, etc.
  3653. async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy) {
  3654. let results = await callDataStrategyImpl(dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
  3655. let dataResults = {};
  3656. await Promise.all(matches.map(async match => {
  3657. if (!(match.route.id in results)) {
  3658. return;
  3659. }
  3660. let result = results[match.route.id];
  3661. if (isRedirectDataStrategyResultResult(result)) {
  3662. let response = result.result;
  3663. // Throw redirects and let the server handle them with an HTTP redirect
  3664. throw normalizeRelativeRoutingRedirectResponse(response, request, match.route.id, matches, basename, future.v7_relativeSplatPath);
  3665. }
  3666. if (isResponse(result.result) && isRouteRequest) {
  3667. // For SSR single-route requests, we want to hand Responses back
  3668. // directly without unwrapping
  3669. throw result;
  3670. }
  3671. dataResults[match.route.id] = await convertDataStrategyResultToDataResult(result);
  3672. }));
  3673. return dataResults;
  3674. }
  3675. return {
  3676. dataRoutes,
  3677. query,
  3678. queryRoute
  3679. };
  3680. }
  3681. //#endregion
  3682. ////////////////////////////////////////////////////////////////////////////////
  3683. //#region Helpers
  3684. ////////////////////////////////////////////////////////////////////////////////
  3685. /**
  3686. * Given an existing StaticHandlerContext and an error thrown at render time,
  3687. * provide an updated StaticHandlerContext suitable for a second SSR render
  3688. */
  3689. function getStaticContextFromError(routes, context, error) {
  3690. let newContext = _extends({}, context, {
  3691. statusCode: isRouteErrorResponse(error) ? error.status : 500,
  3692. errors: {
  3693. [context._deepestRenderedBoundaryId || routes[0].id]: error
  3694. }
  3695. });
  3696. return newContext;
  3697. }
  3698. function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
  3699. if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
  3700. throw request.signal.reason;
  3701. }
  3702. let method = isRouteRequest ? "queryRoute" : "query";
  3703. throw new Error(method + "() call aborted: " + request.method + " " + request.url);
  3704. }
  3705. function isSubmissionNavigation(opts) {
  3706. return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
  3707. }
  3708. function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
  3709. let contextualMatches;
  3710. let activeRouteMatch;
  3711. if (fromRouteId) {
  3712. // Grab matches up to the calling route so our route-relative logic is
  3713. // relative to the correct source route
  3714. contextualMatches = [];
  3715. for (let match of matches) {
  3716. contextualMatches.push(match);
  3717. if (match.route.id === fromRouteId) {
  3718. activeRouteMatch = match;
  3719. break;
  3720. }
  3721. }
  3722. } else {
  3723. contextualMatches = matches;
  3724. activeRouteMatch = matches[matches.length - 1];
  3725. }
  3726. // Resolve the relative path
  3727. let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches, v7_relativeSplatPath), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
  3728. // When `to` is not specified we inherit search/hash from the current
  3729. // location, unlike when to="." and we just inherit the path.
  3730. // See https://github.com/remix-run/remix/issues/927
  3731. if (to == null) {
  3732. path.search = location.search;
  3733. path.hash = location.hash;
  3734. }
  3735. // Account for `?index` params when routing to the current location
  3736. if ((to == null || to === "" || to === ".") && activeRouteMatch) {
  3737. let nakedIndex = hasNakedIndexQuery(path.search);
  3738. if (activeRouteMatch.route.index && !nakedIndex) {
  3739. // Add one when we're targeting an index route
  3740. path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
  3741. } else if (!activeRouteMatch.route.index && nakedIndex) {
  3742. // Remove existing ones when we're not
  3743. let params = new URLSearchParams(path.search);
  3744. let indexValues = params.getAll("index");
  3745. params.delete("index");
  3746. indexValues.filter(v => v).forEach(v => params.append("index", v));
  3747. let qs = params.toString();
  3748. path.search = qs ? "?" + qs : "";
  3749. }
  3750. }
  3751. // If we're operating within a basename, prepend it to the pathname. If
  3752. // this is a root navigation, then just use the raw basename which allows
  3753. // the basename to have full control over the presence of a trailing slash
  3754. // on root actions
  3755. if (prependBasename && basename !== "/") {
  3756. path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
  3757. }
  3758. return createPath(path);
  3759. }
  3760. // Normalize navigation options by converting formMethod=GET formData objects to
  3761. // URLSearchParams so they behave identically to links with query params
  3762. function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
  3763. // Return location verbatim on non-submission navigations
  3764. if (!opts || !isSubmissionNavigation(opts)) {
  3765. return {
  3766. path
  3767. };
  3768. }
  3769. if (opts.formMethod && !isValidMethod(opts.formMethod)) {
  3770. return {
  3771. path,
  3772. error: getInternalRouterError(405, {
  3773. method: opts.formMethod
  3774. })
  3775. };
  3776. }
  3777. let getInvalidBodyError = () => ({
  3778. path,
  3779. error: getInternalRouterError(400, {
  3780. type: "invalid-body"
  3781. })
  3782. });
  3783. // Create a Submission on non-GET navigations
  3784. let rawFormMethod = opts.formMethod || "get";
  3785. let formMethod = normalizeFormMethod ? rawFormMethod.toUpperCase() : rawFormMethod.toLowerCase();
  3786. let formAction = stripHashFromPath(path);
  3787. if (opts.body !== undefined) {
  3788. if (opts.formEncType === "text/plain") {
  3789. // text only support POST/PUT/PATCH/DELETE submissions
  3790. if (!isMutationMethod(formMethod)) {
  3791. return getInvalidBodyError();
  3792. }
  3793. let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
  3794. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
  3795. Array.from(opts.body.entries()).reduce((acc, _ref3) => {
  3796. let [name, value] = _ref3;
  3797. return "" + acc + name + "=" + value + "\n";
  3798. }, "") : String(opts.body);
  3799. return {
  3800. path,
  3801. submission: {
  3802. formMethod,
  3803. formAction,
  3804. formEncType: opts.formEncType,
  3805. formData: undefined,
  3806. json: undefined,
  3807. text
  3808. }
  3809. };
  3810. } else if (opts.formEncType === "application/json") {
  3811. // json only supports POST/PUT/PATCH/DELETE submissions
  3812. if (!isMutationMethod(formMethod)) {
  3813. return getInvalidBodyError();
  3814. }
  3815. try {
  3816. let json = typeof opts.body === "string" ? JSON.parse(opts.body) : opts.body;
  3817. return {
  3818. path,
  3819. submission: {
  3820. formMethod,
  3821. formAction,
  3822. formEncType: opts.formEncType,
  3823. formData: undefined,
  3824. json,
  3825. text: undefined
  3826. }
  3827. };
  3828. } catch (e) {
  3829. return getInvalidBodyError();
  3830. }
  3831. }
  3832. }
  3833. invariant(typeof FormData === "function", "FormData is not available in this environment");
  3834. let searchParams;
  3835. let formData;
  3836. if (opts.formData) {
  3837. searchParams = convertFormDataToSearchParams(opts.formData);
  3838. formData = opts.formData;
  3839. } else if (opts.body instanceof FormData) {
  3840. searchParams = convertFormDataToSearchParams(opts.body);
  3841. formData = opts.body;
  3842. } else if (opts.body instanceof URLSearchParams) {
  3843. searchParams = opts.body;
  3844. formData = convertSearchParamsToFormData(searchParams);
  3845. } else if (opts.body == null) {
  3846. searchParams = new URLSearchParams();
  3847. formData = new FormData();
  3848. } else {
  3849. try {
  3850. searchParams = new URLSearchParams(opts.body);
  3851. formData = convertSearchParamsToFormData(searchParams);
  3852. } catch (e) {
  3853. return getInvalidBodyError();
  3854. }
  3855. }
  3856. let submission = {
  3857. formMethod,
  3858. formAction,
  3859. formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
  3860. formData,
  3861. json: undefined,
  3862. text: undefined
  3863. };
  3864. if (isMutationMethod(submission.formMethod)) {
  3865. return {
  3866. path,
  3867. submission
  3868. };
  3869. }
  3870. // Flatten submission onto URLSearchParams for GET submissions
  3871. let parsedPath = parsePath(path);
  3872. // On GET navigation submissions we can drop the ?index param from the
  3873. // resulting location since all loaders will run. But fetcher GET submissions
  3874. // only run a single loader so we need to preserve any incoming ?index params
  3875. if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
  3876. searchParams.append("index", "");
  3877. }
  3878. parsedPath.search = "?" + searchParams;
  3879. return {
  3880. path: createPath(parsedPath),
  3881. submission
  3882. };
  3883. }
  3884. // Filter out all routes at/below any caught error as they aren't going to
  3885. // render so we don't need to load them
  3886. function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
  3887. if (includeBoundary === void 0) {
  3888. includeBoundary = false;
  3889. }
  3890. let index = matches.findIndex(m => m.route.id === boundaryId);
  3891. if (index >= 0) {
  3892. return matches.slice(0, includeBoundary ? index + 1 : index);
  3893. }
  3894. return matches;
  3895. }
  3896. function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
  3897. let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
  3898. let currentUrl = history.createURL(state.location);
  3899. let nextUrl = history.createURL(location);
  3900. // Pick navigation matches that are net-new or qualify for revalidation
  3901. let boundaryMatches = matches;
  3902. if (initialHydration && state.errors) {
  3903. // On initial hydration, only consider matches up to _and including_ the boundary.
  3904. // This is inclusive to handle cases where a server loader ran successfully,
  3905. // a child server loader bubbled up to this route, but this route has
  3906. // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
  3907. // we have a complete version of `loaderData`
  3908. boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
  3909. } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
  3910. // If an action threw an error, we call loaders up to, but not including the
  3911. // boundary
  3912. boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
  3913. }
  3914. // Don't revalidate loaders by default after action 4xx/5xx responses
  3915. // when the flag is enabled. They can still opt-into revalidation via
  3916. // `shouldRevalidate` via `actionResult`
  3917. let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
  3918. let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
  3919. let navigationMatches = boundaryMatches.filter((match, index) => {
  3920. let {
  3921. route
  3922. } = match;
  3923. if (route.lazy) {
  3924. // We haven't loaded this route yet so we don't know if it's got a loader!
  3925. return true;
  3926. }
  3927. if (route.loader == null) {
  3928. return false;
  3929. }
  3930. if (initialHydration) {
  3931. return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
  3932. }
  3933. // Always call the loader on new route instances and pending defer cancellations
  3934. if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
  3935. return true;
  3936. }
  3937. // This is the default implementation for when we revalidate. If the route
  3938. // provides it's own implementation, then we give them full control but
  3939. // provide this value so they can leverage it if needed after they check
  3940. // their own specific use cases
  3941. let currentRouteMatch = state.matches[index];
  3942. let nextRouteMatch = match;
  3943. return shouldRevalidateLoader(match, _extends({
  3944. currentUrl,
  3945. currentParams: currentRouteMatch.params,
  3946. nextUrl,
  3947. nextParams: nextRouteMatch.params
  3948. }, submission, {
  3949. actionResult,
  3950. actionStatus,
  3951. defaultShouldRevalidate: shouldSkipRevalidation ? false :
  3952. // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
  3953. isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
  3954. // Search params affect all loaders
  3955. currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
  3956. }));
  3957. });
  3958. // Pick fetcher.loads that need to be revalidated
  3959. let revalidatingFetchers = [];
  3960. fetchLoadMatches.forEach((f, key) => {
  3961. // Don't revalidate:
  3962. // - on initial hydration (shouldn't be any fetchers then anyway)
  3963. // - if fetcher won't be present in the subsequent render
  3964. // - no longer matches the URL (v7_fetcherPersist=false)
  3965. // - was unmounted but persisted due to v7_fetcherPersist=true
  3966. if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
  3967. return;
  3968. }
  3969. let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
  3970. // If the fetcher path no longer matches, push it in with null matches so
  3971. // we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
  3972. // currently only a use-case for Remix HMR where the route tree can change
  3973. // at runtime and remove a route previously loaded via a fetcher
  3974. if (!fetcherMatches) {
  3975. revalidatingFetchers.push({
  3976. key,
  3977. routeId: f.routeId,
  3978. path: f.path,
  3979. matches: null,
  3980. match: null,
  3981. controller: null
  3982. });
  3983. return;
  3984. }
  3985. // Revalidating fetchers are decoupled from the route matches since they
  3986. // load from a static href. They revalidate based on explicit revalidation
  3987. // (submission, useRevalidator, or X-Remix-Revalidate)
  3988. let fetcher = state.fetchers.get(key);
  3989. let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
  3990. let shouldRevalidate = false;
  3991. if (fetchRedirectIds.has(key)) {
  3992. // Never trigger a revalidation of an actively redirecting fetcher
  3993. shouldRevalidate = false;
  3994. } else if (cancelledFetcherLoads.has(key)) {
  3995. // Always mark for revalidation if the fetcher was cancelled
  3996. cancelledFetcherLoads.delete(key);
  3997. shouldRevalidate = true;
  3998. } else if (fetcher && fetcher.state !== "idle" && fetcher.data === undefined) {
  3999. // If the fetcher hasn't ever completed loading yet, then this isn't a
  4000. // revalidation, it would just be a brand new load if an explicit
  4001. // revalidation is required
  4002. shouldRevalidate = isRevalidationRequired;
  4003. } else {
  4004. // Otherwise fall back on any user-defined shouldRevalidate, defaulting
  4005. // to explicit revalidations only
  4006. shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
  4007. currentUrl,
  4008. currentParams: state.matches[state.matches.length - 1].params,
  4009. nextUrl,
  4010. nextParams: matches[matches.length - 1].params
  4011. }, submission, {
  4012. actionResult,
  4013. actionStatus,
  4014. defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
  4015. }));
  4016. }
  4017. if (shouldRevalidate) {
  4018. revalidatingFetchers.push({
  4019. key,
  4020. routeId: f.routeId,
  4021. path: f.path,
  4022. matches: fetcherMatches,
  4023. match: fetcherMatch,
  4024. controller: new AbortController()
  4025. });
  4026. }
  4027. });
  4028. return [navigationMatches, revalidatingFetchers];
  4029. }
  4030. function shouldLoadRouteOnHydration(route, loaderData, errors) {
  4031. // We dunno if we have a loader - gotta find out!
  4032. if (route.lazy) {
  4033. return true;
  4034. }
  4035. // No loader, nothing to initialize
  4036. if (!route.loader) {
  4037. return false;
  4038. }
  4039. let hasData = loaderData != null && loaderData[route.id] !== undefined;
  4040. let hasError = errors != null && errors[route.id] !== undefined;
  4041. // Don't run if we error'd during SSR
  4042. if (!hasData && hasError) {
  4043. return false;
  4044. }
  4045. // Explicitly opting-in to running on hydration
  4046. if (typeof route.loader === "function" && route.loader.hydrate === true) {
  4047. return true;
  4048. }
  4049. // Otherwise, run if we're not yet initialized with anything
  4050. return !hasData && !hasError;
  4051. }
  4052. function isNewLoader(currentLoaderData, currentMatch, match) {
  4053. let isNew =
  4054. // [a] -> [a, b]
  4055. !currentMatch ||
  4056. // [a, b] -> [a, c]
  4057. match.route.id !== currentMatch.route.id;
  4058. // Handle the case that we don't have data for a re-used route, potentially
  4059. // from a prior error or from a cancelled pending deferred
  4060. let isMissingData = currentLoaderData[match.route.id] === undefined;
  4061. // Always load if this is a net-new route or we don't yet have data
  4062. return isNew || isMissingData;
  4063. }
  4064. function isNewRouteInstance(currentMatch, match) {
  4065. let currentPath = currentMatch.route.path;
  4066. return (
  4067. // param change for this match, /users/123 -> /users/456
  4068. currentMatch.pathname !== match.pathname ||
  4069. // splat param changed, which is not present in match.path
  4070. // e.g. /files/images/avatar.jpg -> files/finances.xls
  4071. currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
  4072. );
  4073. }
  4074. function shouldRevalidateLoader(loaderMatch, arg) {
  4075. if (loaderMatch.route.shouldRevalidate) {
  4076. let routeChoice = loaderMatch.route.shouldRevalidate(arg);
  4077. if (typeof routeChoice === "boolean") {
  4078. return routeChoice;
  4079. }
  4080. }
  4081. return arg.defaultShouldRevalidate;
  4082. }
  4083. function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
  4084. var _childrenToPatch;
  4085. let childrenToPatch;
  4086. if (routeId) {
  4087. let route = manifest[routeId];
  4088. invariant(route, "No route found to patch children into: routeId = " + routeId);
  4089. if (!route.children) {
  4090. route.children = [];
  4091. }
  4092. childrenToPatch = route.children;
  4093. } else {
  4094. childrenToPatch = routesToUse;
  4095. }
  4096. // Don't patch in routes we already know about so that `patch` is idempotent
  4097. // to simplify user-land code. This is useful because we re-call the
  4098. // `patchRoutesOnNavigation` function for matched routes with params.
  4099. let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
  4100. let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
  4101. childrenToPatch.push(...newRoutes);
  4102. }
  4103. function isSameRoute(newRoute, existingRoute) {
  4104. // Most optimal check is by id
  4105. if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
  4106. return true;
  4107. }
  4108. // Second is by pathing differences
  4109. if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
  4110. return false;
  4111. }
  4112. // Pathless layout routes are trickier since we need to check children.
  4113. // If they have no children then they're the same as far as we can tell
  4114. if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
  4115. return true;
  4116. }
  4117. // Otherwise, we look to see if every child in the new route is already
  4118. // represented in the existing route's children
  4119. return newRoute.children.every((aChild, i) => {
  4120. var _existingRoute$childr;
  4121. return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
  4122. });
  4123. }
  4124. /**
  4125. * Execute route.lazy() methods to lazily load route modules (loader, action,
  4126. * shouldRevalidate) and update the routeManifest in place which shares objects
  4127. * with dataRoutes so those get updated as well.
  4128. */
  4129. async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
  4130. if (!route.lazy) {
  4131. return;
  4132. }
  4133. let lazyRoute = await route.lazy();
  4134. // If the lazy route function was executed and removed by another parallel
  4135. // call then we can return - first lazy() to finish wins because the return
  4136. // value of lazy is expected to be static
  4137. if (!route.lazy) {
  4138. return;
  4139. }
  4140. let routeToUpdate = manifest[route.id];
  4141. invariant(routeToUpdate, "No route found in manifest");
  4142. // Update the route in place. This should be safe because there's no way
  4143. // we could yet be sitting on this route as we can't get there without
  4144. // resolving lazy() first.
  4145. //
  4146. // This is different than the HMR "update" use-case where we may actively be
  4147. // on the route being updated. The main concern boils down to "does this
  4148. // mutation affect any ongoing navigations or any current state.matches
  4149. // values?". If not, it should be safe to update in place.
  4150. let routeUpdates = {};
  4151. for (let lazyRouteProperty in lazyRoute) {
  4152. let staticRouteValue = routeToUpdate[lazyRouteProperty];
  4153. let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
  4154. // This property isn't static since it should always be updated based
  4155. // on the route updates
  4156. lazyRouteProperty !== "hasErrorBoundary";
  4157. warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
  4158. if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
  4159. routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
  4160. }
  4161. }
  4162. // Mutate the route with the provided updates. Do this first so we pass
  4163. // the updated version to mapRouteProperties
  4164. Object.assign(routeToUpdate, routeUpdates);
  4165. // Mutate the `hasErrorBoundary` property on the route based on the route
  4166. // updates and remove the `lazy` function so we don't resolve the lazy
  4167. // route again.
  4168. Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
  4169. lazy: undefined
  4170. }));
  4171. }
  4172. // Default implementation of `dataStrategy` which fetches all loaders in parallel
  4173. async function defaultDataStrategy(_ref4) {
  4174. let {
  4175. matches
  4176. } = _ref4;
  4177. let matchesToLoad = matches.filter(m => m.shouldLoad);
  4178. let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
  4179. return results.reduce((acc, result, i) => Object.assign(acc, {
  4180. [matchesToLoad[i].route.id]: result
  4181. }), {});
  4182. }
  4183. async function callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties, requestContext) {
  4184. let loadRouteDefinitionsPromises = matches.map(m => m.route.lazy ? loadLazyRouteModule(m.route, mapRouteProperties, manifest) : undefined);
  4185. let dsMatches = matches.map((match, i) => {
  4186. let loadRoutePromise = loadRouteDefinitionsPromises[i];
  4187. let shouldLoad = matchesToLoad.some(m => m.route.id === match.route.id);
  4188. // `resolve` encapsulates route.lazy(), executing the loader/action,
  4189. // and mapping return values/thrown errors to a `DataStrategyResult`. Users
  4190. // can pass a callback to take fine-grained control over the execution
  4191. // of the loader/action
  4192. let resolve = async handlerOverride => {
  4193. if (handlerOverride && request.method === "GET" && (match.route.lazy || match.route.loader)) {
  4194. shouldLoad = true;
  4195. }
  4196. return shouldLoad ? callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, requestContext) : Promise.resolve({
  4197. type: ResultType.data,
  4198. result: undefined
  4199. });
  4200. };
  4201. return _extends({}, match, {
  4202. shouldLoad,
  4203. resolve
  4204. });
  4205. });
  4206. // Send all matches here to allow for a middleware-type implementation.
  4207. // handler will be a no-op for unneeded routes and we filter those results
  4208. // back out below.
  4209. let results = await dataStrategyImpl({
  4210. matches: dsMatches,
  4211. request,
  4212. params: matches[0].params,
  4213. fetcherKey,
  4214. context: requestContext
  4215. });
  4216. // Wait for all routes to load here but 'swallow the error since we want
  4217. // it to bubble up from the `await loadRoutePromise` in `callLoaderOrAction` -
  4218. // called from `match.resolve()`
  4219. try {
  4220. await Promise.all(loadRouteDefinitionsPromises);
  4221. } catch (e) {
  4222. // No-op
  4223. }
  4224. return results;
  4225. }
  4226. // Default logic for calling a loader/action is the user has no specified a dataStrategy
  4227. async function callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, staticContext) {
  4228. let result;
  4229. let onReject;
  4230. let runHandler = handler => {
  4231. // Setup a promise we can race against so that abort signals short circuit
  4232. let reject;
  4233. // This will never resolve so safe to type it as Promise<DataStrategyResult> to
  4234. // satisfy the function return value
  4235. let abortPromise = new Promise((_, r) => reject = r);
  4236. onReject = () => reject();
  4237. request.signal.addEventListener("abort", onReject);
  4238. let actualHandler = ctx => {
  4239. if (typeof handler !== "function") {
  4240. return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
  4241. }
  4242. return handler({
  4243. request,
  4244. params: match.params,
  4245. context: staticContext
  4246. }, ...(ctx !== undefined ? [ctx] : []));
  4247. };
  4248. let handlerPromise = (async () => {
  4249. try {
  4250. let val = await (handlerOverride ? handlerOverride(ctx => actualHandler(ctx)) : actualHandler());
  4251. return {
  4252. type: "data",
  4253. result: val
  4254. };
  4255. } catch (e) {
  4256. return {
  4257. type: "error",
  4258. result: e
  4259. };
  4260. }
  4261. })();
  4262. return Promise.race([handlerPromise, abortPromise]);
  4263. };
  4264. try {
  4265. let handler = match.route[type];
  4266. // If we have a route.lazy promise, await that first
  4267. if (loadRoutePromise) {
  4268. if (handler) {
  4269. // Run statically defined handler in parallel with lazy()
  4270. let handlerError;
  4271. let [value] = await Promise.all([
  4272. // If the handler throws, don't let it immediately bubble out,
  4273. // since we need to let the lazy() execution finish so we know if this
  4274. // route has a boundary that can handle the error
  4275. runHandler(handler).catch(e => {
  4276. handlerError = e;
  4277. }), loadRoutePromise]);
  4278. if (handlerError !== undefined) {
  4279. throw handlerError;
  4280. }
  4281. result = value;
  4282. } else {
  4283. // Load lazy route module, then run any returned handler
  4284. await loadRoutePromise;
  4285. handler = match.route[type];
  4286. if (handler) {
  4287. // Handler still runs even if we got interrupted to maintain consistency
  4288. // with un-abortable behavior of handler execution on non-lazy or
  4289. // previously-lazy-loaded routes
  4290. result = await runHandler(handler);
  4291. } else if (type === "action") {
  4292. let url = new URL(request.url);
  4293. let pathname = url.pathname + url.search;
  4294. throw getInternalRouterError(405, {
  4295. method: request.method,
  4296. pathname,
  4297. routeId: match.route.id
  4298. });
  4299. } else {
  4300. // lazy() route has no loader to run. Short circuit here so we don't
  4301. // hit the invariant below that errors on returning undefined.
  4302. return {
  4303. type: ResultType.data,
  4304. result: undefined
  4305. };
  4306. }
  4307. }
  4308. } else if (!handler) {
  4309. let url = new URL(request.url);
  4310. let pathname = url.pathname + url.search;
  4311. throw getInternalRouterError(404, {
  4312. pathname
  4313. });
  4314. } else {
  4315. result = await runHandler(handler);
  4316. }
  4317. invariant(result.result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
  4318. } catch (e) {
  4319. // We should already be catching and converting normal handler executions to
  4320. // DataStrategyResults and returning them, so anything that throws here is an
  4321. // unexpected error we still need to wrap
  4322. return {
  4323. type: ResultType.error,
  4324. result: e
  4325. };
  4326. } finally {
  4327. if (onReject) {
  4328. request.signal.removeEventListener("abort", onReject);
  4329. }
  4330. }
  4331. return result;
  4332. }
  4333. async function convertDataStrategyResultToDataResult(dataStrategyResult) {
  4334. let {
  4335. result,
  4336. type
  4337. } = dataStrategyResult;
  4338. if (isResponse(result)) {
  4339. let data;
  4340. try {
  4341. let contentType = result.headers.get("Content-Type");
  4342. // Check between word boundaries instead of startsWith() due to the last
  4343. // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
  4344. if (contentType && /\bapplication\/json\b/.test(contentType)) {
  4345. if (result.body == null) {
  4346. data = null;
  4347. } else {
  4348. data = await result.json();
  4349. }
  4350. } else {
  4351. data = await result.text();
  4352. }
  4353. } catch (e) {
  4354. return {
  4355. type: ResultType.error,
  4356. error: e
  4357. };
  4358. }
  4359. if (type === ResultType.error) {
  4360. return {
  4361. type: ResultType.error,
  4362. error: new ErrorResponseImpl(result.status, result.statusText, data),
  4363. statusCode: result.status,
  4364. headers: result.headers
  4365. };
  4366. }
  4367. return {
  4368. type: ResultType.data,
  4369. data,
  4370. statusCode: result.status,
  4371. headers: result.headers
  4372. };
  4373. }
  4374. if (type === ResultType.error) {
  4375. if (isDataWithResponseInit(result)) {
  4376. var _result$init3, _result$init4;
  4377. if (result.data instanceof Error) {
  4378. var _result$init, _result$init2;
  4379. return {
  4380. type: ResultType.error,
  4381. error: result.data,
  4382. statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status,
  4383. headers: (_result$init2 = result.init) != null && _result$init2.headers ? new Headers(result.init.headers) : undefined
  4384. };
  4385. }
  4386. // Convert thrown data() to ErrorResponse instances
  4387. return {
  4388. type: ResultType.error,
  4389. error: new ErrorResponseImpl(((_result$init3 = result.init) == null ? void 0 : _result$init3.status) || 500, undefined, result.data),
  4390. statusCode: isRouteErrorResponse(result) ? result.status : undefined,
  4391. headers: (_result$init4 = result.init) != null && _result$init4.headers ? new Headers(result.init.headers) : undefined
  4392. };
  4393. }
  4394. return {
  4395. type: ResultType.error,
  4396. error: result,
  4397. statusCode: isRouteErrorResponse(result) ? result.status : undefined
  4398. };
  4399. }
  4400. if (isDeferredData(result)) {
  4401. var _result$init5, _result$init6;
  4402. return {
  4403. type: ResultType.deferred,
  4404. deferredData: result,
  4405. statusCode: (_result$init5 = result.init) == null ? void 0 : _result$init5.status,
  4406. headers: ((_result$init6 = result.init) == null ? void 0 : _result$init6.headers) && new Headers(result.init.headers)
  4407. };
  4408. }
  4409. if (isDataWithResponseInit(result)) {
  4410. var _result$init7, _result$init8;
  4411. return {
  4412. type: ResultType.data,
  4413. data: result.data,
  4414. statusCode: (_result$init7 = result.init) == null ? void 0 : _result$init7.status,
  4415. headers: (_result$init8 = result.init) != null && _result$init8.headers ? new Headers(result.init.headers) : undefined
  4416. };
  4417. }
  4418. return {
  4419. type: ResultType.data,
  4420. data: result
  4421. };
  4422. }
  4423. // Support relative routing in internal redirects
  4424. function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
  4425. let location = response.headers.get("Location");
  4426. invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
  4427. if (!ABSOLUTE_URL_REGEX.test(location)) {
  4428. let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
  4429. location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
  4430. response.headers.set("Location", location);
  4431. }
  4432. return response;
  4433. }
  4434. function normalizeRedirectLocation(location, currentUrl, basename, historyInstance) {
  4435. // Match Chrome's behavior:
  4436. // https://github.com/chromium/chromium/blob/216dbeb61db0c667e62082e5f5400a32d6983df3/content/public/common/url_utils.cc#L82
  4437. let invalidProtocols = ["about:", "blob:", "chrome:", "chrome-untrusted:", "content:", "data:", "devtools:", "file:", "filesystem:",
  4438. // eslint-disable-next-line no-script-url
  4439. "javascript:"];
  4440. if (ABSOLUTE_URL_REGEX.test(location)) {
  4441. // Strip off the protocol+origin for same-origin + same-basename absolute redirects
  4442. let normalizedLocation = location;
  4443. let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
  4444. if (invalidProtocols.includes(url.protocol)) {
  4445. throw new Error("Invalid redirect location");
  4446. }
  4447. let isSameBasename = stripBasename(url.pathname, basename) != null;
  4448. if (url.origin === currentUrl.origin && isSameBasename) {
  4449. return url.pathname + url.search + url.hash;
  4450. }
  4451. }
  4452. try {
  4453. let url = historyInstance.createURL(location);
  4454. if (invalidProtocols.includes(url.protocol)) {
  4455. throw new Error("Invalid redirect location");
  4456. }
  4457. } catch (e) {}
  4458. return location;
  4459. }
  4460. // Utility method for creating the Request instances for loaders/actions during
  4461. // client-side navigations and fetches. During SSR we will always have a
  4462. // Request instance from the static handler (query/queryRoute)
  4463. function createClientSideRequest(history, location, signal, submission) {
  4464. let url = history.createURL(stripHashFromPath(location)).toString();
  4465. let init = {
  4466. signal
  4467. };
  4468. if (submission && isMutationMethod(submission.formMethod)) {
  4469. let {
  4470. formMethod,
  4471. formEncType
  4472. } = submission;
  4473. // Didn't think we needed this but it turns out unlike other methods, patch
  4474. // won't be properly normalized to uppercase and results in a 405 error.
  4475. // See: https://fetch.spec.whatwg.org/#concept-method
  4476. init.method = formMethod.toUpperCase();
  4477. if (formEncType === "application/json") {
  4478. init.headers = new Headers({
  4479. "Content-Type": formEncType
  4480. });
  4481. init.body = JSON.stringify(submission.json);
  4482. } else if (formEncType === "text/plain") {
  4483. // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
  4484. init.body = submission.text;
  4485. } else if (formEncType === "application/x-www-form-urlencoded" && submission.formData) {
  4486. // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
  4487. init.body = convertFormDataToSearchParams(submission.formData);
  4488. } else {
  4489. // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
  4490. init.body = submission.formData;
  4491. }
  4492. }
  4493. return new Request(url, init);
  4494. }
  4495. function convertFormDataToSearchParams(formData) {
  4496. let searchParams = new URLSearchParams();
  4497. for (let [key, value] of formData.entries()) {
  4498. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
  4499. searchParams.append(key, typeof value === "string" ? value : value.name);
  4500. }
  4501. return searchParams;
  4502. }
  4503. function convertSearchParamsToFormData(searchParams) {
  4504. let formData = new FormData();
  4505. for (let [key, value] of searchParams.entries()) {
  4506. formData.append(key, value);
  4507. }
  4508. return formData;
  4509. }
  4510. function processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
  4511. // Fill in loaderData/errors from our loaders
  4512. let loaderData = {};
  4513. let errors = null;
  4514. let statusCode;
  4515. let foundError = false;
  4516. let loaderHeaders = {};
  4517. let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
  4518. // Process loader results into state.loaderData/state.errors
  4519. matches.forEach(match => {
  4520. if (!(match.route.id in results)) {
  4521. return;
  4522. }
  4523. let id = match.route.id;
  4524. let result = results[id];
  4525. invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
  4526. if (isErrorResult(result)) {
  4527. let error = result.error;
  4528. // If we have a pending action error, we report it at the highest-route
  4529. // that throws a loader error, and then clear it out to indicate that
  4530. // it was consumed
  4531. if (pendingError !== undefined) {
  4532. error = pendingError;
  4533. pendingError = undefined;
  4534. }
  4535. errors = errors || {};
  4536. if (skipLoaderErrorBubbling) {
  4537. errors[id] = error;
  4538. } else {
  4539. // Look upwards from the matched route for the closest ancestor error
  4540. // boundary, defaulting to the root match. Prefer higher error values
  4541. // if lower errors bubble to the same boundary
  4542. let boundaryMatch = findNearestBoundary(matches, id);
  4543. if (errors[boundaryMatch.route.id] == null) {
  4544. errors[boundaryMatch.route.id] = error;
  4545. }
  4546. }
  4547. // Clear our any prior loaderData for the throwing route
  4548. loaderData[id] = undefined;
  4549. // Once we find our first (highest) error, we set the status code and
  4550. // prevent deeper status codes from overriding
  4551. if (!foundError) {
  4552. foundError = true;
  4553. statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
  4554. }
  4555. if (result.headers) {
  4556. loaderHeaders[id] = result.headers;
  4557. }
  4558. } else {
  4559. if (isDeferredResult(result)) {
  4560. activeDeferreds.set(id, result.deferredData);
  4561. loaderData[id] = result.deferredData.data;
  4562. // Error status codes always override success status codes, but if all
  4563. // loaders are successful we take the deepest status code.
  4564. if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
  4565. statusCode = result.statusCode;
  4566. }
  4567. if (result.headers) {
  4568. loaderHeaders[id] = result.headers;
  4569. }
  4570. } else {
  4571. loaderData[id] = result.data;
  4572. // Error status codes always override success status codes, but if all
  4573. // loaders are successful we take the deepest status code.
  4574. if (result.statusCode && result.statusCode !== 200 && !foundError) {
  4575. statusCode = result.statusCode;
  4576. }
  4577. if (result.headers) {
  4578. loaderHeaders[id] = result.headers;
  4579. }
  4580. }
  4581. }
  4582. });
  4583. // If we didn't consume the pending action error (i.e., all loaders
  4584. // resolved), then consume it here. Also clear out any loaderData for the
  4585. // throwing route
  4586. if (pendingError !== undefined && pendingActionResult) {
  4587. errors = {
  4588. [pendingActionResult[0]]: pendingError
  4589. };
  4590. loaderData[pendingActionResult[0]] = undefined;
  4591. }
  4592. return {
  4593. loaderData,
  4594. errors,
  4595. statusCode: statusCode || 200,
  4596. loaderHeaders
  4597. };
  4598. }
  4599. function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
  4600. let {
  4601. loaderData,
  4602. errors
  4603. } = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
  4604. );
  4605. // Process results from our revalidating fetchers
  4606. revalidatingFetchers.forEach(rf => {
  4607. let {
  4608. key,
  4609. match,
  4610. controller
  4611. } = rf;
  4612. let result = fetcherResults[key];
  4613. invariant(result, "Did not find corresponding fetcher result");
  4614. // Process fetcher non-redirect errors
  4615. if (controller && controller.signal.aborted) {
  4616. // Nothing to do for aborted fetchers
  4617. return;
  4618. } else if (isErrorResult(result)) {
  4619. let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
  4620. if (!(errors && errors[boundaryMatch.route.id])) {
  4621. errors = _extends({}, errors, {
  4622. [boundaryMatch.route.id]: result.error
  4623. });
  4624. }
  4625. state.fetchers.delete(key);
  4626. } else if (isRedirectResult(result)) {
  4627. // Should never get here, redirects should get processed above, but we
  4628. // keep this to type narrow to a success result in the else
  4629. invariant(false, "Unhandled fetcher revalidation redirect");
  4630. } else if (isDeferredResult(result)) {
  4631. // Should never get here, deferred data should be awaited for fetchers
  4632. // in resolveDeferredResults
  4633. invariant(false, "Unhandled fetcher deferred data");
  4634. } else {
  4635. let doneFetcher = getDoneFetcher(result.data);
  4636. state.fetchers.set(key, doneFetcher);
  4637. }
  4638. });
  4639. return {
  4640. loaderData,
  4641. errors
  4642. };
  4643. }
  4644. function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
  4645. let mergedLoaderData = _extends({}, newLoaderData);
  4646. for (let match of matches) {
  4647. let id = match.route.id;
  4648. if (newLoaderData.hasOwnProperty(id)) {
  4649. if (newLoaderData[id] !== undefined) {
  4650. mergedLoaderData[id] = newLoaderData[id];
  4651. }
  4652. } else if (loaderData[id] !== undefined && match.route.loader) {
  4653. // Preserve existing keys not included in newLoaderData and where a loader
  4654. // wasn't removed by HMR
  4655. mergedLoaderData[id] = loaderData[id];
  4656. }
  4657. if (errors && errors.hasOwnProperty(id)) {
  4658. // Don't keep any loader data below the boundary
  4659. break;
  4660. }
  4661. }
  4662. return mergedLoaderData;
  4663. }
  4664. function getActionDataForCommit(pendingActionResult) {
  4665. if (!pendingActionResult) {
  4666. return {};
  4667. }
  4668. return isErrorResult(pendingActionResult[1]) ? {
  4669. // Clear out prior actionData on errors
  4670. actionData: {}
  4671. } : {
  4672. actionData: {
  4673. [pendingActionResult[0]]: pendingActionResult[1].data
  4674. }
  4675. };
  4676. }
  4677. // Find the nearest error boundary, looking upwards from the leaf route (or the
  4678. // route specified by routeId) for the closest ancestor error boundary,
  4679. // defaulting to the root match
  4680. function findNearestBoundary(matches, routeId) {
  4681. let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
  4682. return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
  4683. }
  4684. function getShortCircuitMatches(routes) {
  4685. // Prefer a root layout route if present, otherwise shim in a route object
  4686. let route = routes.length === 1 ? routes[0] : routes.find(r => r.index || !r.path || r.path === "/") || {
  4687. id: "__shim-error-route__"
  4688. };
  4689. return {
  4690. matches: [{
  4691. params: {},
  4692. pathname: "",
  4693. pathnameBase: "",
  4694. route
  4695. }],
  4696. route
  4697. };
  4698. }
  4699. function getInternalRouterError(status, _temp5) {
  4700. let {
  4701. pathname,
  4702. routeId,
  4703. method,
  4704. type,
  4705. message
  4706. } = _temp5 === void 0 ? {} : _temp5;
  4707. let statusText = "Unknown Server Error";
  4708. let errorMessage = "Unknown @remix-run/router error";
  4709. if (status === 400) {
  4710. statusText = "Bad Request";
  4711. if (method && pathname && routeId) {
  4712. errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
  4713. } else if (type === "defer-action") {
  4714. errorMessage = "defer() is not supported in actions";
  4715. } else if (type === "invalid-body") {
  4716. errorMessage = "Unable to encode submission body";
  4717. }
  4718. } else if (status === 403) {
  4719. statusText = "Forbidden";
  4720. errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
  4721. } else if (status === 404) {
  4722. statusText = "Not Found";
  4723. errorMessage = "No route matches URL \"" + pathname + "\"";
  4724. } else if (status === 405) {
  4725. statusText = "Method Not Allowed";
  4726. if (method && pathname && routeId) {
  4727. errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
  4728. } else if (method) {
  4729. errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
  4730. }
  4731. }
  4732. return new ErrorResponseImpl(status || 500, statusText, new Error(errorMessage), true);
  4733. }
  4734. // Find any returned redirect errors, starting from the lowest match
  4735. function findRedirect(results) {
  4736. let entries = Object.entries(results);
  4737. for (let i = entries.length - 1; i >= 0; i--) {
  4738. let [key, result] = entries[i];
  4739. if (isRedirectResult(result)) {
  4740. return {
  4741. key,
  4742. result
  4743. };
  4744. }
  4745. }
  4746. }
  4747. function stripHashFromPath(path) {
  4748. let parsedPath = typeof path === "string" ? parsePath(path) : path;
  4749. return createPath(_extends({}, parsedPath, {
  4750. hash: ""
  4751. }));
  4752. }
  4753. function isHashChangeOnly(a, b) {
  4754. if (a.pathname !== b.pathname || a.search !== b.search) {
  4755. return false;
  4756. }
  4757. if (a.hash === "") {
  4758. // /page -> /page#hash
  4759. return b.hash !== "";
  4760. } else if (a.hash === b.hash) {
  4761. // /page#hash -> /page#hash
  4762. return true;
  4763. } else if (b.hash !== "") {
  4764. // /page#hash -> /page#other
  4765. return true;
  4766. }
  4767. // If the hash is removed the browser will re-perform a request to the server
  4768. // /page#hash -> /page
  4769. return false;
  4770. }
  4771. function isDataStrategyResult(result) {
  4772. return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
  4773. }
  4774. function isRedirectDataStrategyResultResult(result) {
  4775. return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
  4776. }
  4777. function isDeferredResult(result) {
  4778. return result.type === ResultType.deferred;
  4779. }
  4780. function isErrorResult(result) {
  4781. return result.type === ResultType.error;
  4782. }
  4783. function isRedirectResult(result) {
  4784. return (result && result.type) === ResultType.redirect;
  4785. }
  4786. function isDataWithResponseInit(value) {
  4787. return typeof value === "object" && value != null && "type" in value && "data" in value && "init" in value && value.type === "DataWithResponseInit";
  4788. }
  4789. function isDeferredData(value) {
  4790. let deferred = value;
  4791. return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
  4792. }
  4793. function isResponse(value) {
  4794. return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
  4795. }
  4796. function isRedirectResponse(result) {
  4797. if (!isResponse(result)) {
  4798. return false;
  4799. }
  4800. let status = result.status;
  4801. let location = result.headers.get("Location");
  4802. return status >= 300 && status <= 399 && location != null;
  4803. }
  4804. function isValidMethod(method) {
  4805. return validRequestMethods.has(method.toLowerCase());
  4806. }
  4807. function isMutationMethod(method) {
  4808. return validMutationMethods.has(method.toLowerCase());
  4809. }
  4810. async function resolveNavigationDeferredResults(matches, results, signal, currentMatches, currentLoaderData) {
  4811. let entries = Object.entries(results);
  4812. for (let index = 0; index < entries.length; index++) {
  4813. let [routeId, result] = entries[index];
  4814. let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
  4815. // If we don't have a match, then we can have a deferred result to do
  4816. // anything with. This is for revalidating fetchers where the route was
  4817. // removed during HMR
  4818. if (!match) {
  4819. continue;
  4820. }
  4821. let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
  4822. let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
  4823. if (isDeferredResult(result) && isRevalidatingLoader) {
  4824. // Note: we do not have to touch activeDeferreds here since we race them
  4825. // against the signal in resolveDeferredData and they'll get aborted
  4826. // there if needed
  4827. await resolveDeferredData(result, signal, false).then(result => {
  4828. if (result) {
  4829. results[routeId] = result;
  4830. }
  4831. });
  4832. }
  4833. }
  4834. }
  4835. async function resolveFetcherDeferredResults(matches, results, revalidatingFetchers) {
  4836. for (let index = 0; index < revalidatingFetchers.length; index++) {
  4837. let {
  4838. key,
  4839. routeId,
  4840. controller
  4841. } = revalidatingFetchers[index];
  4842. let result = results[key];
  4843. let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
  4844. // If we don't have a match, then we can have a deferred result to do
  4845. // anything with. This is for revalidating fetchers where the route was
  4846. // removed during HMR
  4847. if (!match) {
  4848. continue;
  4849. }
  4850. if (isDeferredResult(result)) {
  4851. // Note: we do not have to touch activeDeferreds here since we race them
  4852. // against the signal in resolveDeferredData and they'll get aborted
  4853. // there if needed
  4854. invariant(controller, "Expected an AbortController for revalidating fetcher deferred result");
  4855. await resolveDeferredData(result, controller.signal, true).then(result => {
  4856. if (result) {
  4857. results[key] = result;
  4858. }
  4859. });
  4860. }
  4861. }
  4862. }
  4863. async function resolveDeferredData(result, signal, unwrap) {
  4864. if (unwrap === void 0) {
  4865. unwrap = false;
  4866. }
  4867. let aborted = await result.deferredData.resolveData(signal);
  4868. if (aborted) {
  4869. return;
  4870. }
  4871. if (unwrap) {
  4872. try {
  4873. return {
  4874. type: ResultType.data,
  4875. data: result.deferredData.unwrappedData
  4876. };
  4877. } catch (e) {
  4878. // Handle any TrackedPromise._error values encountered while unwrapping
  4879. return {
  4880. type: ResultType.error,
  4881. error: e
  4882. };
  4883. }
  4884. }
  4885. return {
  4886. type: ResultType.data,
  4887. data: result.deferredData.data
  4888. };
  4889. }
  4890. function hasNakedIndexQuery(search) {
  4891. return new URLSearchParams(search).getAll("index").some(v => v === "");
  4892. }
  4893. function getTargetMatch(matches, location) {
  4894. let search = typeof location === "string" ? parsePath(location).search : location.search;
  4895. if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
  4896. // Return the leaf index route when index is present
  4897. return matches[matches.length - 1];
  4898. }
  4899. // Otherwise grab the deepest "path contributing" match (ignoring index and
  4900. // pathless layout routes)
  4901. let pathMatches = getPathContributingMatches(matches);
  4902. return pathMatches[pathMatches.length - 1];
  4903. }
  4904. function getSubmissionFromNavigation(navigation) {
  4905. let {
  4906. formMethod,
  4907. formAction,
  4908. formEncType,
  4909. text,
  4910. formData,
  4911. json
  4912. } = navigation;
  4913. if (!formMethod || !formAction || !formEncType) {
  4914. return;
  4915. }
  4916. if (text != null) {
  4917. return {
  4918. formMethod,
  4919. formAction,
  4920. formEncType,
  4921. formData: undefined,
  4922. json: undefined,
  4923. text
  4924. };
  4925. } else if (formData != null) {
  4926. return {
  4927. formMethod,
  4928. formAction,
  4929. formEncType,
  4930. formData,
  4931. json: undefined,
  4932. text: undefined
  4933. };
  4934. } else if (json !== undefined) {
  4935. return {
  4936. formMethod,
  4937. formAction,
  4938. formEncType,
  4939. formData: undefined,
  4940. json,
  4941. text: undefined
  4942. };
  4943. }
  4944. }
  4945. function getLoadingNavigation(location, submission) {
  4946. if (submission) {
  4947. let navigation = {
  4948. state: "loading",
  4949. location,
  4950. formMethod: submission.formMethod,
  4951. formAction: submission.formAction,
  4952. formEncType: submission.formEncType,
  4953. formData: submission.formData,
  4954. json: submission.json,
  4955. text: submission.text
  4956. };
  4957. return navigation;
  4958. } else {
  4959. let navigation = {
  4960. state: "loading",
  4961. location,
  4962. formMethod: undefined,
  4963. formAction: undefined,
  4964. formEncType: undefined,
  4965. formData: undefined,
  4966. json: undefined,
  4967. text: undefined
  4968. };
  4969. return navigation;
  4970. }
  4971. }
  4972. function getSubmittingNavigation(location, submission) {
  4973. let navigation = {
  4974. state: "submitting",
  4975. location,
  4976. formMethod: submission.formMethod,
  4977. formAction: submission.formAction,
  4978. formEncType: submission.formEncType,
  4979. formData: submission.formData,
  4980. json: submission.json,
  4981. text: submission.text
  4982. };
  4983. return navigation;
  4984. }
  4985. function getLoadingFetcher(submission, data) {
  4986. if (submission) {
  4987. let fetcher = {
  4988. state: "loading",
  4989. formMethod: submission.formMethod,
  4990. formAction: submission.formAction,
  4991. formEncType: submission.formEncType,
  4992. formData: submission.formData,
  4993. json: submission.json,
  4994. text: submission.text,
  4995. data
  4996. };
  4997. return fetcher;
  4998. } else {
  4999. let fetcher = {
  5000. state: "loading",
  5001. formMethod: undefined,
  5002. formAction: undefined,
  5003. formEncType: undefined,
  5004. formData: undefined,
  5005. json: undefined,
  5006. text: undefined,
  5007. data
  5008. };
  5009. return fetcher;
  5010. }
  5011. }
  5012. function getSubmittingFetcher(submission, existingFetcher) {
  5013. let fetcher = {
  5014. state: "submitting",
  5015. formMethod: submission.formMethod,
  5016. formAction: submission.formAction,
  5017. formEncType: submission.formEncType,
  5018. formData: submission.formData,
  5019. json: submission.json,
  5020. text: submission.text,
  5021. data: existingFetcher ? existingFetcher.data : undefined
  5022. };
  5023. return fetcher;
  5024. }
  5025. function getDoneFetcher(data) {
  5026. let fetcher = {
  5027. state: "idle",
  5028. formMethod: undefined,
  5029. formAction: undefined,
  5030. formEncType: undefined,
  5031. formData: undefined,
  5032. json: undefined,
  5033. text: undefined,
  5034. data
  5035. };
  5036. return fetcher;
  5037. }
  5038. function restoreAppliedTransitions(_window, transitions) {
  5039. try {
  5040. let sessionPositions = _window.sessionStorage.getItem(TRANSITIONS_STORAGE_KEY);
  5041. if (sessionPositions) {
  5042. let json = JSON.parse(sessionPositions);
  5043. for (let [k, v] of Object.entries(json || {})) {
  5044. if (v && Array.isArray(v)) {
  5045. transitions.set(k, new Set(v || []));
  5046. }
  5047. }
  5048. }
  5049. } catch (e) {
  5050. // no-op, use default empty object
  5051. }
  5052. }
  5053. function persistAppliedTransitions(_window, transitions) {
  5054. if (transitions.size > 0) {
  5055. let json = {};
  5056. for (let [k, v] of transitions) {
  5057. json[k] = [...v];
  5058. }
  5059. try {
  5060. _window.sessionStorage.setItem(TRANSITIONS_STORAGE_KEY, JSON.stringify(json));
  5061. } catch (error) {
  5062. warning(false, "Failed to save applied view transitions in sessionStorage (" + error + ").");
  5063. }
  5064. }
  5065. }
  5066. //#endregion
  5067. export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, decodePath as UNSAFE_decodePath, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, data, defer, generatePath, getStaticContextFromError, getToPathname, isDataWithResponseInit, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, replace, resolvePath, resolveTo, stripBasename };
  5068. //# sourceMappingURL=router.js.map