router.cjs.js 133 KB

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