router.umd.js 212 KB

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