router.js 130 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981
  1. /**
  2. * @remix-run/router v1.3.2
  3. *
  4. * Copyright (c) Remix Software Inc.
  5. *
  6. * This source code is licensed under the MIT license found in the
  7. * LICENSE.md file in the root directory of this source tree.
  8. *
  9. * @license MIT
  10. */
  11. function _extends() {
  12. _extends = Object.assign ? Object.assign.bind() : function (target) {
  13. for (var i = 1; i < arguments.length; i++) {
  14. var source = arguments[i];
  15. for (var key in source) {
  16. if (Object.prototype.hasOwnProperty.call(source, key)) {
  17. target[key] = source[key];
  18. }
  19. }
  20. }
  21. return target;
  22. };
  23. return _extends.apply(this, arguments);
  24. }
  25. ////////////////////////////////////////////////////////////////////////////////
  26. //#region Types and Constants
  27. ////////////////////////////////////////////////////////////////////////////////
  28. /**
  29. * Actions represent the type of change to a location value.
  30. */
  31. var Action;
  32. (function (Action) {
  33. /**
  34. * A POP indicates a change to an arbitrary index in the history stack, such
  35. * as a back or forward navigation. It does not describe the direction of the
  36. * navigation, only that the current index changed.
  37. *
  38. * Note: This is the default action for newly created history objects.
  39. */
  40. Action["Pop"] = "POP";
  41. /**
  42. * A PUSH indicates a new entry being added to the history stack, such as when
  43. * a link is clicked and a new page loads. When this happens, all subsequent
  44. * entries in the stack are lost.
  45. */
  46. Action["Push"] = "PUSH";
  47. /**
  48. * A REPLACE indicates the entry at the current index in the history stack
  49. * being replaced by a new one.
  50. */
  51. Action["Replace"] = "REPLACE";
  52. })(Action || (Action = {}));
  53. const PopStateEventType = "popstate";
  54. /**
  55. * Memory history stores the current location in memory. It is designed for use
  56. * in stateful non-browser environments like tests and React Native.
  57. */
  58. function createMemoryHistory(options) {
  59. if (options === void 0) {
  60. options = {};
  61. }
  62. let {
  63. initialEntries = ["/"],
  64. initialIndex,
  65. v5Compat = false
  66. } = options;
  67. let entries; // Declare so we can access from createMemoryLocation
  68. entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
  69. let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
  70. let action = Action.Pop;
  71. let listener = null;
  72. function clampIndex(n) {
  73. return Math.min(Math.max(n, 0), entries.length - 1);
  74. }
  75. function getCurrentLocation() {
  76. return entries[index];
  77. }
  78. function createMemoryLocation(to, state, key) {
  79. if (state === void 0) {
  80. state = null;
  81. }
  82. let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
  83. warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
  84. return location;
  85. }
  86. function createHref(to) {
  87. return typeof to === "string" ? to : createPath(to);
  88. }
  89. let history = {
  90. get index() {
  91. return index;
  92. },
  93. get action() {
  94. return action;
  95. },
  96. get location() {
  97. return getCurrentLocation();
  98. },
  99. createHref,
  100. createURL(to) {
  101. return new URL(createHref(to), "http://localhost");
  102. },
  103. encodeLocation(to) {
  104. let path = typeof to === "string" ? parsePath(to) : to;
  105. return {
  106. pathname: path.pathname || "",
  107. search: path.search || "",
  108. hash: path.hash || ""
  109. };
  110. },
  111. push(to, state) {
  112. action = Action.Push;
  113. let nextLocation = createMemoryLocation(to, state);
  114. index += 1;
  115. entries.splice(index, entries.length, nextLocation);
  116. if (v5Compat && listener) {
  117. listener({
  118. action,
  119. location: nextLocation,
  120. delta: 1
  121. });
  122. }
  123. },
  124. replace(to, state) {
  125. action = Action.Replace;
  126. let nextLocation = createMemoryLocation(to, state);
  127. entries[index] = nextLocation;
  128. if (v5Compat && listener) {
  129. listener({
  130. action,
  131. location: nextLocation,
  132. delta: 0
  133. });
  134. }
  135. },
  136. go(delta) {
  137. action = Action.Pop;
  138. let nextIndex = clampIndex(index + delta);
  139. let nextLocation = entries[nextIndex];
  140. index = nextIndex;
  141. if (listener) {
  142. listener({
  143. action,
  144. location: nextLocation,
  145. delta
  146. });
  147. }
  148. },
  149. listen(fn) {
  150. listener = fn;
  151. return () => {
  152. listener = null;
  153. };
  154. }
  155. };
  156. return history;
  157. }
  158. /**
  159. * Browser history stores the location in regular URLs. This is the standard for
  160. * most web apps, but it requires some configuration on the server to ensure you
  161. * serve the same app at multiple URLs.
  162. *
  163. * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
  164. */
  165. function createBrowserHistory(options) {
  166. if (options === void 0) {
  167. options = {};
  168. }
  169. function createBrowserLocation(window, globalHistory) {
  170. let {
  171. pathname,
  172. search,
  173. hash
  174. } = window.location;
  175. return createLocation("", {
  176. pathname,
  177. search,
  178. hash
  179. }, // state defaults to `null` because `window.history.state` does
  180. globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
  181. }
  182. function createBrowserHref(window, to) {
  183. return typeof to === "string" ? to : createPath(to);
  184. }
  185. return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
  186. }
  187. /**
  188. * Hash history stores the location in window.location.hash. This makes it ideal
  189. * for situations where you don't want to send the location to the server for
  190. * some reason, either because you do cannot configure it or the URL space is
  191. * reserved for something else.
  192. *
  193. * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
  194. */
  195. function createHashHistory(options) {
  196. if (options === void 0) {
  197. options = {};
  198. }
  199. function createHashLocation(window, globalHistory) {
  200. let {
  201. pathname = "/",
  202. search = "",
  203. hash = ""
  204. } = parsePath(window.location.hash.substr(1));
  205. return createLocation("", {
  206. pathname,
  207. search,
  208. hash
  209. }, // state defaults to `null` because `window.history.state` does
  210. globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
  211. }
  212. function createHashHref(window, to) {
  213. let base = window.document.querySelector("base");
  214. let href = "";
  215. if (base && base.getAttribute("href")) {
  216. let url = window.location.href;
  217. let hashIndex = url.indexOf("#");
  218. href = hashIndex === -1 ? url : url.slice(0, hashIndex);
  219. }
  220. return href + "#" + (typeof to === "string" ? to : createPath(to));
  221. }
  222. function validateHashLocation(location, to) {
  223. warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
  224. }
  225. return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
  226. }
  227. function invariant(value, message) {
  228. if (value === false || value === null || typeof value === "undefined") {
  229. throw new Error(message);
  230. }
  231. }
  232. function warning$1(cond, message) {
  233. if (!cond) {
  234. // eslint-disable-next-line no-console
  235. if (typeof console !== "undefined") console.warn(message);
  236. try {
  237. // Welcome to debugging history!
  238. //
  239. // This error is thrown as a convenience so you can more easily
  240. // find the source for a warning that appears in the console by
  241. // enabling "pause on exceptions" in your JavaScript debugger.
  242. throw new Error(message); // eslint-disable-next-line no-empty
  243. } catch (e) {}
  244. }
  245. }
  246. function createKey() {
  247. return Math.random().toString(36).substr(2, 8);
  248. }
  249. /**
  250. * For browser-based histories, we combine the state and key into an object
  251. */
  252. function getHistoryState(location, index) {
  253. return {
  254. usr: location.state,
  255. key: location.key,
  256. idx: index
  257. };
  258. }
  259. /**
  260. * Creates a Location object with a unique key from the given Path
  261. */
  262. function createLocation(current, to, state, key) {
  263. if (state === void 0) {
  264. state = null;
  265. }
  266. let location = _extends({
  267. pathname: typeof current === "string" ? current : current.pathname,
  268. search: "",
  269. hash: ""
  270. }, typeof to === "string" ? parsePath(to) : to, {
  271. state,
  272. // TODO: This could be cleaned up. push/replace should probably just take
  273. // full Locations now and avoid the need to run through this flow at all
  274. // But that's a pretty big refactor to the current test suite so going to
  275. // keep as is for the time being and just let any incoming keys take precedence
  276. key: to && to.key || key || createKey()
  277. });
  278. return location;
  279. }
  280. /**
  281. * Creates a string URL path from the given pathname, search, and hash components.
  282. */
  283. function createPath(_ref) {
  284. let {
  285. pathname = "/",
  286. search = "",
  287. hash = ""
  288. } = _ref;
  289. if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
  290. if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
  291. return pathname;
  292. }
  293. /**
  294. * Parses a string URL path into its separate pathname, search, and hash components.
  295. */
  296. function parsePath(path) {
  297. let parsedPath = {};
  298. if (path) {
  299. let hashIndex = path.indexOf("#");
  300. if (hashIndex >= 0) {
  301. parsedPath.hash = path.substr(hashIndex);
  302. path = path.substr(0, hashIndex);
  303. }
  304. let searchIndex = path.indexOf("?");
  305. if (searchIndex >= 0) {
  306. parsedPath.search = path.substr(searchIndex);
  307. path = path.substr(0, searchIndex);
  308. }
  309. if (path) {
  310. parsedPath.pathname = path;
  311. }
  312. }
  313. return parsedPath;
  314. }
  315. function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
  316. if (options === void 0) {
  317. options = {};
  318. }
  319. let {
  320. window = document.defaultView,
  321. v5Compat = false
  322. } = options;
  323. let globalHistory = window.history;
  324. let action = Action.Pop;
  325. let listener = null;
  326. let index = getIndex(); // Index should only be null when we initialize. If not, it's because the
  327. // user called history.pushState or history.replaceState directly, in which
  328. // case we should log a warning as it will result in bugs.
  329. if (index == null) {
  330. index = 0;
  331. globalHistory.replaceState(_extends({}, globalHistory.state, {
  332. idx: index
  333. }), "");
  334. }
  335. function getIndex() {
  336. let state = globalHistory.state || {
  337. idx: null
  338. };
  339. return state.idx;
  340. }
  341. function handlePop() {
  342. action = Action.Pop;
  343. let nextIndex = getIndex();
  344. let delta = nextIndex == null ? null : nextIndex - index;
  345. index = nextIndex;
  346. if (listener) {
  347. listener({
  348. action,
  349. location: history.location,
  350. delta
  351. });
  352. }
  353. }
  354. function push(to, state) {
  355. action = Action.Push;
  356. let location = createLocation(history.location, to, state);
  357. if (validateLocation) validateLocation(location, to);
  358. index = getIndex() + 1;
  359. let historyState = getHistoryState(location, index);
  360. let url = history.createHref(location); // try...catch because iOS limits us to 100 pushState calls :/
  361. try {
  362. globalHistory.pushState(historyState, "", url);
  363. } catch (error) {
  364. // They are going to lose state here, but there is no real
  365. // way to warn them about it since the page will refresh...
  366. window.location.assign(url);
  367. }
  368. if (v5Compat && listener) {
  369. listener({
  370. action,
  371. location: history.location,
  372. delta: 1
  373. });
  374. }
  375. }
  376. function replace(to, state) {
  377. action = Action.Replace;
  378. let location = createLocation(history.location, to, state);
  379. if (validateLocation) validateLocation(location, to);
  380. index = getIndex();
  381. let historyState = getHistoryState(location, index);
  382. let url = history.createHref(location);
  383. globalHistory.replaceState(historyState, "", url);
  384. if (v5Compat && listener) {
  385. listener({
  386. action,
  387. location: history.location,
  388. delta: 0
  389. });
  390. }
  391. }
  392. function createURL(to) {
  393. // window.location.origin is "null" (the literal string value) in Firefox
  394. // under certain conditions, notably when serving from a local HTML file
  395. // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
  396. let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
  397. let href = typeof to === "string" ? to : createPath(to);
  398. invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
  399. return new URL(href, base);
  400. }
  401. let history = {
  402. get action() {
  403. return action;
  404. },
  405. get location() {
  406. return getLocation(window, globalHistory);
  407. },
  408. listen(fn) {
  409. if (listener) {
  410. throw new Error("A history only accepts one active listener");
  411. }
  412. window.addEventListener(PopStateEventType, handlePop);
  413. listener = fn;
  414. return () => {
  415. window.removeEventListener(PopStateEventType, handlePop);
  416. listener = null;
  417. };
  418. },
  419. createHref(to) {
  420. return createHref(window, to);
  421. },
  422. createURL,
  423. encodeLocation(to) {
  424. // Encode a Location the same way window.location would
  425. let url = createURL(to);
  426. return {
  427. pathname: url.pathname,
  428. search: url.search,
  429. hash: url.hash
  430. };
  431. },
  432. push,
  433. replace,
  434. go(n) {
  435. return globalHistory.go(n);
  436. }
  437. };
  438. return history;
  439. } //#endregion
  440. var ResultType;
  441. (function (ResultType) {
  442. ResultType["data"] = "data";
  443. ResultType["deferred"] = "deferred";
  444. ResultType["redirect"] = "redirect";
  445. ResultType["error"] = "error";
  446. })(ResultType || (ResultType = {}));
  447. function isIndexRoute(route) {
  448. return route.index === true;
  449. } // Walk the route tree generating unique IDs where necessary so we are working
  450. // solely with AgnosticDataRouteObject's within the Router
  451. function convertRoutesToDataRoutes(routes, parentPath, allIds) {
  452. if (parentPath === void 0) {
  453. parentPath = [];
  454. }
  455. if (allIds === void 0) {
  456. allIds = new Set();
  457. }
  458. return routes.map((route, index) => {
  459. let treePath = [...parentPath, index];
  460. let id = typeof route.id === "string" ? route.id : treePath.join("-");
  461. invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
  462. invariant(!allIds.has(id), "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
  463. allIds.add(id);
  464. if (isIndexRoute(route)) {
  465. let indexRoute = _extends({}, route, {
  466. id
  467. });
  468. return indexRoute;
  469. } else {
  470. let pathOrLayoutRoute = _extends({}, route, {
  471. id,
  472. children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
  473. });
  474. return pathOrLayoutRoute;
  475. }
  476. });
  477. }
  478. /**
  479. * Matches the given routes to a location and returns the match data.
  480. *
  481. * @see https://reactrouter.com/utils/match-routes
  482. */
  483. function matchRoutes(routes, locationArg, basename) {
  484. if (basename === void 0) {
  485. basename = "/";
  486. }
  487. let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
  488. let pathname = stripBasename(location.pathname || "/", basename);
  489. if (pathname == null) {
  490. return null;
  491. }
  492. let branches = flattenRoutes(routes);
  493. rankRouteBranches(branches);
  494. let matches = null;
  495. for (let i = 0; matches == null && i < branches.length; ++i) {
  496. matches = matchRouteBranch(branches[i], // Incoming pathnames are generally encoded from either window.location
  497. // or from router.navigate, but we want to match against the unencoded
  498. // paths in the route definitions. Memory router locations won't be
  499. // encoded here but there also shouldn't be anything to decode so this
  500. // should be a safe operation. This avoids needing matchRoutes to be
  501. // history-aware.
  502. safelyDecodeURI(pathname));
  503. }
  504. return matches;
  505. }
  506. function flattenRoutes(routes, branches, parentsMeta, parentPath) {
  507. if (branches === void 0) {
  508. branches = [];
  509. }
  510. if (parentsMeta === void 0) {
  511. parentsMeta = [];
  512. }
  513. if (parentPath === void 0) {
  514. parentPath = "";
  515. }
  516. let flattenRoute = (route, index, relativePath) => {
  517. let meta = {
  518. relativePath: relativePath === undefined ? route.path || "" : relativePath,
  519. caseSensitive: route.caseSensitive === true,
  520. childrenIndex: index,
  521. route
  522. };
  523. if (meta.relativePath.startsWith("/")) {
  524. 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.");
  525. meta.relativePath = meta.relativePath.slice(parentPath.length);
  526. }
  527. let path = joinPaths([parentPath, meta.relativePath]);
  528. let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
  529. // route tree depth-first and child routes appear before their parents in
  530. // the "flattened" version.
  531. if (route.children && route.children.length > 0) {
  532. invariant( // Our types know better, but runtime JS may not!
  533. // @ts-expect-error
  534. route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
  535. flattenRoutes(route.children, branches, routesMeta, path);
  536. } // Routes without a path shouldn't ever match by themselves unless they are
  537. // index routes, so don't add them to the list of possible branches.
  538. if (route.path == null && !route.index) {
  539. return;
  540. }
  541. branches.push({
  542. path,
  543. score: computeScore(path, route.index),
  544. routesMeta
  545. });
  546. };
  547. routes.forEach((route, index) => {
  548. var _route$path;
  549. // coarse-grain check for optional params
  550. if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
  551. flattenRoute(route, index);
  552. } else {
  553. for (let exploded of explodeOptionalSegments(route.path)) {
  554. flattenRoute(route, index, exploded);
  555. }
  556. }
  557. });
  558. return branches;
  559. }
  560. /**
  561. * Computes all combinations of optional path segments for a given path,
  562. * excluding combinations that are ambiguous and of lower priority.
  563. *
  564. * For example, `/one/:two?/three/:four?/:five?` explodes to:
  565. * - `/one/three`
  566. * - `/one/:two/three`
  567. * - `/one/three/:four`
  568. * - `/one/three/:five`
  569. * - `/one/:two/three/:four`
  570. * - `/one/:two/three/:five`
  571. * - `/one/three/:four/:five`
  572. * - `/one/:two/three/:four/:five`
  573. */
  574. function explodeOptionalSegments(path) {
  575. let segments = path.split("/");
  576. if (segments.length === 0) return [];
  577. let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
  578. let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
  579. let required = first.replace(/\?$/, "");
  580. if (rest.length === 0) {
  581. // Intepret empty string as omitting an optional segment
  582. // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
  583. return isOptional ? [required, ""] : [required];
  584. }
  585. let restExploded = explodeOptionalSegments(rest.join("/"));
  586. let result = []; // All child paths with the prefix. Do this for all children before the
  587. // optional version for all children so we get consistent ordering where the
  588. // parent optional aspect is preferred as required. Otherwise, we can get
  589. // child sections interspersed where deeper optional segments are higher than
  590. // parent optional segments, where for example, /:two would explodes _earlier_
  591. // then /:one. By always including the parent as required _for all children_
  592. // first, we avoid this issue
  593. result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
  594. if (isOptional) {
  595. result.push(...restExploded);
  596. } // for absolute paths, ensure `/` instead of empty segment
  597. return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
  598. }
  599. function rankRouteBranches(branches) {
  600. branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
  601. : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
  602. }
  603. const paramRe = /^:\w+$/;
  604. const dynamicSegmentValue = 3;
  605. const indexRouteValue = 2;
  606. const emptySegmentValue = 1;
  607. const staticSegmentValue = 10;
  608. const splatPenalty = -2;
  609. const isSplat = s => s === "*";
  610. function computeScore(path, index) {
  611. let segments = path.split("/");
  612. let initialScore = segments.length;
  613. if (segments.some(isSplat)) {
  614. initialScore += splatPenalty;
  615. }
  616. if (index) {
  617. initialScore += indexRouteValue;
  618. }
  619. return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
  620. }
  621. function compareIndexes(a, b) {
  622. let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
  623. return siblings ? // If two routes are siblings, we should try to match the earlier sibling
  624. // first. This allows people to have fine-grained control over the matching
  625. // behavior by simply putting routes with identical paths in the order they
  626. // want them tried.
  627. a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
  628. // so they sort equally.
  629. 0;
  630. }
  631. function matchRouteBranch(branch, pathname) {
  632. let {
  633. routesMeta
  634. } = branch;
  635. let matchedParams = {};
  636. let matchedPathname = "/";
  637. let matches = [];
  638. for (let i = 0; i < routesMeta.length; ++i) {
  639. let meta = routesMeta[i];
  640. let end = i === routesMeta.length - 1;
  641. let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
  642. let match = matchPath({
  643. path: meta.relativePath,
  644. caseSensitive: meta.caseSensitive,
  645. end
  646. }, remainingPathname);
  647. if (!match) return null;
  648. Object.assign(matchedParams, match.params);
  649. let route = meta.route;
  650. matches.push({
  651. // TODO: Can this as be avoided?
  652. params: matchedParams,
  653. pathname: joinPaths([matchedPathname, match.pathname]),
  654. pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
  655. route
  656. });
  657. if (match.pathnameBase !== "/") {
  658. matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
  659. }
  660. }
  661. return matches;
  662. }
  663. /**
  664. * Returns a path with params interpolated.
  665. *
  666. * @see https://reactrouter.com/utils/generate-path
  667. */
  668. function generatePath(originalPath, params) {
  669. if (params === void 0) {
  670. params = {};
  671. }
  672. let path = originalPath;
  673. if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
  674. 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(/\*$/, "/*") + "\"."));
  675. path = path.replace(/\*$/, "/*");
  676. }
  677. return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
  678. let param = params[key];
  679. if (optional === "?") {
  680. return param == null ? "" : param;
  681. }
  682. if (param == null) {
  683. invariant(false, "Missing \":" + key + "\" param");
  684. }
  685. return param;
  686. }).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
  687. let param = params[key];
  688. if (optional === "?") {
  689. return param == null ? "" : "/" + param;
  690. }
  691. if (param == null) {
  692. invariant(false, "Missing \":" + key + "\" param");
  693. }
  694. return "/" + param;
  695. }) // Remove any optional markers from optional static segments
  696. .replace(/\?/g, "").replace(/(\/?)\*/, (_, prefix, __, str) => {
  697. const star = "*";
  698. if (params[star] == null) {
  699. // If no splat was provided, trim the trailing slash _unless_ it's
  700. // the entire path
  701. return str === "/*" ? "/" : "";
  702. } // Apply the splat
  703. return "" + prefix + params[star];
  704. });
  705. }
  706. /**
  707. * Performs pattern matching on a URL pathname and returns information about
  708. * the match.
  709. *
  710. * @see https://reactrouter.com/utils/match-path
  711. */
  712. function matchPath(pattern, pathname) {
  713. if (typeof pattern === "string") {
  714. pattern = {
  715. path: pattern,
  716. caseSensitive: false,
  717. end: true
  718. };
  719. }
  720. let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
  721. let match = pathname.match(matcher);
  722. if (!match) return null;
  723. let matchedPathname = match[0];
  724. let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
  725. let captureGroups = match.slice(1);
  726. let params = paramNames.reduce((memo, paramName, index) => {
  727. // We need to compute the pathnameBase here using the raw splat value
  728. // instead of using params["*"] later because it will be decoded then
  729. if (paramName === "*") {
  730. let splatValue = captureGroups[index] || "";
  731. pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
  732. }
  733. memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
  734. return memo;
  735. }, {});
  736. return {
  737. params,
  738. pathname: matchedPathname,
  739. pathnameBase,
  740. pattern
  741. };
  742. }
  743. function compilePath(path, caseSensitive, end) {
  744. if (caseSensitive === void 0) {
  745. caseSensitive = false;
  746. }
  747. if (end === void 0) {
  748. end = true;
  749. }
  750. 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(/\*$/, "/*") + "\"."));
  751. let paramNames = [];
  752. let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
  753. .replace(/^\/*/, "/") // Make sure it has a leading /
  754. .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
  755. .replace(/\/:(\w+)/g, (_, paramName) => {
  756. paramNames.push(paramName);
  757. return "/([^\\/]+)";
  758. });
  759. if (path.endsWith("*")) {
  760. paramNames.push("*");
  761. regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
  762. : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
  763. } else if (end) {
  764. // When matching to the end, ignore trailing slashes
  765. regexpSource += "\\/*$";
  766. } else if (path !== "" && path !== "/") {
  767. // If our path is non-empty and contains anything beyond an initial slash,
  768. // then we have _some_ form of path in our regex so we should expect to
  769. // match only if we find the end of this path segment. Look for an optional
  770. // non-captured trailing slash (to match a portion of the URL) or the end
  771. // of the path (if we've matched to the end). We used to do this with a
  772. // word boundary but that gives false positives on routes like
  773. // /user-preferences since `-` counts as a word boundary.
  774. regexpSource += "(?:(?=\\/|$))";
  775. } else ;
  776. let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
  777. return [matcher, paramNames];
  778. }
  779. function safelyDecodeURI(value) {
  780. try {
  781. return decodeURI(value);
  782. } catch (error) {
  783. 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 + ")."));
  784. return value;
  785. }
  786. }
  787. function safelyDecodeURIComponent(value, paramName) {
  788. try {
  789. return decodeURIComponent(value);
  790. } catch (error) {
  791. warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
  792. return value;
  793. }
  794. }
  795. /**
  796. * @private
  797. */
  798. function stripBasename(pathname, basename) {
  799. if (basename === "/") return pathname;
  800. if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
  801. return null;
  802. } // We want to leave trailing slash behavior in the user's control, so if they
  803. // specify a basename with a trailing slash, we should support it
  804. let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
  805. let nextChar = pathname.charAt(startIndex);
  806. if (nextChar && nextChar !== "/") {
  807. // pathname does not start with basename/
  808. return null;
  809. }
  810. return pathname.slice(startIndex) || "/";
  811. }
  812. /**
  813. * @private
  814. */
  815. function warning(cond, message) {
  816. if (!cond) {
  817. // eslint-disable-next-line no-console
  818. if (typeof console !== "undefined") console.warn(message);
  819. try {
  820. // Welcome to debugging @remix-run/router!
  821. //
  822. // This error is thrown as a convenience so you can more easily
  823. // find the source for a warning that appears in the console by
  824. // enabling "pause on exceptions" in your JavaScript debugger.
  825. throw new Error(message); // eslint-disable-next-line no-empty
  826. } catch (e) {}
  827. }
  828. }
  829. /**
  830. * Returns a resolved path object relative to the given pathname.
  831. *
  832. * @see https://reactrouter.com/utils/resolve-path
  833. */
  834. function resolvePath(to, fromPathname) {
  835. if (fromPathname === void 0) {
  836. fromPathname = "/";
  837. }
  838. let {
  839. pathname: toPathname,
  840. search = "",
  841. hash = ""
  842. } = typeof to === "string" ? parsePath(to) : to;
  843. let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
  844. return {
  845. pathname,
  846. search: normalizeSearch(search),
  847. hash: normalizeHash(hash)
  848. };
  849. }
  850. function resolvePathname(relativePath, fromPathname) {
  851. let segments = fromPathname.replace(/\/+$/, "").split("/");
  852. let relativeSegments = relativePath.split("/");
  853. relativeSegments.forEach(segment => {
  854. if (segment === "..") {
  855. // Keep the root "" segment so the pathname starts at /
  856. if (segments.length > 1) segments.pop();
  857. } else if (segment !== ".") {
  858. segments.push(segment);
  859. }
  860. });
  861. return segments.length > 1 ? segments.join("/") : "/";
  862. }
  863. function getInvalidPathError(char, field, dest, path) {
  864. 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.";
  865. }
  866. /**
  867. * @private
  868. *
  869. * When processing relative navigation we want to ignore ancestor routes that
  870. * do not contribute to the path, such that index/pathless layout routes don't
  871. * interfere.
  872. *
  873. * For example, when moving a route element into an index route and/or a
  874. * pathless layout route, relative link behavior contained within should stay
  875. * the same. Both of the following examples should link back to the root:
  876. *
  877. * <Route path="/">
  878. * <Route path="accounts" element={<Link to=".."}>
  879. * </Route>
  880. *
  881. * <Route path="/">
  882. * <Route path="accounts">
  883. * <Route element={<AccountsLayout />}> // <-- Does not contribute
  884. * <Route index element={<Link to=".."} /> // <-- Does not contribute
  885. * </Route
  886. * </Route>
  887. * </Route>
  888. */
  889. function getPathContributingMatches(matches) {
  890. return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
  891. }
  892. /**
  893. * @private
  894. */
  895. function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
  896. if (isPathRelative === void 0) {
  897. isPathRelative = false;
  898. }
  899. let to;
  900. if (typeof toArg === "string") {
  901. to = parsePath(toArg);
  902. } else {
  903. to = _extends({}, toArg);
  904. invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
  905. invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
  906. invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
  907. }
  908. let isEmptyPath = toArg === "" || to.pathname === "";
  909. let toPathname = isEmptyPath ? "/" : to.pathname;
  910. let from; // Routing is relative to the current pathname if explicitly requested.
  911. //
  912. // If a pathname is explicitly provided in `to`, it should be relative to the
  913. // route context. This is explained in `Note on `<Link to>` values` in our
  914. // migration guide from v5 as a means of disambiguation between `to` values
  915. // that begin with `/` and those that do not. However, this is problematic for
  916. // `to` values that do not provide a pathname. `to` can simply be a search or
  917. // hash string, in which case we should assume that the navigation is relative
  918. // to the current location's pathname and *not* the route pathname.
  919. if (isPathRelative || toPathname == null) {
  920. from = locationPathname;
  921. } else {
  922. let routePathnameIndex = routePathnames.length - 1;
  923. if (toPathname.startsWith("..")) {
  924. let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
  925. // URL segment". This is a key difference from how <a href> works and a
  926. // major reason we call this a "to" value instead of a "href".
  927. while (toSegments[0] === "..") {
  928. toSegments.shift();
  929. routePathnameIndex -= 1;
  930. }
  931. to.pathname = toSegments.join("/");
  932. } // If there are more ".." segments than parent routes, resolve relative to
  933. // the root / URL.
  934. from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
  935. }
  936. let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original "to" had one
  937. let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/"); // Or if this was a link to the current path which has a trailing slash
  938. let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
  939. if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
  940. path.pathname += "/";
  941. }
  942. return path;
  943. }
  944. /**
  945. * @private
  946. */
  947. function getToPathname(to) {
  948. // Empty strings should be treated the same as / paths
  949. return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
  950. }
  951. /**
  952. * @private
  953. */
  954. const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
  955. /**
  956. * @private
  957. */
  958. const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
  959. /**
  960. * @private
  961. */
  962. const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
  963. /**
  964. * @private
  965. */
  966. const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
  967. /**
  968. * This is a shortcut for creating `application/json` responses. Converts `data`
  969. * to JSON and sets the `Content-Type` header.
  970. */
  971. const json = function json(data, init) {
  972. if (init === void 0) {
  973. init = {};
  974. }
  975. let responseInit = typeof init === "number" ? {
  976. status: init
  977. } : init;
  978. let headers = new Headers(responseInit.headers);
  979. if (!headers.has("Content-Type")) {
  980. headers.set("Content-Type", "application/json; charset=utf-8");
  981. }
  982. return new Response(JSON.stringify(data), _extends({}, responseInit, {
  983. headers
  984. }));
  985. };
  986. class AbortedDeferredError extends Error {}
  987. class DeferredData {
  988. constructor(data, responseInit) {
  989. this.pendingKeysSet = new Set();
  990. this.subscribers = new Set();
  991. this.deferredKeys = [];
  992. invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects"); // Set up an AbortController + Promise we can race against to exit early
  993. // cancellation
  994. let reject;
  995. this.abortPromise = new Promise((_, r) => reject = r);
  996. this.controller = new AbortController();
  997. let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
  998. this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
  999. this.controller.signal.addEventListener("abort", onAbort);
  1000. this.data = Object.entries(data).reduce((acc, _ref) => {
  1001. let [key, value] = _ref;
  1002. return Object.assign(acc, {
  1003. [key]: this.trackPromise(key, value)
  1004. });
  1005. }, {});
  1006. if (this.done) {
  1007. // All incoming values were resolved
  1008. this.unlistenAbortSignal();
  1009. }
  1010. this.init = responseInit;
  1011. }
  1012. trackPromise(key, value) {
  1013. if (!(value instanceof Promise)) {
  1014. return value;
  1015. }
  1016. this.deferredKeys.push(key);
  1017. this.pendingKeysSet.add(key); // We store a little wrapper promise that will be extended with
  1018. // _data/_error props upon resolve/reject
  1019. let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error)); // Register rejection listeners to avoid uncaught promise rejections on
  1020. // errors or aborted deferred values
  1021. promise.catch(() => {});
  1022. Object.defineProperty(promise, "_tracked", {
  1023. get: () => true
  1024. });
  1025. return promise;
  1026. }
  1027. onSettle(promise, key, error, data) {
  1028. if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
  1029. this.unlistenAbortSignal();
  1030. Object.defineProperty(promise, "_error", {
  1031. get: () => error
  1032. });
  1033. return Promise.reject(error);
  1034. }
  1035. this.pendingKeysSet.delete(key);
  1036. if (this.done) {
  1037. // Nothing left to abort!
  1038. this.unlistenAbortSignal();
  1039. }
  1040. if (error) {
  1041. Object.defineProperty(promise, "_error", {
  1042. get: () => error
  1043. });
  1044. this.emit(false, key);
  1045. return Promise.reject(error);
  1046. }
  1047. Object.defineProperty(promise, "_data", {
  1048. get: () => data
  1049. });
  1050. this.emit(false, key);
  1051. return data;
  1052. }
  1053. emit(aborted, settledKey) {
  1054. this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
  1055. }
  1056. subscribe(fn) {
  1057. this.subscribers.add(fn);
  1058. return () => this.subscribers.delete(fn);
  1059. }
  1060. cancel() {
  1061. this.controller.abort();
  1062. this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
  1063. this.emit(true);
  1064. }
  1065. async resolveData(signal) {
  1066. let aborted = false;
  1067. if (!this.done) {
  1068. let onAbort = () => this.cancel();
  1069. signal.addEventListener("abort", onAbort);
  1070. aborted = await new Promise(resolve => {
  1071. this.subscribe(aborted => {
  1072. signal.removeEventListener("abort", onAbort);
  1073. if (aborted || this.done) {
  1074. resolve(aborted);
  1075. }
  1076. });
  1077. });
  1078. }
  1079. return aborted;
  1080. }
  1081. get done() {
  1082. return this.pendingKeysSet.size === 0;
  1083. }
  1084. get unwrappedData() {
  1085. invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
  1086. return Object.entries(this.data).reduce((acc, _ref2) => {
  1087. let [key, value] = _ref2;
  1088. return Object.assign(acc, {
  1089. [key]: unwrapTrackedPromise(value)
  1090. });
  1091. }, {});
  1092. }
  1093. get pendingKeys() {
  1094. return Array.from(this.pendingKeysSet);
  1095. }
  1096. }
  1097. function isTrackedPromise(value) {
  1098. return value instanceof Promise && value._tracked === true;
  1099. }
  1100. function unwrapTrackedPromise(value) {
  1101. if (!isTrackedPromise(value)) {
  1102. return value;
  1103. }
  1104. if (value._error) {
  1105. throw value._error;
  1106. }
  1107. return value._data;
  1108. }
  1109. const defer = function defer(data, init) {
  1110. if (init === void 0) {
  1111. init = {};
  1112. }
  1113. let responseInit = typeof init === "number" ? {
  1114. status: init
  1115. } : init;
  1116. return new DeferredData(data, responseInit);
  1117. };
  1118. /**
  1119. * A redirect response. Sets the status code and the `Location` header.
  1120. * Defaults to "302 Found".
  1121. */
  1122. const redirect = function redirect(url, init) {
  1123. if (init === void 0) {
  1124. init = 302;
  1125. }
  1126. let responseInit = init;
  1127. if (typeof responseInit === "number") {
  1128. responseInit = {
  1129. status: responseInit
  1130. };
  1131. } else if (typeof responseInit.status === "undefined") {
  1132. responseInit.status = 302;
  1133. }
  1134. let headers = new Headers(responseInit.headers);
  1135. headers.set("Location", url);
  1136. return new Response(null, _extends({}, responseInit, {
  1137. headers
  1138. }));
  1139. };
  1140. /**
  1141. * @private
  1142. * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
  1143. */
  1144. class ErrorResponse {
  1145. constructor(status, statusText, data, internal) {
  1146. if (internal === void 0) {
  1147. internal = false;
  1148. }
  1149. this.status = status;
  1150. this.statusText = statusText || "";
  1151. this.internal = internal;
  1152. if (data instanceof Error) {
  1153. this.data = data.toString();
  1154. this.error = data;
  1155. } else {
  1156. this.data = data;
  1157. }
  1158. }
  1159. }
  1160. /**
  1161. * Check if the given error is an ErrorResponse generated from a 4xx/5xx
  1162. * Response thrown from an action/loader
  1163. */
  1164. function isRouteErrorResponse(error) {
  1165. return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
  1166. }
  1167. const validMutationMethodsArr = ["post", "put", "patch", "delete"];
  1168. const validMutationMethods = new Set(validMutationMethodsArr);
  1169. const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
  1170. const validRequestMethods = new Set(validRequestMethodsArr);
  1171. const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
  1172. const redirectPreserveMethodStatusCodes = new Set([307, 308]);
  1173. const IDLE_NAVIGATION = {
  1174. state: "idle",
  1175. location: undefined,
  1176. formMethod: undefined,
  1177. formAction: undefined,
  1178. formEncType: undefined,
  1179. formData: undefined
  1180. };
  1181. const IDLE_FETCHER = {
  1182. state: "idle",
  1183. data: undefined,
  1184. formMethod: undefined,
  1185. formAction: undefined,
  1186. formEncType: undefined,
  1187. formData: undefined
  1188. };
  1189. const IDLE_BLOCKER = {
  1190. state: "unblocked",
  1191. proceed: undefined,
  1192. reset: undefined,
  1193. location: undefined
  1194. };
  1195. const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
  1196. const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
  1197. const isServer = !isBrowser; //#endregion
  1198. ////////////////////////////////////////////////////////////////////////////////
  1199. //#region createRouter
  1200. ////////////////////////////////////////////////////////////////////////////////
  1201. /**
  1202. * Create a router and listen to history POP navigations
  1203. */
  1204. function createRouter(init) {
  1205. invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
  1206. let dataRoutes = convertRoutesToDataRoutes(init.routes); // Cleanup function for history
  1207. let unlistenHistory = null; // Externally-provided functions to call on all state changes
  1208. let subscribers = new Set(); // Externally-provided object to hold scroll restoration locations during routing
  1209. let savedScrollPositions = null; // Externally-provided function to get scroll restoration keys
  1210. let getScrollRestorationKey = null; // Externally-provided function to get current scroll position
  1211. let getScrollPosition = null; // One-time flag to control the initial hydration scroll restoration. Because
  1212. // we don't get the saved positions from <ScrollRestoration /> until _after_
  1213. // the initial render, we need to manually trigger a separate updateState to
  1214. // send along the restoreScrollPosition
  1215. // Set to true if we have `hydrationData` since we assume we were SSR'd and that
  1216. // SSR did the initial scroll restoration.
  1217. let initialScrollRestored = init.hydrationData != null;
  1218. let initialMatches = matchRoutes(dataRoutes, init.history.location, init.basename);
  1219. let initialErrors = null;
  1220. if (initialMatches == null) {
  1221. // If we do not match a user-provided-route, fall back to the root
  1222. // to allow the error boundary to take over
  1223. let error = getInternalRouterError(404, {
  1224. pathname: init.history.location.pathname
  1225. });
  1226. let {
  1227. matches,
  1228. route
  1229. } = getShortCircuitMatches(dataRoutes);
  1230. initialMatches = matches;
  1231. initialErrors = {
  1232. [route.id]: error
  1233. };
  1234. }
  1235. let initialized = !initialMatches.some(m => m.route.loader) || init.hydrationData != null;
  1236. let router;
  1237. let state = {
  1238. historyAction: init.history.action,
  1239. location: init.history.location,
  1240. matches: initialMatches,
  1241. initialized,
  1242. navigation: IDLE_NAVIGATION,
  1243. // Don't restore on initial updateState() if we were SSR'd
  1244. restoreScrollPosition: init.hydrationData != null ? false : null,
  1245. preventScrollReset: false,
  1246. revalidation: "idle",
  1247. loaderData: init.hydrationData && init.hydrationData.loaderData || {},
  1248. actionData: init.hydrationData && init.hydrationData.actionData || null,
  1249. errors: init.hydrationData && init.hydrationData.errors || initialErrors,
  1250. fetchers: new Map(),
  1251. blockers: new Map()
  1252. }; // -- Stateful internal variables to manage navigations --
  1253. // Current navigation in progress (to be committed in completeNavigation)
  1254. let pendingAction = Action.Pop; // Should the current navigation prevent the scroll reset if scroll cannot
  1255. // be restored?
  1256. let pendingPreventScrollReset = false; // AbortController for the active navigation
  1257. let pendingNavigationController; // We use this to avoid touching history in completeNavigation if a
  1258. // revalidation is entirely uninterrupted
  1259. let isUninterruptedRevalidation = false; // Use this internal flag to force revalidation of all loaders:
  1260. // - submissions (completed or interrupted)
  1261. // - useRevalidate()
  1262. // - X-Remix-Revalidate (from redirect)
  1263. let isRevalidationRequired = false; // Use this internal array to capture routes that require revalidation due
  1264. // to a cancelled deferred on action submission
  1265. let cancelledDeferredRoutes = []; // Use this internal array to capture fetcher loads that were cancelled by an
  1266. // action navigation and require revalidation
  1267. let cancelledFetcherLoads = []; // AbortControllers for any in-flight fetchers
  1268. let fetchControllers = new Map(); // Track loads based on the order in which they started
  1269. let incrementingLoadId = 0; // Track the outstanding pending navigation data load to be compared against
  1270. // the globally incrementing load when a fetcher load lands after a completed
  1271. // navigation
  1272. let pendingNavigationLoadId = -1; // Fetchers that triggered data reloads as a result of their actions
  1273. let fetchReloadIds = new Map(); // Fetchers that triggered redirect navigations from their actions
  1274. let fetchRedirectIds = new Set(); // Most recent href/match for fetcher.load calls for fetchers
  1275. let fetchLoadMatches = new Map(); // Store DeferredData instances for active route matches. When a
  1276. // route loader returns defer() we stick one in here. Then, when a nested
  1277. // promise resolves we update loaderData. If a new navigation starts we
  1278. // cancel active deferreds for eliminated routes.
  1279. let activeDeferreds = new Map(); // Store blocker functions in a separate Map outside of router state since
  1280. // we don't need to update UI state if they change
  1281. let blockerFunctions = new Map(); // Flag to ignore the next history update, so we can revert the URL change on
  1282. // a POP navigation that was blocked by the user without touching router state
  1283. let ignoreNextHistoryUpdate = false; // Initialize the router, all side effects should be kicked off from here.
  1284. // Implemented as a Fluent API for ease of:
  1285. // let router = createRouter(init).initialize();
  1286. function initialize() {
  1287. // If history informs us of a POP navigation, start the navigation but do not update
  1288. // state. We'll update our own state once the navigation completes
  1289. unlistenHistory = init.history.listen(_ref => {
  1290. let {
  1291. action: historyAction,
  1292. location,
  1293. delta
  1294. } = _ref;
  1295. // Ignore this event if it was just us resetting the URL from a
  1296. // blocked POP navigation
  1297. if (ignoreNextHistoryUpdate) {
  1298. ignoreNextHistoryUpdate = false;
  1299. return;
  1300. }
  1301. 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.");
  1302. let blockerKey = shouldBlockNavigation({
  1303. currentLocation: state.location,
  1304. nextLocation: location,
  1305. historyAction
  1306. });
  1307. if (blockerKey && delta != null) {
  1308. // Restore the URL to match the current UI, but don't update router state
  1309. ignoreNextHistoryUpdate = true;
  1310. init.history.go(delta * -1); // Put the blocker into a blocked state
  1311. updateBlocker(blockerKey, {
  1312. state: "blocked",
  1313. location,
  1314. proceed() {
  1315. updateBlocker(blockerKey, {
  1316. state: "proceeding",
  1317. proceed: undefined,
  1318. reset: undefined,
  1319. location
  1320. }); // Re-do the same POP navigation we just blocked
  1321. init.history.go(delta);
  1322. },
  1323. reset() {
  1324. deleteBlocker(blockerKey);
  1325. updateState({
  1326. blockers: new Map(router.state.blockers)
  1327. });
  1328. }
  1329. });
  1330. return;
  1331. }
  1332. return startNavigation(historyAction, location);
  1333. }); // Kick off initial data load if needed. Use Pop to avoid modifying history
  1334. if (!state.initialized) {
  1335. startNavigation(Action.Pop, state.location);
  1336. }
  1337. return router;
  1338. } // Clean up a router and it's side effects
  1339. function dispose() {
  1340. if (unlistenHistory) {
  1341. unlistenHistory();
  1342. }
  1343. subscribers.clear();
  1344. pendingNavigationController && pendingNavigationController.abort();
  1345. state.fetchers.forEach((_, key) => deleteFetcher(key));
  1346. state.blockers.forEach((_, key) => deleteBlocker(key));
  1347. } // Subscribe to state updates for the router
  1348. function subscribe(fn) {
  1349. subscribers.add(fn);
  1350. return () => subscribers.delete(fn);
  1351. } // Update our state and notify the calling context of the change
  1352. function updateState(newState) {
  1353. state = _extends({}, state, newState);
  1354. subscribers.forEach(subscriber => subscriber(state));
  1355. } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
  1356. // and setting state.[historyAction/location/matches] to the new route.
  1357. // - Location is a required param
  1358. // - Navigation will always be set to IDLE_NAVIGATION
  1359. // - Can pass any other state in newState
  1360. function completeNavigation(location, newState) {
  1361. var _location$state, _location$state2;
  1362. // Deduce if we're in a loading/actionReload state:
  1363. // - We have committed actionData in the store
  1364. // - The current navigation was a mutation submission
  1365. // - We're past the submitting state and into the loading state
  1366. // - The location being loaded is not the result of a redirect
  1367. 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;
  1368. let actionData;
  1369. if (newState.actionData) {
  1370. if (Object.keys(newState.actionData).length > 0) {
  1371. actionData = newState.actionData;
  1372. } else {
  1373. // Empty actionData -> clear prior actionData due to an action error
  1374. actionData = null;
  1375. }
  1376. } else if (isActionReload) {
  1377. // Keep the current data if we're wrapping up the action reload
  1378. actionData = state.actionData;
  1379. } else {
  1380. // Clear actionData on any other completed navigations
  1381. actionData = null;
  1382. } // Always preserve any existing loaderData from re-used routes
  1383. let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData; // On a successful navigation we can assume we got through all blockers
  1384. // so we can start fresh
  1385. for (let [key] of blockerFunctions) {
  1386. deleteBlocker(key);
  1387. } // Always respect the user flag. Otherwise don't reset on mutation
  1388. // submission navigations unless they redirect
  1389. let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
  1390. updateState(_extends({}, newState, {
  1391. actionData,
  1392. loaderData,
  1393. historyAction: pendingAction,
  1394. location,
  1395. initialized: true,
  1396. navigation: IDLE_NAVIGATION,
  1397. revalidation: "idle",
  1398. restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
  1399. preventScrollReset,
  1400. blockers: new Map(state.blockers)
  1401. }));
  1402. if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
  1403. init.history.push(location, location.state);
  1404. } else if (pendingAction === Action.Replace) {
  1405. init.history.replace(location, location.state);
  1406. } // Reset stateful navigation vars
  1407. pendingAction = Action.Pop;
  1408. pendingPreventScrollReset = false;
  1409. isUninterruptedRevalidation = false;
  1410. isRevalidationRequired = false;
  1411. cancelledDeferredRoutes = [];
  1412. cancelledFetcherLoads = [];
  1413. } // Trigger a navigation event, which can either be a numerical POP or a PUSH
  1414. // replace with an optional submission
  1415. async function navigate(to, opts) {
  1416. if (typeof to === "number") {
  1417. init.history.go(to);
  1418. return;
  1419. }
  1420. let {
  1421. path,
  1422. submission,
  1423. error
  1424. } = normalizeNavigateOptions(to, opts);
  1425. let currentLocation = state.location;
  1426. let nextLocation = createLocation(state.location, path, opts && opts.state); // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
  1427. // URL from window.location, so we need to encode it here so the behavior
  1428. // remains the same as POP and non-data-router usages. new URL() does all
  1429. // the same encoding we'd get from a history.pushState/window.location read
  1430. // without having to touch history
  1431. nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
  1432. let userReplace = opts && opts.replace != null ? opts.replace : undefined;
  1433. let historyAction = Action.Push;
  1434. if (userReplace === true) {
  1435. historyAction = Action.Replace;
  1436. } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
  1437. // By default on submissions to the current location we REPLACE so that
  1438. // users don't have to double-click the back button to get to the prior
  1439. // location. If the user redirects to a different location from the
  1440. // action/loader this will be ignored and the redirect will be a PUSH
  1441. historyAction = Action.Replace;
  1442. }
  1443. let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
  1444. let blockerKey = shouldBlockNavigation({
  1445. currentLocation,
  1446. nextLocation,
  1447. historyAction
  1448. });
  1449. if (blockerKey) {
  1450. // Put the blocker into a blocked state
  1451. updateBlocker(blockerKey, {
  1452. state: "blocked",
  1453. location: nextLocation,
  1454. proceed() {
  1455. updateBlocker(blockerKey, {
  1456. state: "proceeding",
  1457. proceed: undefined,
  1458. reset: undefined,
  1459. location: nextLocation
  1460. }); // Send the same navigation through
  1461. navigate(to, opts);
  1462. },
  1463. reset() {
  1464. deleteBlocker(blockerKey);
  1465. updateState({
  1466. blockers: new Map(state.blockers)
  1467. });
  1468. }
  1469. });
  1470. return;
  1471. }
  1472. return await startNavigation(historyAction, nextLocation, {
  1473. submission,
  1474. // Send through the formData serialization error if we have one so we can
  1475. // render at the right error boundary after we match routes
  1476. pendingError: error,
  1477. preventScrollReset,
  1478. replace: opts && opts.replace
  1479. });
  1480. } // Revalidate all current loaders. If a navigation is in progress or if this
  1481. // is interrupted by a navigation, allow this to "succeed" by calling all
  1482. // loaders during the next loader round
  1483. function revalidate() {
  1484. interruptActiveLoads();
  1485. updateState({
  1486. revalidation: "loading"
  1487. }); // If we're currently submitting an action, we don't need to start a new
  1488. // navigation, we'll just let the follow up loader execution call all loaders
  1489. if (state.navigation.state === "submitting") {
  1490. return;
  1491. } // If we're currently in an idle state, start a new navigation for the current
  1492. // action/location and mark it as uninterrupted, which will skip the history
  1493. // update in completeNavigation
  1494. if (state.navigation.state === "idle") {
  1495. startNavigation(state.historyAction, state.location, {
  1496. startUninterruptedRevalidation: true
  1497. });
  1498. return;
  1499. } // Otherwise, if we're currently in a loading state, just start a new
  1500. // navigation to the navigation.location but do not trigger an uninterrupted
  1501. // revalidation so that history correctly updates once the navigation completes
  1502. startNavigation(pendingAction || state.historyAction, state.navigation.location, {
  1503. overrideNavigation: state.navigation
  1504. });
  1505. } // Start a navigation to the given action/location. Can optionally provide a
  1506. // overrideNavigation which will override the normalLoad in the case of a redirect
  1507. // navigation
  1508. async function startNavigation(historyAction, location, opts) {
  1509. // Abort any in-progress navigations and start a new one. Unset any ongoing
  1510. // uninterrupted revalidations unless told otherwise, since we want this
  1511. // new navigation to update history normally
  1512. pendingNavigationController && pendingNavigationController.abort();
  1513. pendingNavigationController = null;
  1514. pendingAction = historyAction;
  1515. isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true; // Save the current scroll position every time we start a new navigation,
  1516. // and track whether we should reset scroll on completion
  1517. saveScrollPosition(state.location, state.matches);
  1518. pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
  1519. let loadingNavigation = opts && opts.overrideNavigation;
  1520. let matches = matchRoutes(dataRoutes, location, init.basename); // Short circuit with a 404 on the root error boundary if we match nothing
  1521. if (!matches) {
  1522. let error = getInternalRouterError(404, {
  1523. pathname: location.pathname
  1524. });
  1525. let {
  1526. matches: notFoundMatches,
  1527. route
  1528. } = getShortCircuitMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
  1529. cancelActiveDeferreds();
  1530. completeNavigation(location, {
  1531. matches: notFoundMatches,
  1532. loaderData: {},
  1533. errors: {
  1534. [route.id]: error
  1535. }
  1536. });
  1537. return;
  1538. } // Short circuit if it's only a hash change and not a mutation submission
  1539. // For example, on /page#hash and submit a <Form method="post"> which will
  1540. // default to a navigation to /page
  1541. if (isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
  1542. completeNavigation(location, {
  1543. matches
  1544. });
  1545. return;
  1546. } // Create a controller/Request for this navigation
  1547. pendingNavigationController = new AbortController();
  1548. let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
  1549. let pendingActionData;
  1550. let pendingError;
  1551. if (opts && opts.pendingError) {
  1552. // If we have a pendingError, it means the user attempted a GET submission
  1553. // with binary FormData so assign here and skip to handleLoaders. That
  1554. // way we handle calling loaders above the boundary etc. It's not really
  1555. // different from an actionError in that sense.
  1556. pendingError = {
  1557. [findNearestBoundary(matches).route.id]: opts.pendingError
  1558. };
  1559. } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
  1560. // Call action if we received an action submission
  1561. let actionOutput = await handleAction(request, location, opts.submission, matches, {
  1562. replace: opts.replace
  1563. });
  1564. if (actionOutput.shortCircuited) {
  1565. return;
  1566. }
  1567. pendingActionData = actionOutput.pendingActionData;
  1568. pendingError = actionOutput.pendingActionError;
  1569. let navigation = _extends({
  1570. state: "loading",
  1571. location
  1572. }, opts.submission);
  1573. loadingNavigation = navigation; // Create a GET request for the loaders
  1574. request = new Request(request.url, {
  1575. signal: request.signal
  1576. });
  1577. } // Call loaders
  1578. let {
  1579. shortCircuited,
  1580. loaderData,
  1581. errors
  1582. } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.replace, pendingActionData, pendingError);
  1583. if (shortCircuited) {
  1584. return;
  1585. } // Clean up now that the action/loaders have completed. Don't clean up if
  1586. // we short circuited because pendingNavigationController will have already
  1587. // been assigned to a new controller for the next navigation
  1588. pendingNavigationController = null;
  1589. completeNavigation(location, _extends({
  1590. matches
  1591. }, pendingActionData ? {
  1592. actionData: pendingActionData
  1593. } : {}, {
  1594. loaderData,
  1595. errors
  1596. }));
  1597. } // Call the action matched by the leaf route for this navigation and handle
  1598. // redirects/errors
  1599. async function handleAction(request, location, submission, matches, opts) {
  1600. interruptActiveLoads(); // Put us in a submitting state
  1601. let navigation = _extends({
  1602. state: "submitting",
  1603. location
  1604. }, submission);
  1605. updateState({
  1606. navigation
  1607. }); // Call our action and get the result
  1608. let result;
  1609. let actionMatch = getTargetMatch(matches, location);
  1610. if (!actionMatch.route.action) {
  1611. result = {
  1612. type: ResultType.error,
  1613. error: getInternalRouterError(405, {
  1614. method: request.method,
  1615. pathname: location.pathname,
  1616. routeId: actionMatch.route.id
  1617. })
  1618. };
  1619. } else {
  1620. result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
  1621. if (request.signal.aborted) {
  1622. return {
  1623. shortCircuited: true
  1624. };
  1625. }
  1626. }
  1627. if (isRedirectResult(result)) {
  1628. let replace;
  1629. if (opts && opts.replace != null) {
  1630. replace = opts.replace;
  1631. } else {
  1632. // If the user didn't explicity indicate replace behavior, replace if
  1633. // we redirected to the exact same location we're currently at to avoid
  1634. // double back-buttons
  1635. replace = result.location === state.location.pathname + state.location.search;
  1636. }
  1637. await startRedirectNavigation(state, result, {
  1638. submission,
  1639. replace
  1640. });
  1641. return {
  1642. shortCircuited: true
  1643. };
  1644. }
  1645. if (isErrorResult(result)) {
  1646. // Store off the pending error - we use it to determine which loaders
  1647. // to call and will commit it when we complete the navigation
  1648. let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id); // By default, all submissions are REPLACE navigations, but if the
  1649. // action threw an error that'll be rendered in an errorElement, we fall
  1650. // back to PUSH so that the user can use the back button to get back to
  1651. // the pre-submission form location to try again
  1652. if ((opts && opts.replace) !== true) {
  1653. pendingAction = Action.Push;
  1654. }
  1655. return {
  1656. // Send back an empty object we can use to clear out any prior actionData
  1657. pendingActionData: {},
  1658. pendingActionError: {
  1659. [boundaryMatch.route.id]: result.error
  1660. }
  1661. };
  1662. }
  1663. if (isDeferredResult(result)) {
  1664. throw getInternalRouterError(400, {
  1665. type: "defer-action"
  1666. });
  1667. }
  1668. return {
  1669. pendingActionData: {
  1670. [actionMatch.route.id]: result.data
  1671. }
  1672. };
  1673. } // Call all applicable loaders for the given matches, handling redirects,
  1674. // errors, etc.
  1675. async function handleLoaders(request, location, matches, overrideNavigation, submission, replace, pendingActionData, pendingError) {
  1676. // Figure out the right navigation we want to use for data loading
  1677. let loadingNavigation = overrideNavigation;
  1678. if (!loadingNavigation) {
  1679. let navigation = _extends({
  1680. state: "loading",
  1681. location,
  1682. formMethod: undefined,
  1683. formAction: undefined,
  1684. formEncType: undefined,
  1685. formData: undefined
  1686. }, submission);
  1687. loadingNavigation = navigation;
  1688. } // If this was a redirect from an action we don't have a "submission" but
  1689. // we have it on the loading navigation so use that if available
  1690. let activeSubmission = submission ? submission : loadingNavigation.formMethod && loadingNavigation.formAction && loadingNavigation.formData && loadingNavigation.formEncType ? {
  1691. formMethod: loadingNavigation.formMethod,
  1692. formAction: loadingNavigation.formAction,
  1693. formData: loadingNavigation.formData,
  1694. formEncType: loadingNavigation.formEncType
  1695. } : undefined;
  1696. let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches); // Cancel pending deferreds for no-longer-matched routes or routes we're
  1697. // about to reload. Note that if this is an action reload we would have
  1698. // already cancelled all pending deferreds so this would be a no-op
  1699. cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId)); // Short circuit if we have no loaders to run
  1700. if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
  1701. completeNavigation(location, _extends({
  1702. matches,
  1703. loaderData: {},
  1704. // Commit pending error if we're short circuiting
  1705. errors: pendingError || null
  1706. }, pendingActionData ? {
  1707. actionData: pendingActionData
  1708. } : {}));
  1709. return {
  1710. shortCircuited: true
  1711. };
  1712. } // If this is an uninterrupted revalidation, we remain in our current idle
  1713. // state. If not, we need to switch to our loading state and load data,
  1714. // preserving any new action data or existing action data (in the case of
  1715. // a revalidation interrupting an actionReload)
  1716. if (!isUninterruptedRevalidation) {
  1717. revalidatingFetchers.forEach(rf => {
  1718. let fetcher = state.fetchers.get(rf.key);
  1719. let revalidatingFetcher = {
  1720. state: "loading",
  1721. data: fetcher && fetcher.data,
  1722. formMethod: undefined,
  1723. formAction: undefined,
  1724. formEncType: undefined,
  1725. formData: undefined,
  1726. " _hasFetcherDoneAnything ": true
  1727. };
  1728. state.fetchers.set(rf.key, revalidatingFetcher);
  1729. });
  1730. let actionData = pendingActionData || state.actionData;
  1731. updateState(_extends({
  1732. navigation: loadingNavigation
  1733. }, actionData ? Object.keys(actionData).length === 0 ? {
  1734. actionData: null
  1735. } : {
  1736. actionData
  1737. } : {}, revalidatingFetchers.length > 0 ? {
  1738. fetchers: new Map(state.fetchers)
  1739. } : {}));
  1740. }
  1741. pendingNavigationLoadId = ++incrementingLoadId;
  1742. revalidatingFetchers.forEach(rf => fetchControllers.set(rf.key, pendingNavigationController));
  1743. let {
  1744. results,
  1745. loaderResults,
  1746. fetcherResults
  1747. } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
  1748. if (request.signal.aborted) {
  1749. return {
  1750. shortCircuited: true
  1751. };
  1752. } // Clean up _after_ loaders have completed. Don't clean up if we short
  1753. // circuited because fetchControllers would have been aborted and
  1754. // reassigned to new controllers for the next navigation
  1755. revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key)); // If any loaders returned a redirect Response, start a new REPLACE navigation
  1756. let redirect = findRedirect(results);
  1757. if (redirect) {
  1758. await startRedirectNavigation(state, redirect, {
  1759. replace
  1760. });
  1761. return {
  1762. shortCircuited: true
  1763. };
  1764. } // Process and commit output from loaders
  1765. let {
  1766. loaderData,
  1767. errors
  1768. } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds); // Wire up subscribers to update loaderData as promises settle
  1769. activeDeferreds.forEach((deferredData, routeId) => {
  1770. deferredData.subscribe(aborted => {
  1771. // Note: No need to updateState here since the TrackedPromise on
  1772. // loaderData is stable across resolve/reject
  1773. // Remove this instance if we were aborted or if promises have settled
  1774. if (aborted || deferredData.done) {
  1775. activeDeferreds.delete(routeId);
  1776. }
  1777. });
  1778. });
  1779. markFetchRedirectsDone();
  1780. let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
  1781. return _extends({
  1782. loaderData,
  1783. errors
  1784. }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
  1785. fetchers: new Map(state.fetchers)
  1786. } : {});
  1787. }
  1788. function getFetcher(key) {
  1789. return state.fetchers.get(key) || IDLE_FETCHER;
  1790. } // Trigger a fetcher load/submit for the given fetcher key
  1791. function fetch(key, routeId, href, opts) {
  1792. if (isServer) {
  1793. 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.");
  1794. }
  1795. if (fetchControllers.has(key)) abortFetcher(key);
  1796. let matches = matchRoutes(dataRoutes, href, init.basename);
  1797. if (!matches) {
  1798. setFetcherError(key, routeId, getInternalRouterError(404, {
  1799. pathname: href
  1800. }));
  1801. return;
  1802. }
  1803. let {
  1804. path,
  1805. submission
  1806. } = normalizeNavigateOptions(href, opts, true);
  1807. let match = getTargetMatch(matches, path);
  1808. pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
  1809. if (submission && isMutationMethod(submission.formMethod)) {
  1810. handleFetcherAction(key, routeId, path, match, matches, submission);
  1811. return;
  1812. } // Store off the match so we can call it's shouldRevalidate on subsequent
  1813. // revalidations
  1814. fetchLoadMatches.set(key, {
  1815. routeId,
  1816. path,
  1817. match,
  1818. matches
  1819. });
  1820. handleFetcherLoader(key, routeId, path, match, matches, submission);
  1821. } // Call the action for the matched fetcher.submit(), and then handle redirects,
  1822. // errors, and revalidation
  1823. async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
  1824. interruptActiveLoads();
  1825. fetchLoadMatches.delete(key);
  1826. if (!match.route.action) {
  1827. let error = getInternalRouterError(405, {
  1828. method: submission.formMethod,
  1829. pathname: path,
  1830. routeId: routeId
  1831. });
  1832. setFetcherError(key, routeId, error);
  1833. return;
  1834. } // Put this fetcher into it's submitting state
  1835. let existingFetcher = state.fetchers.get(key);
  1836. let fetcher = _extends({
  1837. state: "submitting"
  1838. }, submission, {
  1839. data: existingFetcher && existingFetcher.data,
  1840. " _hasFetcherDoneAnything ": true
  1841. });
  1842. state.fetchers.set(key, fetcher);
  1843. updateState({
  1844. fetchers: new Map(state.fetchers)
  1845. }); // Call the action for the fetcher
  1846. let abortController = new AbortController();
  1847. let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
  1848. fetchControllers.set(key, abortController);
  1849. let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
  1850. if (fetchRequest.signal.aborted) {
  1851. // We can delete this so long as we weren't aborted by ou our own fetcher
  1852. // re-submit which would have put _new_ controller is in fetchControllers
  1853. if (fetchControllers.get(key) === abortController) {
  1854. fetchControllers.delete(key);
  1855. }
  1856. return;
  1857. }
  1858. if (isRedirectResult(actionResult)) {
  1859. fetchControllers.delete(key);
  1860. fetchRedirectIds.add(key);
  1861. let loadingFetcher = _extends({
  1862. state: "loading"
  1863. }, submission, {
  1864. data: undefined,
  1865. " _hasFetcherDoneAnything ": true
  1866. });
  1867. state.fetchers.set(key, loadingFetcher);
  1868. updateState({
  1869. fetchers: new Map(state.fetchers)
  1870. });
  1871. return startRedirectNavigation(state, actionResult, {
  1872. isFetchActionRedirect: true
  1873. });
  1874. } // Process any non-redirect errors thrown
  1875. if (isErrorResult(actionResult)) {
  1876. setFetcherError(key, routeId, actionResult.error);
  1877. return;
  1878. }
  1879. if (isDeferredResult(actionResult)) {
  1880. throw getInternalRouterError(400, {
  1881. type: "defer-action"
  1882. });
  1883. } // Start the data load for current matches, or the next location if we're
  1884. // in the middle of a navigation
  1885. let nextLocation = state.navigation.location || state.location;
  1886. let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
  1887. let matches = state.navigation.state !== "idle" ? matchRoutes(dataRoutes, state.navigation.location, init.basename) : state.matches;
  1888. invariant(matches, "Didn't find any matches after fetcher action");
  1889. let loadId = ++incrementingLoadId;
  1890. fetchReloadIds.set(key, loadId);
  1891. let loadFetcher = _extends({
  1892. state: "loading",
  1893. data: actionResult.data
  1894. }, submission, {
  1895. " _hasFetcherDoneAnything ": true
  1896. });
  1897. state.fetchers.set(key, loadFetcher);
  1898. let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, {
  1899. [match.route.id]: actionResult.data
  1900. }, undefined, // No need to send through errors since we short circuit above
  1901. fetchLoadMatches); // Put all revalidating fetchers into the loading state, except for the
  1902. // current fetcher which we want to keep in it's current loading state which
  1903. // contains it's action submission info + action data
  1904. revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
  1905. let staleKey = rf.key;
  1906. let existingFetcher = state.fetchers.get(staleKey);
  1907. let revalidatingFetcher = {
  1908. state: "loading",
  1909. data: existingFetcher && existingFetcher.data,
  1910. formMethod: undefined,
  1911. formAction: undefined,
  1912. formEncType: undefined,
  1913. formData: undefined,
  1914. " _hasFetcherDoneAnything ": true
  1915. };
  1916. state.fetchers.set(staleKey, revalidatingFetcher);
  1917. fetchControllers.set(staleKey, abortController);
  1918. });
  1919. updateState({
  1920. fetchers: new Map(state.fetchers)
  1921. });
  1922. let {
  1923. results,
  1924. loaderResults,
  1925. fetcherResults
  1926. } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
  1927. if (abortController.signal.aborted) {
  1928. return;
  1929. }
  1930. fetchReloadIds.delete(key);
  1931. fetchControllers.delete(key);
  1932. revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
  1933. let redirect = findRedirect(results);
  1934. if (redirect) {
  1935. return startRedirectNavigation(state, redirect);
  1936. } // Process and commit output from loaders
  1937. let {
  1938. loaderData,
  1939. errors
  1940. } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
  1941. let doneFetcher = {
  1942. state: "idle",
  1943. data: actionResult.data,
  1944. formMethod: undefined,
  1945. formAction: undefined,
  1946. formEncType: undefined,
  1947. formData: undefined,
  1948. " _hasFetcherDoneAnything ": true
  1949. };
  1950. state.fetchers.set(key, doneFetcher);
  1951. let didAbortFetchLoads = abortStaleFetchLoads(loadId); // If we are currently in a navigation loading state and this fetcher is
  1952. // more recent than the navigation, we want the newer data so abort the
  1953. // navigation and complete it with the fetcher data
  1954. if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
  1955. invariant(pendingAction, "Expected pending action");
  1956. pendingNavigationController && pendingNavigationController.abort();
  1957. completeNavigation(state.navigation.location, {
  1958. matches,
  1959. loaderData,
  1960. errors,
  1961. fetchers: new Map(state.fetchers)
  1962. });
  1963. } else {
  1964. // otherwise just update with the fetcher data, preserving any existing
  1965. // loaderData for loaders that did not need to reload. We have to
  1966. // manually merge here since we aren't going through completeNavigation
  1967. updateState(_extends({
  1968. errors,
  1969. loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
  1970. }, didAbortFetchLoads ? {
  1971. fetchers: new Map(state.fetchers)
  1972. } : {}));
  1973. isRevalidationRequired = false;
  1974. }
  1975. } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
  1976. async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
  1977. let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
  1978. let loadingFetcher = _extends({
  1979. state: "loading",
  1980. formMethod: undefined,
  1981. formAction: undefined,
  1982. formEncType: undefined,
  1983. formData: undefined
  1984. }, submission, {
  1985. data: existingFetcher && existingFetcher.data,
  1986. " _hasFetcherDoneAnything ": true
  1987. });
  1988. state.fetchers.set(key, loadingFetcher);
  1989. updateState({
  1990. fetchers: new Map(state.fetchers)
  1991. }); // Call the loader for this fetcher route match
  1992. let abortController = new AbortController();
  1993. let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
  1994. fetchControllers.set(key, abortController);
  1995. let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
  1996. // as a normal load. resolveDeferredData will return undefined if this
  1997. // fetcher gets aborted, so we just leave result untouched and short circuit
  1998. // below if that happens
  1999. if (isDeferredResult(result)) {
  2000. result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
  2001. } // We can delete this so long as we weren't aborted by ou our own fetcher
  2002. // re-load which would have put _new_ controller is in fetchControllers
  2003. if (fetchControllers.get(key) === abortController) {
  2004. fetchControllers.delete(key);
  2005. }
  2006. if (fetchRequest.signal.aborted) {
  2007. return;
  2008. } // If the loader threw a redirect Response, start a new REPLACE navigation
  2009. if (isRedirectResult(result)) {
  2010. await startRedirectNavigation(state, result);
  2011. return;
  2012. } // Process any non-redirect errors thrown
  2013. if (isErrorResult(result)) {
  2014. let boundaryMatch = findNearestBoundary(state.matches, routeId);
  2015. state.fetchers.delete(key); // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
  2016. // do we need to behave any differently with our non-redirect errors?
  2017. // What if it was a non-redirect Response?
  2018. updateState({
  2019. fetchers: new Map(state.fetchers),
  2020. errors: {
  2021. [boundaryMatch.route.id]: result.error
  2022. }
  2023. });
  2024. return;
  2025. }
  2026. invariant(!isDeferredResult(result), "Unhandled fetcher deferred data"); // Put the fetcher back into an idle state
  2027. let doneFetcher = {
  2028. state: "idle",
  2029. data: result.data,
  2030. formMethod: undefined,
  2031. formAction: undefined,
  2032. formEncType: undefined,
  2033. formData: undefined,
  2034. " _hasFetcherDoneAnything ": true
  2035. };
  2036. state.fetchers.set(key, doneFetcher);
  2037. updateState({
  2038. fetchers: new Map(state.fetchers)
  2039. });
  2040. }
  2041. /**
  2042. * Utility function to handle redirects returned from an action or loader.
  2043. * Normally, a redirect "replaces" the navigation that triggered it. So, for
  2044. * example:
  2045. *
  2046. * - user is on /a
  2047. * - user clicks a link to /b
  2048. * - loader for /b redirects to /c
  2049. *
  2050. * In a non-JS app the browser would track the in-flight navigation to /b and
  2051. * then replace it with /c when it encountered the redirect response. In
  2052. * the end it would only ever update the URL bar with /c.
  2053. *
  2054. * In client-side routing using pushState/replaceState, we aim to emulate
  2055. * this behavior and we also do not update history until the end of the
  2056. * navigation (including processed redirects). This means that we never
  2057. * actually touch history until we've processed redirects, so we just use
  2058. * the history action from the original navigation (PUSH or REPLACE).
  2059. */
  2060. async function startRedirectNavigation(state, redirect, _temp) {
  2061. var _window;
  2062. let {
  2063. submission,
  2064. replace,
  2065. isFetchActionRedirect
  2066. } = _temp === void 0 ? {} : _temp;
  2067. if (redirect.revalidate) {
  2068. isRevalidationRequired = true;
  2069. }
  2070. let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2
  2071. _extends({
  2072. _isRedirect: true
  2073. }, isFetchActionRedirect ? {
  2074. _isFetchActionRedirect: true
  2075. } : {}));
  2076. invariant(redirectLocation, "Expected a location on the redirect navigation"); // Check if this an absolute external redirect that goes to a new origin
  2077. if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser && typeof ((_window = window) == null ? void 0 : _window.location) !== "undefined") {
  2078. let newOrigin = init.history.createURL(redirect.location).origin;
  2079. if (window.location.origin !== newOrigin) {
  2080. if (replace) {
  2081. window.location.replace(redirect.location);
  2082. } else {
  2083. window.location.assign(redirect.location);
  2084. }
  2085. return;
  2086. }
  2087. } // There's no need to abort on redirects, since we don't detect the
  2088. // redirect until the action/loaders have settled
  2089. pendingNavigationController = null;
  2090. let redirectHistoryAction = replace === true ? Action.Replace : Action.Push; // Use the incoming submission if provided, fallback on the active one in
  2091. // state.navigation
  2092. let {
  2093. formMethod,
  2094. formAction,
  2095. formEncType,
  2096. formData
  2097. } = state.navigation;
  2098. if (!submission && formMethod && formAction && formData && formEncType) {
  2099. submission = {
  2100. formMethod,
  2101. formAction,
  2102. formEncType,
  2103. formData
  2104. };
  2105. } // If this was a 307/308 submission we want to preserve the HTTP method and
  2106. // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
  2107. // redirected location
  2108. if (redirectPreserveMethodStatusCodes.has(redirect.status) && submission && isMutationMethod(submission.formMethod)) {
  2109. await startNavigation(redirectHistoryAction, redirectLocation, {
  2110. submission: _extends({}, submission, {
  2111. formAction: redirect.location
  2112. }),
  2113. // Preserve this flag across redirects
  2114. preventScrollReset: pendingPreventScrollReset
  2115. });
  2116. } else {
  2117. // Otherwise, we kick off a new loading navigation, preserving the
  2118. // submission info for the duration of this navigation
  2119. await startNavigation(redirectHistoryAction, redirectLocation, {
  2120. overrideNavigation: {
  2121. state: "loading",
  2122. location: redirectLocation,
  2123. formMethod: submission ? submission.formMethod : undefined,
  2124. formAction: submission ? submission.formAction : undefined,
  2125. formEncType: submission ? submission.formEncType : undefined,
  2126. formData: submission ? submission.formData : undefined
  2127. },
  2128. // Preserve this flag across redirects
  2129. preventScrollReset: pendingPreventScrollReset
  2130. });
  2131. }
  2132. }
  2133. async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
  2134. // Call all navigation loaders and revalidating fetcher loaders in parallel,
  2135. // then slice off the results into separate arrays so we can handle them
  2136. // accordingly
  2137. let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename))]);
  2138. let loaderResults = results.slice(0, matchesToLoad.length);
  2139. let fetcherResults = results.slice(matchesToLoad.length);
  2140. await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
  2141. return {
  2142. results,
  2143. loaderResults,
  2144. fetcherResults
  2145. };
  2146. }
  2147. function interruptActiveLoads() {
  2148. // Every interruption triggers a revalidation
  2149. isRevalidationRequired = true; // Cancel pending route-level deferreds and mark cancelled routes for
  2150. // revalidation
  2151. cancelledDeferredRoutes.push(...cancelActiveDeferreds()); // Abort in-flight fetcher loads
  2152. fetchLoadMatches.forEach((_, key) => {
  2153. if (fetchControllers.has(key)) {
  2154. cancelledFetcherLoads.push(key);
  2155. abortFetcher(key);
  2156. }
  2157. });
  2158. }
  2159. function setFetcherError(key, routeId, error) {
  2160. let boundaryMatch = findNearestBoundary(state.matches, routeId);
  2161. deleteFetcher(key);
  2162. updateState({
  2163. errors: {
  2164. [boundaryMatch.route.id]: error
  2165. },
  2166. fetchers: new Map(state.fetchers)
  2167. });
  2168. }
  2169. function deleteFetcher(key) {
  2170. if (fetchControllers.has(key)) abortFetcher(key);
  2171. fetchLoadMatches.delete(key);
  2172. fetchReloadIds.delete(key);
  2173. fetchRedirectIds.delete(key);
  2174. state.fetchers.delete(key);
  2175. }
  2176. function abortFetcher(key) {
  2177. let controller = fetchControllers.get(key);
  2178. invariant(controller, "Expected fetch controller: " + key);
  2179. controller.abort();
  2180. fetchControllers.delete(key);
  2181. }
  2182. function markFetchersDone(keys) {
  2183. for (let key of keys) {
  2184. let fetcher = getFetcher(key);
  2185. let doneFetcher = {
  2186. state: "idle",
  2187. data: fetcher.data,
  2188. formMethod: undefined,
  2189. formAction: undefined,
  2190. formEncType: undefined,
  2191. formData: undefined,
  2192. " _hasFetcherDoneAnything ": true
  2193. };
  2194. state.fetchers.set(key, doneFetcher);
  2195. }
  2196. }
  2197. function markFetchRedirectsDone() {
  2198. let doneKeys = [];
  2199. for (let key of fetchRedirectIds) {
  2200. let fetcher = state.fetchers.get(key);
  2201. invariant(fetcher, "Expected fetcher: " + key);
  2202. if (fetcher.state === "loading") {
  2203. fetchRedirectIds.delete(key);
  2204. doneKeys.push(key);
  2205. }
  2206. }
  2207. markFetchersDone(doneKeys);
  2208. }
  2209. function abortStaleFetchLoads(landedId) {
  2210. let yeetedKeys = [];
  2211. for (let [key, id] of fetchReloadIds) {
  2212. if (id < landedId) {
  2213. let fetcher = state.fetchers.get(key);
  2214. invariant(fetcher, "Expected fetcher: " + key);
  2215. if (fetcher.state === "loading") {
  2216. abortFetcher(key);
  2217. fetchReloadIds.delete(key);
  2218. yeetedKeys.push(key);
  2219. }
  2220. }
  2221. }
  2222. markFetchersDone(yeetedKeys);
  2223. return yeetedKeys.length > 0;
  2224. }
  2225. function getBlocker(key, fn) {
  2226. let blocker = state.blockers.get(key) || IDLE_BLOCKER;
  2227. if (blockerFunctions.get(key) !== fn) {
  2228. blockerFunctions.set(key, fn);
  2229. }
  2230. return blocker;
  2231. }
  2232. function deleteBlocker(key) {
  2233. state.blockers.delete(key);
  2234. blockerFunctions.delete(key);
  2235. } // Utility function to update blockers, ensuring valid state transitions
  2236. function updateBlocker(key, newBlocker) {
  2237. let blocker = state.blockers.get(key) || IDLE_BLOCKER; // Poor mans state machine :)
  2238. // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
  2239. 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);
  2240. state.blockers.set(key, newBlocker);
  2241. updateState({
  2242. blockers: new Map(state.blockers)
  2243. });
  2244. }
  2245. function shouldBlockNavigation(_ref2) {
  2246. let {
  2247. currentLocation,
  2248. nextLocation,
  2249. historyAction
  2250. } = _ref2;
  2251. if (blockerFunctions.size === 0) {
  2252. return;
  2253. } // We ony support a single active blocker at the moment since we don't have
  2254. // any compelling use cases for multi-blocker yet
  2255. if (blockerFunctions.size > 1) {
  2256. warning(false, "A router only supports one blocker at a time");
  2257. }
  2258. let entries = Array.from(blockerFunctions.entries());
  2259. let [blockerKey, blockerFunction] = entries[entries.length - 1];
  2260. let blocker = state.blockers.get(blockerKey);
  2261. if (blocker && blocker.state === "proceeding") {
  2262. // If the blocker is currently proceeding, we don't need to re-check
  2263. // it and can let this navigation continue
  2264. return;
  2265. } // At this point, we know we're unblocked/blocked so we need to check the
  2266. // user-provided blocker function
  2267. if (blockerFunction({
  2268. currentLocation,
  2269. nextLocation,
  2270. historyAction
  2271. })) {
  2272. return blockerKey;
  2273. }
  2274. }
  2275. function cancelActiveDeferreds(predicate) {
  2276. let cancelledRouteIds = [];
  2277. activeDeferreds.forEach((dfd, routeId) => {
  2278. if (!predicate || predicate(routeId)) {
  2279. // Cancel the deferred - but do not remove from activeDeferreds here -
  2280. // we rely on the subscribers to do that so our tests can assert proper
  2281. // cleanup via _internalActiveDeferreds
  2282. dfd.cancel();
  2283. cancelledRouteIds.push(routeId);
  2284. activeDeferreds.delete(routeId);
  2285. }
  2286. });
  2287. return cancelledRouteIds;
  2288. } // Opt in to capturing and reporting scroll positions during navigations,
  2289. // used by the <ScrollRestoration> component
  2290. function enableScrollRestoration(positions, getPosition, getKey) {
  2291. savedScrollPositions = positions;
  2292. getScrollPosition = getPosition;
  2293. getScrollRestorationKey = getKey || (location => location.key); // Perform initial hydration scroll restoration, since we miss the boat on
  2294. // the initial updateState() because we've not yet rendered <ScrollRestoration/>
  2295. // and therefore have no savedScrollPositions available
  2296. if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
  2297. initialScrollRestored = true;
  2298. let y = getSavedScrollPosition(state.location, state.matches);
  2299. if (y != null) {
  2300. updateState({
  2301. restoreScrollPosition: y
  2302. });
  2303. }
  2304. }
  2305. return () => {
  2306. savedScrollPositions = null;
  2307. getScrollPosition = null;
  2308. getScrollRestorationKey = null;
  2309. };
  2310. }
  2311. function saveScrollPosition(location, matches) {
  2312. if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
  2313. let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
  2314. let key = getScrollRestorationKey(location, userMatches) || location.key;
  2315. savedScrollPositions[key] = getScrollPosition();
  2316. }
  2317. }
  2318. function getSavedScrollPosition(location, matches) {
  2319. if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
  2320. let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
  2321. let key = getScrollRestorationKey(location, userMatches) || location.key;
  2322. let y = savedScrollPositions[key];
  2323. if (typeof y === "number") {
  2324. return y;
  2325. }
  2326. }
  2327. return null;
  2328. }
  2329. router = {
  2330. get basename() {
  2331. return init.basename;
  2332. },
  2333. get state() {
  2334. return state;
  2335. },
  2336. get routes() {
  2337. return dataRoutes;
  2338. },
  2339. initialize,
  2340. subscribe,
  2341. enableScrollRestoration,
  2342. navigate,
  2343. fetch,
  2344. revalidate,
  2345. // Passthrough to history-aware createHref used by useHref so we get proper
  2346. // hash-aware URLs in DOM paths
  2347. createHref: to => init.history.createHref(to),
  2348. encodeLocation: to => init.history.encodeLocation(to),
  2349. getFetcher,
  2350. deleteFetcher,
  2351. dispose,
  2352. getBlocker,
  2353. deleteBlocker,
  2354. _internalFetchControllers: fetchControllers,
  2355. _internalActiveDeferreds: activeDeferreds
  2356. };
  2357. return router;
  2358. } //#endregion
  2359. ////////////////////////////////////////////////////////////////////////////////
  2360. //#region createStaticHandler
  2361. ////////////////////////////////////////////////////////////////////////////////
  2362. const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
  2363. function createStaticHandler(routes, opts) {
  2364. invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
  2365. let dataRoutes = convertRoutesToDataRoutes(routes);
  2366. let basename = (opts ? opts.basename : null) || "/";
  2367. /**
  2368. * The query() method is intended for document requests, in which we want to
  2369. * call an optional action and potentially multiple loaders for all nested
  2370. * routes. It returns a StaticHandlerContext object, which is very similar
  2371. * to the router state (location, loaderData, actionData, errors, etc.) and
  2372. * also adds SSR-specific information such as the statusCode and headers
  2373. * from action/loaders Responses.
  2374. *
  2375. * It _should_ never throw and should report all errors through the
  2376. * returned context.errors object, properly associating errors to their error
  2377. * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
  2378. * used to emulate React error boundaries during SSr by performing a second
  2379. * pass only down to the boundaryId.
  2380. *
  2381. * The one exception where we do not return a StaticHandlerContext is when a
  2382. * redirect response is returned or thrown from any action/loader. We
  2383. * propagate that out and return the raw Response so the HTTP server can
  2384. * return it directly.
  2385. */
  2386. async function query(request, _temp2) {
  2387. let {
  2388. requestContext
  2389. } = _temp2 === void 0 ? {} : _temp2;
  2390. let url = new URL(request.url);
  2391. let method = request.method.toLowerCase();
  2392. let location = createLocation("", createPath(url), null, "default");
  2393. let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
  2394. if (!isValidMethod(method) && method !== "head") {
  2395. let error = getInternalRouterError(405, {
  2396. method
  2397. });
  2398. let {
  2399. matches: methodNotAllowedMatches,
  2400. route
  2401. } = getShortCircuitMatches(dataRoutes);
  2402. return {
  2403. basename,
  2404. location,
  2405. matches: methodNotAllowedMatches,
  2406. loaderData: {},
  2407. actionData: null,
  2408. errors: {
  2409. [route.id]: error
  2410. },
  2411. statusCode: error.status,
  2412. loaderHeaders: {},
  2413. actionHeaders: {},
  2414. activeDeferreds: null
  2415. };
  2416. } else if (!matches) {
  2417. let error = getInternalRouterError(404, {
  2418. pathname: location.pathname
  2419. });
  2420. let {
  2421. matches: notFoundMatches,
  2422. route
  2423. } = getShortCircuitMatches(dataRoutes);
  2424. return {
  2425. basename,
  2426. location,
  2427. matches: notFoundMatches,
  2428. loaderData: {},
  2429. actionData: null,
  2430. errors: {
  2431. [route.id]: error
  2432. },
  2433. statusCode: error.status,
  2434. loaderHeaders: {},
  2435. actionHeaders: {},
  2436. activeDeferreds: null
  2437. };
  2438. }
  2439. let result = await queryImpl(request, location, matches, requestContext);
  2440. if (isResponse(result)) {
  2441. return result;
  2442. } // When returning StaticHandlerContext, we patch back in the location here
  2443. // since we need it for React Context. But this helps keep our submit and
  2444. // loadRouteData operating on a Request instead of a Location
  2445. return _extends({
  2446. location,
  2447. basename
  2448. }, result);
  2449. }
  2450. /**
  2451. * The queryRoute() method is intended for targeted route requests, either
  2452. * for fetch ?_data requests or resource route requests. In this case, we
  2453. * are only ever calling a single action or loader, and we are returning the
  2454. * returned value directly. In most cases, this will be a Response returned
  2455. * from the action/loader, but it may be a primitive or other value as well -
  2456. * and in such cases the calling context should handle that accordingly.
  2457. *
  2458. * We do respect the throw/return differentiation, so if an action/loader
  2459. * throws, then this method will throw the value. This is important so we
  2460. * can do proper boundary identification in Remix where a thrown Response
  2461. * must go to the Catch Boundary but a returned Response is happy-path.
  2462. *
  2463. * One thing to note is that any Router-initiated Errors that make sense
  2464. * to associate with a status code will be thrown as an ErrorResponse
  2465. * instance which include the raw Error, such that the calling context can
  2466. * serialize the error as they see fit while including the proper response
  2467. * code. Examples here are 404 and 405 errors that occur prior to reaching
  2468. * any user-defined loaders.
  2469. */
  2470. async function queryRoute(request, _temp3) {
  2471. let {
  2472. routeId,
  2473. requestContext
  2474. } = _temp3 === void 0 ? {} : _temp3;
  2475. let url = new URL(request.url);
  2476. let method = request.method.toLowerCase();
  2477. let location = createLocation("", createPath(url), null, "default");
  2478. let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
  2479. if (!isValidMethod(method) && method !== "head" && method !== "options") {
  2480. throw getInternalRouterError(405, {
  2481. method
  2482. });
  2483. } else if (!matches) {
  2484. throw getInternalRouterError(404, {
  2485. pathname: location.pathname
  2486. });
  2487. }
  2488. let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
  2489. if (routeId && !match) {
  2490. throw getInternalRouterError(403, {
  2491. pathname: location.pathname,
  2492. routeId
  2493. });
  2494. } else if (!match) {
  2495. // This should never hit I don't think?
  2496. throw getInternalRouterError(404, {
  2497. pathname: location.pathname
  2498. });
  2499. }
  2500. let result = await queryImpl(request, location, matches, requestContext, match);
  2501. if (isResponse(result)) {
  2502. return result;
  2503. }
  2504. let error = result.errors ? Object.values(result.errors)[0] : undefined;
  2505. if (error !== undefined) {
  2506. // If we got back result.errors, that means the loader/action threw
  2507. // _something_ that wasn't a Response, but it's not guaranteed/required
  2508. // to be an `instanceof Error` either, so we have to use throw here to
  2509. // preserve the "error" state outside of queryImpl.
  2510. throw error;
  2511. } // Pick off the right state value to return
  2512. if (result.actionData) {
  2513. return Object.values(result.actionData)[0];
  2514. }
  2515. if (result.loaderData) {
  2516. var _result$activeDeferre;
  2517. let data = Object.values(result.loaderData)[0];
  2518. if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
  2519. data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
  2520. }
  2521. return data;
  2522. }
  2523. return undefined;
  2524. }
  2525. async function queryImpl(request, location, matches, requestContext, routeMatch) {
  2526. invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
  2527. try {
  2528. if (isMutationMethod(request.method.toLowerCase())) {
  2529. let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
  2530. return result;
  2531. }
  2532. let result = await loadRouteData(request, matches, requestContext, routeMatch);
  2533. return isResponse(result) ? result : _extends({}, result, {
  2534. actionData: null,
  2535. actionHeaders: {}
  2536. });
  2537. } catch (e) {
  2538. // If the user threw/returned a Response in callLoaderOrAction, we throw
  2539. // it to bail out and then return or throw here based on whether the user
  2540. // returned or threw
  2541. if (isQueryRouteResponse(e)) {
  2542. if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
  2543. throw e.response;
  2544. }
  2545. return e.response;
  2546. } // Redirects are always returned since they don't propagate to catch
  2547. // boundaries
  2548. if (isRedirectResponse(e)) {
  2549. return e;
  2550. }
  2551. throw e;
  2552. }
  2553. }
  2554. async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
  2555. let result;
  2556. if (!actionMatch.route.action) {
  2557. let error = getInternalRouterError(405, {
  2558. method: request.method,
  2559. pathname: new URL(request.url).pathname,
  2560. routeId: actionMatch.route.id
  2561. });
  2562. if (isRouteRequest) {
  2563. throw error;
  2564. }
  2565. result = {
  2566. type: ResultType.error,
  2567. error
  2568. };
  2569. } else {
  2570. result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
  2571. if (request.signal.aborted) {
  2572. let method = isRouteRequest ? "queryRoute" : "query";
  2573. throw new Error(method + "() call aborted");
  2574. }
  2575. }
  2576. if (isRedirectResult(result)) {
  2577. // Uhhhh - this should never happen, we should always throw these from
  2578. // callLoaderOrAction, but the type narrowing here keeps TS happy and we
  2579. // can get back on the "throw all redirect responses" train here should
  2580. // this ever happen :/
  2581. throw new Response(null, {
  2582. status: result.status,
  2583. headers: {
  2584. Location: result.location
  2585. }
  2586. });
  2587. }
  2588. if (isDeferredResult(result)) {
  2589. let error = getInternalRouterError(400, {
  2590. type: "defer-action"
  2591. });
  2592. if (isRouteRequest) {
  2593. throw error;
  2594. }
  2595. result = {
  2596. type: ResultType.error,
  2597. error
  2598. };
  2599. }
  2600. if (isRouteRequest) {
  2601. // Note: This should only be non-Response values if we get here, since
  2602. // isRouteRequest should throw any Response received in callLoaderOrAction
  2603. if (isErrorResult(result)) {
  2604. throw result.error;
  2605. }
  2606. return {
  2607. matches: [actionMatch],
  2608. loaderData: {},
  2609. actionData: {
  2610. [actionMatch.route.id]: result.data
  2611. },
  2612. errors: null,
  2613. // Note: statusCode + headers are unused here since queryRoute will
  2614. // return the raw Response or value
  2615. statusCode: 200,
  2616. loaderHeaders: {},
  2617. actionHeaders: {},
  2618. activeDeferreds: null
  2619. };
  2620. }
  2621. if (isErrorResult(result)) {
  2622. // Store off the pending error - we use it to determine which loaders
  2623. // to call and will commit it when we complete the navigation
  2624. let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
  2625. let context = await loadRouteData(request, matches, requestContext, undefined, {
  2626. [boundaryMatch.route.id]: result.error
  2627. }); // action status codes take precedence over loader status codes
  2628. return _extends({}, context, {
  2629. statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
  2630. actionData: null,
  2631. actionHeaders: _extends({}, result.headers ? {
  2632. [actionMatch.route.id]: result.headers
  2633. } : {})
  2634. });
  2635. } // Create a GET request for the loaders
  2636. let loaderRequest = new Request(request.url, {
  2637. headers: request.headers,
  2638. redirect: request.redirect,
  2639. signal: request.signal
  2640. });
  2641. let context = await loadRouteData(loaderRequest, matches, requestContext);
  2642. return _extends({}, context, result.statusCode ? {
  2643. statusCode: result.statusCode
  2644. } : {}, {
  2645. actionData: {
  2646. [actionMatch.route.id]: result.data
  2647. },
  2648. actionHeaders: _extends({}, result.headers ? {
  2649. [actionMatch.route.id]: result.headers
  2650. } : {})
  2651. });
  2652. }
  2653. async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
  2654. let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
  2655. if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
  2656. throw getInternalRouterError(400, {
  2657. method: request.method,
  2658. pathname: new URL(request.url).pathname,
  2659. routeId: routeMatch == null ? void 0 : routeMatch.route.id
  2660. });
  2661. }
  2662. let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
  2663. let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
  2664. if (matchesToLoad.length === 0) {
  2665. return {
  2666. matches,
  2667. // Add a null for all matched routes for proper revalidation on the client
  2668. loaderData: matches.reduce((acc, m) => Object.assign(acc, {
  2669. [m.route.id]: null
  2670. }), {}),
  2671. errors: pendingActionError || null,
  2672. statusCode: 200,
  2673. loaderHeaders: {},
  2674. activeDeferreds: null
  2675. };
  2676. }
  2677. let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
  2678. if (request.signal.aborted) {
  2679. let method = isRouteRequest ? "queryRoute" : "query";
  2680. throw new Error(method + "() call aborted");
  2681. } // Process and commit output from loaders
  2682. let activeDeferreds = new Map();
  2683. let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds); // Add a null for any non-loader matches for proper revalidation on the client
  2684. let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
  2685. matches.forEach(match => {
  2686. if (!executedLoaders.has(match.route.id)) {
  2687. context.loaderData[match.route.id] = null;
  2688. }
  2689. });
  2690. return _extends({}, context, {
  2691. matches,
  2692. activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
  2693. });
  2694. }
  2695. return {
  2696. dataRoutes,
  2697. query,
  2698. queryRoute
  2699. };
  2700. } //#endregion
  2701. ////////////////////////////////////////////////////////////////////////////////
  2702. //#region Helpers
  2703. ////////////////////////////////////////////////////////////////////////////////
  2704. /**
  2705. * Given an existing StaticHandlerContext and an error thrown at render time,
  2706. * provide an updated StaticHandlerContext suitable for a second SSR render
  2707. */
  2708. function getStaticContextFromError(routes, context, error) {
  2709. let newContext = _extends({}, context, {
  2710. statusCode: 500,
  2711. errors: {
  2712. [context._deepestRenderedBoundaryId || routes[0].id]: error
  2713. }
  2714. });
  2715. return newContext;
  2716. }
  2717. function isSubmissionNavigation(opts) {
  2718. return opts != null && "formData" in opts;
  2719. } // Normalize navigation options by converting formMethod=GET formData objects to
  2720. // URLSearchParams so they behave identically to links with query params
  2721. function normalizeNavigateOptions(to, opts, isFetcher) {
  2722. if (isFetcher === void 0) {
  2723. isFetcher = false;
  2724. }
  2725. let path = typeof to === "string" ? to : createPath(to); // Return location verbatim on non-submission navigations
  2726. if (!opts || !isSubmissionNavigation(opts)) {
  2727. return {
  2728. path
  2729. };
  2730. }
  2731. if (opts.formMethod && !isValidMethod(opts.formMethod)) {
  2732. return {
  2733. path,
  2734. error: getInternalRouterError(405, {
  2735. method: opts.formMethod
  2736. })
  2737. };
  2738. } // Create a Submission on non-GET navigations
  2739. let submission;
  2740. if (opts.formData) {
  2741. submission = {
  2742. formMethod: opts.formMethod || "get",
  2743. formAction: stripHashFromPath(path),
  2744. formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
  2745. formData: opts.formData
  2746. };
  2747. if (isMutationMethod(submission.formMethod)) {
  2748. return {
  2749. path,
  2750. submission
  2751. };
  2752. }
  2753. } // Flatten submission onto URLSearchParams for GET submissions
  2754. let parsedPath = parsePath(path);
  2755. let searchParams = convertFormDataToSearchParams(opts.formData); // Since fetcher GET submissions only run a single loader (as opposed to
  2756. // navigation GET submissions which run all loaders), we need to preserve
  2757. // any incoming ?index params
  2758. if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
  2759. searchParams.append("index", "");
  2760. }
  2761. parsedPath.search = "?" + searchParams;
  2762. return {
  2763. path: createPath(parsedPath),
  2764. submission
  2765. };
  2766. } // Filter out all routes below any caught error as they aren't going to
  2767. // render so we don't need to load them
  2768. function getLoaderMatchesUntilBoundary(matches, boundaryId) {
  2769. let boundaryMatches = matches;
  2770. if (boundaryId) {
  2771. let index = matches.findIndex(m => m.route.id === boundaryId);
  2772. if (index >= 0) {
  2773. boundaryMatches = matches.slice(0, index);
  2774. }
  2775. }
  2776. return boundaryMatches;
  2777. }
  2778. function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError, fetchLoadMatches) {
  2779. let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
  2780. let currentUrl = history.createURL(state.location);
  2781. let nextUrl = history.createURL(location);
  2782. let defaultShouldRevalidate = // Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
  2783. isRevalidationRequired || // Clicked the same link, resubmitted a GET form
  2784. currentUrl.toString() === nextUrl.toString() || // Search params affect all loaders
  2785. currentUrl.search !== nextUrl.search; // Pick navigation matches that are net-new or qualify for revalidation
  2786. let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
  2787. let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
  2788. let navigationMatches = boundaryMatches.filter((match, index) => {
  2789. if (match.route.loader == null) {
  2790. return false;
  2791. } // Always call the loader on new route instances and pending defer cancellations
  2792. if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
  2793. return true;
  2794. } // This is the default implementation for when we revalidate. If the route
  2795. // provides it's own implementation, then we give them full control but
  2796. // provide this value so they can leverage it if needed after they check
  2797. // their own specific use cases
  2798. let currentRouteMatch = state.matches[index];
  2799. let nextRouteMatch = match;
  2800. return shouldRevalidateLoader(match, _extends({
  2801. currentUrl,
  2802. currentParams: currentRouteMatch.params,
  2803. nextUrl,
  2804. nextParams: nextRouteMatch.params
  2805. }, submission, {
  2806. actionResult,
  2807. defaultShouldRevalidate: defaultShouldRevalidate || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
  2808. }));
  2809. }); // Pick fetcher.loads that need to be revalidated
  2810. let revalidatingFetchers = [];
  2811. fetchLoadMatches && fetchLoadMatches.forEach((f, key) => {
  2812. if (!matches.some(m => m.route.id === f.routeId)) {
  2813. // This fetcher is not going to be present in the subsequent render so
  2814. // there's no need to revalidate it
  2815. return;
  2816. } else if (cancelledFetcherLoads.includes(key)) {
  2817. // This fetcher was cancelled from a prior action submission - force reload
  2818. revalidatingFetchers.push(_extends({
  2819. key
  2820. }, f));
  2821. } else {
  2822. // Revalidating fetchers are decoupled from the route matches since they
  2823. // hit a static href, so they _always_ check shouldRevalidate and the
  2824. // default is strictly if a revalidation is explicitly required (action
  2825. // submissions, useRevalidator, X-Remix-Revalidate).
  2826. let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
  2827. currentUrl,
  2828. currentParams: state.matches[state.matches.length - 1].params,
  2829. nextUrl,
  2830. nextParams: matches[matches.length - 1].params
  2831. }, submission, {
  2832. actionResult,
  2833. defaultShouldRevalidate
  2834. }));
  2835. if (shouldRevalidate) {
  2836. revalidatingFetchers.push(_extends({
  2837. key
  2838. }, f));
  2839. }
  2840. }
  2841. });
  2842. return [navigationMatches, revalidatingFetchers];
  2843. }
  2844. function isNewLoader(currentLoaderData, currentMatch, match) {
  2845. let isNew = // [a] -> [a, b]
  2846. !currentMatch || // [a, b] -> [a, c]
  2847. match.route.id !== currentMatch.route.id; // Handle the case that we don't have data for a re-used route, potentially
  2848. // from a prior error or from a cancelled pending deferred
  2849. let isMissingData = currentLoaderData[match.route.id] === undefined; // Always load if this is a net-new route or we don't yet have data
  2850. return isNew || isMissingData;
  2851. }
  2852. function isNewRouteInstance(currentMatch, match) {
  2853. let currentPath = currentMatch.route.path;
  2854. return (// param change for this match, /users/123 -> /users/456
  2855. currentMatch.pathname !== match.pathname || // splat param changed, which is not present in match.path
  2856. // e.g. /files/images/avatar.jpg -> files/finances.xls
  2857. currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
  2858. );
  2859. }
  2860. function shouldRevalidateLoader(loaderMatch, arg) {
  2861. if (loaderMatch.route.shouldRevalidate) {
  2862. let routeChoice = loaderMatch.route.shouldRevalidate(arg);
  2863. if (typeof routeChoice === "boolean") {
  2864. return routeChoice;
  2865. }
  2866. }
  2867. return arg.defaultShouldRevalidate;
  2868. }
  2869. async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
  2870. if (basename === void 0) {
  2871. basename = "/";
  2872. }
  2873. if (isStaticRequest === void 0) {
  2874. isStaticRequest = false;
  2875. }
  2876. if (isRouteRequest === void 0) {
  2877. isRouteRequest = false;
  2878. }
  2879. let resultType;
  2880. let result; // Setup a promise we can race against so that abort signals short circuit
  2881. let reject;
  2882. let abortPromise = new Promise((_, r) => reject = r);
  2883. let onReject = () => reject();
  2884. request.signal.addEventListener("abort", onReject);
  2885. try {
  2886. let handler = match.route[type];
  2887. invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
  2888. result = await Promise.race([handler({
  2889. request,
  2890. params: match.params,
  2891. context: requestContext
  2892. }), abortPromise]);
  2893. invariant(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`.");
  2894. } catch (e) {
  2895. resultType = ResultType.error;
  2896. result = e;
  2897. } finally {
  2898. request.signal.removeEventListener("abort", onReject);
  2899. }
  2900. if (isResponse(result)) {
  2901. let status = result.status; // Process redirects
  2902. if (redirectStatusCodes.has(status)) {
  2903. let location = result.headers.get("Location");
  2904. invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in internal redirects
  2905. if (!ABSOLUTE_URL_REGEX.test(location)) {
  2906. let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
  2907. let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
  2908. let resolvedLocation = resolveTo(location, routePathnames, new URL(request.url).pathname);
  2909. invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + location); // Prepend the basename to the redirect location if we have one
  2910. if (basename) {
  2911. let path = resolvedLocation.pathname;
  2912. resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
  2913. }
  2914. location = createPath(resolvedLocation);
  2915. } else if (!isStaticRequest) {
  2916. // Strip off the protocol+origin for same-origin absolute redirects.
  2917. // If this is a static reques, we can let it go back to the browser
  2918. // as-is
  2919. let currentUrl = new URL(request.url);
  2920. let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
  2921. if (url.origin === currentUrl.origin) {
  2922. location = url.pathname + url.search + url.hash;
  2923. }
  2924. } // Don't process redirects in the router during static requests requests.
  2925. // Instead, throw the Response and let the server handle it with an HTTP
  2926. // redirect. We also update the Location header in place in this flow so
  2927. // basename and relative routing is taken into account
  2928. if (isStaticRequest) {
  2929. result.headers.set("Location", location);
  2930. throw result;
  2931. }
  2932. return {
  2933. type: ResultType.redirect,
  2934. status,
  2935. location,
  2936. revalidate: result.headers.get("X-Remix-Revalidate") !== null
  2937. };
  2938. } // For SSR single-route requests, we want to hand Responses back directly
  2939. // without unwrapping. We do this with the QueryRouteResponse wrapper
  2940. // interface so we can know whether it was returned or thrown
  2941. if (isRouteRequest) {
  2942. // eslint-disable-next-line no-throw-literal
  2943. throw {
  2944. type: resultType || ResultType.data,
  2945. response: result
  2946. };
  2947. }
  2948. let data;
  2949. let contentType = result.headers.get("Content-Type"); // Check between word boundaries instead of startsWith() due to the last
  2950. // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
  2951. if (contentType && /\bapplication\/json\b/.test(contentType)) {
  2952. data = await result.json();
  2953. } else {
  2954. data = await result.text();
  2955. }
  2956. if (resultType === ResultType.error) {
  2957. return {
  2958. type: resultType,
  2959. error: new ErrorResponse(status, result.statusText, data),
  2960. headers: result.headers
  2961. };
  2962. }
  2963. return {
  2964. type: ResultType.data,
  2965. data,
  2966. statusCode: result.status,
  2967. headers: result.headers
  2968. };
  2969. }
  2970. if (resultType === ResultType.error) {
  2971. return {
  2972. type: resultType,
  2973. error: result
  2974. };
  2975. }
  2976. if (result instanceof DeferredData) {
  2977. return {
  2978. type: ResultType.deferred,
  2979. deferredData: result
  2980. };
  2981. }
  2982. return {
  2983. type: ResultType.data,
  2984. data: result
  2985. };
  2986. } // Utility method for creating the Request instances for loaders/actions during
  2987. // client-side navigations and fetches. During SSR we will always have a
  2988. // Request instance from the static handler (query/queryRoute)
  2989. function createClientSideRequest(history, location, signal, submission) {
  2990. let url = history.createURL(stripHashFromPath(location)).toString();
  2991. let init = {
  2992. signal
  2993. };
  2994. if (submission && isMutationMethod(submission.formMethod)) {
  2995. let {
  2996. formMethod,
  2997. formEncType,
  2998. formData
  2999. } = submission;
  3000. init.method = formMethod.toUpperCase();
  3001. init.body = formEncType === "application/x-www-form-urlencoded" ? convertFormDataToSearchParams(formData) : formData;
  3002. } // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
  3003. return new Request(url, init);
  3004. }
  3005. function convertFormDataToSearchParams(formData) {
  3006. let searchParams = new URLSearchParams();
  3007. for (let [key, value] of formData.entries()) {
  3008. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
  3009. searchParams.append(key, value instanceof File ? value.name : value);
  3010. }
  3011. return searchParams;
  3012. }
  3013. function processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {
  3014. // Fill in loaderData/errors from our loaders
  3015. let loaderData = {};
  3016. let errors = null;
  3017. let statusCode;
  3018. let foundError = false;
  3019. let loaderHeaders = {}; // Process loader results into state.loaderData/state.errors
  3020. results.forEach((result, index) => {
  3021. let id = matchesToLoad[index].route.id;
  3022. invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
  3023. if (isErrorResult(result)) {
  3024. // Look upwards from the matched route for the closest ancestor
  3025. // error boundary, defaulting to the root match
  3026. let boundaryMatch = findNearestBoundary(matches, id);
  3027. let error = result.error; // If we have a pending action error, we report it at the highest-route
  3028. // that throws a loader error, and then clear it out to indicate that
  3029. // it was consumed
  3030. if (pendingError) {
  3031. error = Object.values(pendingError)[0];
  3032. pendingError = undefined;
  3033. }
  3034. errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
  3035. if (errors[boundaryMatch.route.id] == null) {
  3036. errors[boundaryMatch.route.id] = error;
  3037. } // Clear our any prior loaderData for the throwing route
  3038. loaderData[id] = undefined; // Once we find our first (highest) error, we set the status code and
  3039. // prevent deeper status codes from overriding
  3040. if (!foundError) {
  3041. foundError = true;
  3042. statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
  3043. }
  3044. if (result.headers) {
  3045. loaderHeaders[id] = result.headers;
  3046. }
  3047. } else {
  3048. if (isDeferredResult(result)) {
  3049. activeDeferreds.set(id, result.deferredData);
  3050. loaderData[id] = result.deferredData.data;
  3051. } else {
  3052. loaderData[id] = result.data;
  3053. } // Error status codes always override success status codes, but if all
  3054. // loaders are successful we take the deepest status code.
  3055. if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
  3056. statusCode = result.statusCode;
  3057. }
  3058. if (result.headers) {
  3059. loaderHeaders[id] = result.headers;
  3060. }
  3061. }
  3062. }); // If we didn't consume the pending action error (i.e., all loaders
  3063. // resolved), then consume it here. Also clear out any loaderData for the
  3064. // throwing route
  3065. if (pendingError) {
  3066. errors = pendingError;
  3067. loaderData[Object.keys(pendingError)[0]] = undefined;
  3068. }
  3069. return {
  3070. loaderData,
  3071. errors,
  3072. statusCode: statusCode || 200,
  3073. loaderHeaders
  3074. };
  3075. }
  3076. function processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {
  3077. let {
  3078. loaderData,
  3079. errors
  3080. } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds); // Process results from our revalidating fetchers
  3081. for (let index = 0; index < revalidatingFetchers.length; index++) {
  3082. let {
  3083. key,
  3084. match
  3085. } = revalidatingFetchers[index];
  3086. invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
  3087. let result = fetcherResults[index]; // Process fetcher non-redirect errors
  3088. if (isErrorResult(result)) {
  3089. let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
  3090. if (!(errors && errors[boundaryMatch.route.id])) {
  3091. errors = _extends({}, errors, {
  3092. [boundaryMatch.route.id]: result.error
  3093. });
  3094. }
  3095. state.fetchers.delete(key);
  3096. } else if (isRedirectResult(result)) {
  3097. // Should never get here, redirects should get processed above, but we
  3098. // keep this to type narrow to a success result in the else
  3099. invariant(false, "Unhandled fetcher revalidation redirect");
  3100. } else if (isDeferredResult(result)) {
  3101. // Should never get here, deferred data should be awaited for fetchers
  3102. // in resolveDeferredResults
  3103. invariant(false, "Unhandled fetcher deferred data");
  3104. } else {
  3105. let doneFetcher = {
  3106. state: "idle",
  3107. data: result.data,
  3108. formMethod: undefined,
  3109. formAction: undefined,
  3110. formEncType: undefined,
  3111. formData: undefined,
  3112. " _hasFetcherDoneAnything ": true
  3113. };
  3114. state.fetchers.set(key, doneFetcher);
  3115. }
  3116. }
  3117. return {
  3118. loaderData,
  3119. errors
  3120. };
  3121. }
  3122. function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
  3123. let mergedLoaderData = _extends({}, newLoaderData);
  3124. for (let match of matches) {
  3125. let id = match.route.id;
  3126. if (newLoaderData.hasOwnProperty(id)) {
  3127. if (newLoaderData[id] !== undefined) {
  3128. mergedLoaderData[id] = newLoaderData[id];
  3129. }
  3130. } else if (loaderData[id] !== undefined) {
  3131. mergedLoaderData[id] = loaderData[id];
  3132. }
  3133. if (errors && errors.hasOwnProperty(id)) {
  3134. // Don't keep any loader data below the boundary
  3135. break;
  3136. }
  3137. }
  3138. return mergedLoaderData;
  3139. } // Find the nearest error boundary, looking upwards from the leaf route (or the
  3140. // route specified by routeId) for the closest ancestor error boundary,
  3141. // defaulting to the root match
  3142. function findNearestBoundary(matches, routeId) {
  3143. let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
  3144. return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
  3145. }
  3146. function getShortCircuitMatches(routes) {
  3147. // Prefer a root layout route if present, otherwise shim in a route object
  3148. let route = routes.find(r => r.index || !r.path || r.path === "/") || {
  3149. id: "__shim-error-route__"
  3150. };
  3151. return {
  3152. matches: [{
  3153. params: {},
  3154. pathname: "",
  3155. pathnameBase: "",
  3156. route
  3157. }],
  3158. route
  3159. };
  3160. }
  3161. function getInternalRouterError(status, _temp4) {
  3162. let {
  3163. pathname,
  3164. routeId,
  3165. method,
  3166. type
  3167. } = _temp4 === void 0 ? {} : _temp4;
  3168. let statusText = "Unknown Server Error";
  3169. let errorMessage = "Unknown @remix-run/router error";
  3170. if (status === 400) {
  3171. statusText = "Bad Request";
  3172. if (method && pathname && routeId) {
  3173. 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.";
  3174. } else if (type === "defer-action") {
  3175. errorMessage = "defer() is not supported in actions";
  3176. }
  3177. } else if (status === 403) {
  3178. statusText = "Forbidden";
  3179. errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
  3180. } else if (status === 404) {
  3181. statusText = "Not Found";
  3182. errorMessage = "No route matches URL \"" + pathname + "\"";
  3183. } else if (status === 405) {
  3184. statusText = "Method Not Allowed";
  3185. if (method && pathname && routeId) {
  3186. 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.";
  3187. } else if (method) {
  3188. errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
  3189. }
  3190. }
  3191. return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
  3192. } // Find any returned redirect errors, starting from the lowest match
  3193. function findRedirect(results) {
  3194. for (let i = results.length - 1; i >= 0; i--) {
  3195. let result = results[i];
  3196. if (isRedirectResult(result)) {
  3197. return result;
  3198. }
  3199. }
  3200. }
  3201. function stripHashFromPath(path) {
  3202. let parsedPath = typeof path === "string" ? parsePath(path) : path;
  3203. return createPath(_extends({}, parsedPath, {
  3204. hash: ""
  3205. }));
  3206. }
  3207. function isHashChangeOnly(a, b) {
  3208. return a.pathname === b.pathname && a.search === b.search && a.hash !== b.hash;
  3209. }
  3210. function isDeferredResult(result) {
  3211. return result.type === ResultType.deferred;
  3212. }
  3213. function isErrorResult(result) {
  3214. return result.type === ResultType.error;
  3215. }
  3216. function isRedirectResult(result) {
  3217. return (result && result.type) === ResultType.redirect;
  3218. }
  3219. function isResponse(value) {
  3220. return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
  3221. }
  3222. function isRedirectResponse(result) {
  3223. if (!isResponse(result)) {
  3224. return false;
  3225. }
  3226. let status = result.status;
  3227. let location = result.headers.get("Location");
  3228. return status >= 300 && status <= 399 && location != null;
  3229. }
  3230. function isQueryRouteResponse(obj) {
  3231. return obj && isResponse(obj.response) && (obj.type === ResultType.data || ResultType.error);
  3232. }
  3233. function isValidMethod(method) {
  3234. return validRequestMethods.has(method);
  3235. }
  3236. function isMutationMethod(method) {
  3237. return validMutationMethods.has(method);
  3238. }
  3239. async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
  3240. for (let index = 0; index < results.length; index++) {
  3241. let result = results[index];
  3242. let match = matchesToLoad[index];
  3243. let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
  3244. let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
  3245. if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
  3246. // Note: we do not have to touch activeDeferreds here since we race them
  3247. // against the signal in resolveDeferredData and they'll get aborted
  3248. // there if needed
  3249. await resolveDeferredData(result, signal, isFetcher).then(result => {
  3250. if (result) {
  3251. results[index] = result || results[index];
  3252. }
  3253. });
  3254. }
  3255. }
  3256. }
  3257. async function resolveDeferredData(result, signal, unwrap) {
  3258. if (unwrap === void 0) {
  3259. unwrap = false;
  3260. }
  3261. let aborted = await result.deferredData.resolveData(signal);
  3262. if (aborted) {
  3263. return;
  3264. }
  3265. if (unwrap) {
  3266. try {
  3267. return {
  3268. type: ResultType.data,
  3269. data: result.deferredData.unwrappedData
  3270. };
  3271. } catch (e) {
  3272. // Handle any TrackedPromise._error values encountered while unwrapping
  3273. return {
  3274. type: ResultType.error,
  3275. error: e
  3276. };
  3277. }
  3278. }
  3279. return {
  3280. type: ResultType.data,
  3281. data: result.deferredData.data
  3282. };
  3283. }
  3284. function hasNakedIndexQuery(search) {
  3285. return new URLSearchParams(search).getAll("index").some(v => v === "");
  3286. } // Note: This should match the format exported by useMatches, so if you change
  3287. // this please also change that :) Eventually we'll DRY this up
  3288. function createUseMatchesMatch(match, loaderData) {
  3289. let {
  3290. route,
  3291. pathname,
  3292. params
  3293. } = match;
  3294. return {
  3295. id: route.id,
  3296. pathname,
  3297. params,
  3298. data: loaderData[route.id],
  3299. handle: route.handle
  3300. };
  3301. }
  3302. function getTargetMatch(matches, location) {
  3303. let search = typeof location === "string" ? parsePath(location).search : location.search;
  3304. if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
  3305. // Return the leaf index route when index is present
  3306. return matches[matches.length - 1];
  3307. } // Otherwise grab the deepest "path contributing" match (ignoring index and
  3308. // pathless layout routes)
  3309. let pathMatches = getPathContributingMatches(matches);
  3310. return pathMatches[pathMatches.length - 1];
  3311. } //#endregion
  3312. export { AbortedDeferredError, Action, ErrorResponse, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, invariant, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, warning };
  3313. //# sourceMappingURL=router.js.map