router.cjs.js 203 KB

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