create-DwAwaNot.js 330 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020
  1. 'use strict';
  2. var motionDom = require('motion-dom');
  3. var motionUtils = require('motion-utils');
  4. var jsxRuntime = require('react/jsx-runtime');
  5. var React = require('react');
  6. const LayoutGroupContext = React.createContext({});
  7. /**
  8. * Creates a constant value over the lifecycle of a component.
  9. *
  10. * Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
  11. * a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
  12. * you can ensure that initialisers don't execute twice or more.
  13. */
  14. function useConstant(init) {
  15. const ref = React.useRef(null);
  16. if (ref.current === null) {
  17. ref.current = init();
  18. }
  19. return ref.current;
  20. }
  21. const isBrowser = typeof window !== "undefined";
  22. const useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect;
  23. /**
  24. * @public
  25. */
  26. const PresenceContext =
  27. /* @__PURE__ */ React.createContext(null);
  28. /**
  29. * @public
  30. */
  31. const MotionConfigContext = React.createContext({
  32. transformPagePoint: (p) => p,
  33. isStatic: false,
  34. reducedMotion: "never",
  35. });
  36. /**
  37. * When a component is the child of `AnimatePresence`, it can use `usePresence`
  38. * to access information about whether it's still present in the React tree.
  39. *
  40. * ```jsx
  41. * import { usePresence } from "framer-motion"
  42. *
  43. * export const Component = () => {
  44. * const [isPresent, safeToRemove] = usePresence()
  45. *
  46. * useEffect(() => {
  47. * !isPresent && setTimeout(safeToRemove, 1000)
  48. * }, [isPresent])
  49. *
  50. * return <div />
  51. * }
  52. * ```
  53. *
  54. * If `isPresent` is `false`, it means that a component has been removed the tree, but
  55. * `AnimatePresence` won't really remove it until `safeToRemove` has been called.
  56. *
  57. * @public
  58. */
  59. function usePresence(subscribe = true) {
  60. const context = React.useContext(PresenceContext);
  61. if (context === null)
  62. return [true, null];
  63. const { isPresent, onExitComplete, register } = context;
  64. // It's safe to call the following hooks conditionally (after an early return) because the context will always
  65. // either be null or non-null for the lifespan of the component.
  66. const id = React.useId();
  67. React.useEffect(() => {
  68. if (subscribe) {
  69. return register(id);
  70. }
  71. }, [subscribe]);
  72. const safeToRemove = React.useCallback(() => subscribe && onExitComplete && onExitComplete(id), [id, onExitComplete, subscribe]);
  73. return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
  74. }
  75. /**
  76. * Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
  77. * There is no `safeToRemove` function.
  78. *
  79. * ```jsx
  80. * import { useIsPresent } from "framer-motion"
  81. *
  82. * export const Component = () => {
  83. * const isPresent = useIsPresent()
  84. *
  85. * useEffect(() => {
  86. * !isPresent && console.log("I've been removed!")
  87. * }, [isPresent])
  88. *
  89. * return <div />
  90. * }
  91. * ```
  92. *
  93. * @public
  94. */
  95. function useIsPresent() {
  96. return isPresent(React.useContext(PresenceContext));
  97. }
  98. function isPresent(context) {
  99. return context === null ? true : context.isPresent;
  100. }
  101. /*
  102. Value in range from progress
  103. Given a lower limit and an upper limit, we return the value within
  104. that range as expressed by progress (usually a number from 0 to 1)
  105. So progress = 0.5 would change
  106. from -------- to
  107. to
  108. from ---- to
  109. E.g. from = 10, to = 20, progress = 0.5 => 15
  110. @param [number]: Lower limit of range
  111. @param [number]: Upper limit of range
  112. @param [number]: The progress between lower and upper limits expressed 0-1
  113. @return [number]: Value as calculated from progress within range (not limited within range)
  114. */
  115. const mixNumber$1 = (from, to, progress) => {
  116. return from + (to - from) * progress;
  117. };
  118. const SCALE_PRECISION = 0.0001;
  119. const SCALE_MIN = 1 - SCALE_PRECISION;
  120. const SCALE_MAX = 1 + SCALE_PRECISION;
  121. const TRANSLATE_PRECISION = 0.01;
  122. const TRANSLATE_MIN = 0 - TRANSLATE_PRECISION;
  123. const TRANSLATE_MAX = 0 + TRANSLATE_PRECISION;
  124. function calcLength(axis) {
  125. return axis.max - axis.min;
  126. }
  127. function isNear(value, target, maxDistance) {
  128. return Math.abs(value - target) <= maxDistance;
  129. }
  130. function calcAxisDelta(delta, source, target, origin = 0.5) {
  131. delta.origin = origin;
  132. delta.originPoint = mixNumber$1(source.min, source.max, delta.origin);
  133. delta.scale = calcLength(target) / calcLength(source);
  134. delta.translate =
  135. mixNumber$1(target.min, target.max, delta.origin) - delta.originPoint;
  136. if ((delta.scale >= SCALE_MIN && delta.scale <= SCALE_MAX) ||
  137. isNaN(delta.scale)) {
  138. delta.scale = 1.0;
  139. }
  140. if ((delta.translate >= TRANSLATE_MIN &&
  141. delta.translate <= TRANSLATE_MAX) ||
  142. isNaN(delta.translate)) {
  143. delta.translate = 0.0;
  144. }
  145. }
  146. function calcBoxDelta(delta, source, target, origin) {
  147. calcAxisDelta(delta.x, source.x, target.x, origin ? origin.originX : undefined);
  148. calcAxisDelta(delta.y, source.y, target.y, origin ? origin.originY : undefined);
  149. }
  150. function calcRelativeAxis(target, relative, parent) {
  151. target.min = parent.min + relative.min;
  152. target.max = target.min + calcLength(relative);
  153. }
  154. function calcRelativeBox(target, relative, parent) {
  155. calcRelativeAxis(target.x, relative.x, parent.x);
  156. calcRelativeAxis(target.y, relative.y, parent.y);
  157. }
  158. function calcRelativeAxisPosition(target, layout, parent) {
  159. target.min = layout.min - parent.min;
  160. target.max = target.min + calcLength(layout);
  161. }
  162. function calcRelativePosition(target, layout, parent) {
  163. calcRelativeAxisPosition(target.x, layout.x, parent.x);
  164. calcRelativeAxisPosition(target.y, layout.y, parent.y);
  165. }
  166. const isMotionValue = (value) => Boolean(value && value.getVelocity);
  167. const instantAnimationState = {
  168. current: false,
  169. };
  170. /*
  171. Bezier function generator
  172. This has been modified from Gaëtan Renaudeau's BezierEasing
  173. https://github.com/gre/bezier-easing/blob/master/src/index.js
  174. https://github.com/gre/bezier-easing/blob/master/LICENSE
  175. I've removed the newtonRaphsonIterate algo because in benchmarking it
  176. wasn't noticiably faster than binarySubdivision, indeed removing it
  177. usually improved times, depending on the curve.
  178. I also removed the lookup table, as for the added bundle size and loop we're
  179. only cutting ~4 or so subdivision iterations. I bumped the max iterations up
  180. to 12 to compensate and this still tended to be faster for no perceivable
  181. loss in accuracy.
  182. Usage
  183. const easeOut = cubicBezier(.17,.67,.83,.67);
  184. const x = easeOut(0.5); // returns 0.627...
  185. */
  186. // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  187. const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
  188. t;
  189. const subdivisionPrecision = 0.0000001;
  190. const subdivisionMaxIterations = 12;
  191. function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
  192. let currentX;
  193. let currentT;
  194. let i = 0;
  195. do {
  196. currentT = lowerBound + (upperBound - lowerBound) / 2.0;
  197. currentX = calcBezier(currentT, mX1, mX2) - x;
  198. if (currentX > 0.0) {
  199. upperBound = currentT;
  200. }
  201. else {
  202. lowerBound = currentT;
  203. }
  204. } while (Math.abs(currentX) > subdivisionPrecision &&
  205. ++i < subdivisionMaxIterations);
  206. return currentT;
  207. }
  208. function cubicBezier(mX1, mY1, mX2, mY2) {
  209. // If this is a linear gradient, return linear easing
  210. if (mX1 === mY1 && mX2 === mY2)
  211. return motionUtils.noop;
  212. const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
  213. // If animation is at start/end, return t without easing
  214. return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
  215. }
  216. // Accepts an easing function and returns a new one that outputs mirrored values for
  217. // the second half of the animation. Turns easeIn into easeInOut.
  218. const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
  219. // Accepts an easing function and returns a new one that outputs reversed values.
  220. // Turns easeIn into easeOut.
  221. const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
  222. const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
  223. const backIn = /*@__PURE__*/ reverseEasing(backOut);
  224. const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
  225. const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
  226. const circIn = (p) => 1 - Math.sin(Math.acos(p));
  227. const circOut = reverseEasing(circIn);
  228. const circInOut = mirrorEasing(circIn);
  229. /**
  230. * Check if the value is a zero value string like "0px" or "0%"
  231. */
  232. const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
  233. function isNone(value) {
  234. if (typeof value === "number") {
  235. return value === 0;
  236. }
  237. else if (value !== null) {
  238. return value === "none" || value === "0" || isZeroValueString(value);
  239. }
  240. else {
  241. return true;
  242. }
  243. }
  244. /**
  245. * Generate a list of every possible transform key.
  246. */
  247. const transformPropOrder = [
  248. "transformPerspective",
  249. "x",
  250. "y",
  251. "z",
  252. "translateX",
  253. "translateY",
  254. "translateZ",
  255. "scale",
  256. "scaleX",
  257. "scaleY",
  258. "rotate",
  259. "rotateX",
  260. "rotateY",
  261. "rotateZ",
  262. "skew",
  263. "skewX",
  264. "skewY",
  265. ];
  266. /**
  267. * A quick lookup for transform props.
  268. */
  269. const transformProps = new Set(transformPropOrder);
  270. const positionalKeys = new Set([
  271. "width",
  272. "height",
  273. "top",
  274. "left",
  275. "right",
  276. "bottom",
  277. ...transformPropOrder,
  278. ]);
  279. const clamp = (min, max, v) => {
  280. if (v > max)
  281. return max;
  282. if (v < min)
  283. return min;
  284. return v;
  285. };
  286. const number = {
  287. test: (v) => typeof v === "number",
  288. parse: parseFloat,
  289. transform: (v) => v,
  290. };
  291. const alpha = {
  292. ...number,
  293. transform: (v) => clamp(0, 1, v),
  294. };
  295. const scale = {
  296. ...number,
  297. default: 1,
  298. };
  299. // If this number is a decimal, make it just five decimal places
  300. // to avoid exponents
  301. const sanitize = (v) => Math.round(v * 100000) / 100000;
  302. const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu;
  303. function isNullish(v) {
  304. return v == null;
  305. }
  306. const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu;
  307. /**
  308. * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
  309. * but false if a number or multiple colors
  310. */
  311. const isColorString = (type, testProp) => (v) => {
  312. return Boolean((typeof v === "string" &&
  313. singleColorRegex.test(v) &&
  314. v.startsWith(type)) ||
  315. (testProp &&
  316. !isNullish(v) &&
  317. Object.prototype.hasOwnProperty.call(v, testProp)));
  318. };
  319. const splitColor = (aName, bName, cName) => (v) => {
  320. if (typeof v !== "string")
  321. return v;
  322. const [a, b, c, alpha] = v.match(floatRegex);
  323. return {
  324. [aName]: parseFloat(a),
  325. [bName]: parseFloat(b),
  326. [cName]: parseFloat(c),
  327. alpha: alpha !== undefined ? parseFloat(alpha) : 1,
  328. };
  329. };
  330. const clampRgbUnit = (v) => clamp(0, 255, v);
  331. const rgbUnit = {
  332. ...number,
  333. transform: (v) => Math.round(clampRgbUnit(v)),
  334. };
  335. const rgba = {
  336. test: /*@__PURE__*/ isColorString("rgb", "red"),
  337. parse: /*@__PURE__*/ splitColor("red", "green", "blue"),
  338. transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
  339. rgbUnit.transform(red) +
  340. ", " +
  341. rgbUnit.transform(green) +
  342. ", " +
  343. rgbUnit.transform(blue) +
  344. ", " +
  345. sanitize(alpha.transform(alpha$1)) +
  346. ")",
  347. };
  348. function parseHex(v) {
  349. let r = "";
  350. let g = "";
  351. let b = "";
  352. let a = "";
  353. // If we have 6 characters, ie #FF0000
  354. if (v.length > 5) {
  355. r = v.substring(1, 3);
  356. g = v.substring(3, 5);
  357. b = v.substring(5, 7);
  358. a = v.substring(7, 9);
  359. // Or we have 3 characters, ie #F00
  360. }
  361. else {
  362. r = v.substring(1, 2);
  363. g = v.substring(2, 3);
  364. b = v.substring(3, 4);
  365. a = v.substring(4, 5);
  366. r += r;
  367. g += g;
  368. b += b;
  369. a += a;
  370. }
  371. return {
  372. red: parseInt(r, 16),
  373. green: parseInt(g, 16),
  374. blue: parseInt(b, 16),
  375. alpha: a ? parseInt(a, 16) / 255 : 1,
  376. };
  377. }
  378. const hex = {
  379. test: /*@__PURE__*/ isColorString("#"),
  380. parse: parseHex,
  381. transform: rgba.transform,
  382. };
  383. const createUnitType = (unit) => ({
  384. test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1,
  385. parse: parseFloat,
  386. transform: (v) => `${v}${unit}`,
  387. });
  388. const degrees = /*@__PURE__*/ createUnitType("deg");
  389. const percent = /*@__PURE__*/ createUnitType("%");
  390. const px = /*@__PURE__*/ createUnitType("px");
  391. const vh = /*@__PURE__*/ createUnitType("vh");
  392. const vw = /*@__PURE__*/ createUnitType("vw");
  393. const progressPercentage = {
  394. ...percent,
  395. parse: (v) => percent.parse(v) / 100,
  396. transform: (v) => percent.transform(v * 100),
  397. };
  398. const hsla = {
  399. test: /*@__PURE__*/ isColorString("hsl", "hue"),
  400. parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"),
  401. transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
  402. return ("hsla(" +
  403. Math.round(hue) +
  404. ", " +
  405. percent.transform(sanitize(saturation)) +
  406. ", " +
  407. percent.transform(sanitize(lightness)) +
  408. ", " +
  409. sanitize(alpha.transform(alpha$1)) +
  410. ")");
  411. },
  412. };
  413. const color = {
  414. test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
  415. parse: (v) => {
  416. if (rgba.test(v)) {
  417. return rgba.parse(v);
  418. }
  419. else if (hsla.test(v)) {
  420. return hsla.parse(v);
  421. }
  422. else {
  423. return hex.parse(v);
  424. }
  425. },
  426. transform: (v) => {
  427. return typeof v === "string"
  428. ? v
  429. : v.hasOwnProperty("red")
  430. ? rgba.transform(v)
  431. : hsla.transform(v);
  432. },
  433. };
  434. const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;
  435. function test(v) {
  436. return (isNaN(v) &&
  437. typeof v === "string" &&
  438. (v.match(floatRegex)?.length || 0) +
  439. (v.match(colorRegex)?.length || 0) >
  440. 0);
  441. }
  442. const NUMBER_TOKEN = "number";
  443. const COLOR_TOKEN = "color";
  444. const VAR_TOKEN = "var";
  445. const VAR_FUNCTION_TOKEN = "var(";
  446. const SPLIT_TOKEN = "${}";
  447. // this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex`
  448. const complexRegex = /var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu;
  449. function analyseComplexValue(value) {
  450. const originalValue = value.toString();
  451. const values = [];
  452. const indexes = {
  453. color: [],
  454. number: [],
  455. var: [],
  456. };
  457. const types = [];
  458. let i = 0;
  459. const tokenised = originalValue.replace(complexRegex, (parsedValue) => {
  460. if (color.test(parsedValue)) {
  461. indexes.color.push(i);
  462. types.push(COLOR_TOKEN);
  463. values.push(color.parse(parsedValue));
  464. }
  465. else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) {
  466. indexes.var.push(i);
  467. types.push(VAR_TOKEN);
  468. values.push(parsedValue);
  469. }
  470. else {
  471. indexes.number.push(i);
  472. types.push(NUMBER_TOKEN);
  473. values.push(parseFloat(parsedValue));
  474. }
  475. ++i;
  476. return SPLIT_TOKEN;
  477. });
  478. const split = tokenised.split(SPLIT_TOKEN);
  479. return { values, split, indexes, types };
  480. }
  481. function parseComplexValue(v) {
  482. return analyseComplexValue(v).values;
  483. }
  484. function createTransformer(source) {
  485. const { split, types } = analyseComplexValue(source);
  486. const numSections = split.length;
  487. return (v) => {
  488. let output = "";
  489. for (let i = 0; i < numSections; i++) {
  490. output += split[i];
  491. if (v[i] !== undefined) {
  492. const type = types[i];
  493. if (type === NUMBER_TOKEN) {
  494. output += sanitize(v[i]);
  495. }
  496. else if (type === COLOR_TOKEN) {
  497. output += color.transform(v[i]);
  498. }
  499. else {
  500. output += v[i];
  501. }
  502. }
  503. }
  504. return output;
  505. };
  506. }
  507. const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
  508. function getAnimatableNone$1(v) {
  509. const parsed = parseComplexValue(v);
  510. const transformer = createTransformer(v);
  511. return transformer(parsed.map(convertNumbersToZero));
  512. }
  513. const complex = {
  514. test,
  515. parse: parseComplexValue,
  516. createTransformer,
  517. getAnimatableNone: getAnimatableNone$1,
  518. };
  519. /**
  520. * Properties that should default to 1 or 100%
  521. */
  522. const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
  523. function applyDefaultFilter(v) {
  524. const [name, value] = v.slice(0, -1).split("(");
  525. if (name === "drop-shadow")
  526. return v;
  527. const [number] = value.match(floatRegex) || [];
  528. if (!number)
  529. return v;
  530. const unit = value.replace(number, "");
  531. let defaultValue = maxDefaults.has(name) ? 1 : 0;
  532. if (number !== value)
  533. defaultValue *= 100;
  534. return name + "(" + defaultValue + unit + ")";
  535. }
  536. const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
  537. const filter = {
  538. ...complex,
  539. getAnimatableNone: (v) => {
  540. const functions = v.match(functionRegex);
  541. return functions ? functions.map(applyDefaultFilter).join(" ") : v;
  542. },
  543. };
  544. const browserNumberValueTypes = {
  545. // Border props
  546. borderWidth: px,
  547. borderTopWidth: px,
  548. borderRightWidth: px,
  549. borderBottomWidth: px,
  550. borderLeftWidth: px,
  551. borderRadius: px,
  552. radius: px,
  553. borderTopLeftRadius: px,
  554. borderTopRightRadius: px,
  555. borderBottomRightRadius: px,
  556. borderBottomLeftRadius: px,
  557. // Positioning props
  558. width: px,
  559. maxWidth: px,
  560. height: px,
  561. maxHeight: px,
  562. top: px,
  563. right: px,
  564. bottom: px,
  565. left: px,
  566. // Spacing props
  567. padding: px,
  568. paddingTop: px,
  569. paddingRight: px,
  570. paddingBottom: px,
  571. paddingLeft: px,
  572. margin: px,
  573. marginTop: px,
  574. marginRight: px,
  575. marginBottom: px,
  576. marginLeft: px,
  577. // Misc
  578. backgroundPositionX: px,
  579. backgroundPositionY: px,
  580. };
  581. const transformValueTypes = {
  582. rotate: degrees,
  583. rotateX: degrees,
  584. rotateY: degrees,
  585. rotateZ: degrees,
  586. scale,
  587. scaleX: scale,
  588. scaleY: scale,
  589. scaleZ: scale,
  590. skew: degrees,
  591. skewX: degrees,
  592. skewY: degrees,
  593. distance: px,
  594. translateX: px,
  595. translateY: px,
  596. translateZ: px,
  597. x: px,
  598. y: px,
  599. z: px,
  600. perspective: px,
  601. transformPerspective: px,
  602. opacity: alpha,
  603. originX: progressPercentage,
  604. originY: progressPercentage,
  605. originZ: px,
  606. };
  607. const int = {
  608. ...number,
  609. transform: Math.round,
  610. };
  611. const numberValueTypes = {
  612. ...browserNumberValueTypes,
  613. ...transformValueTypes,
  614. zIndex: int,
  615. size: px,
  616. // SVG
  617. fillOpacity: alpha,
  618. strokeOpacity: alpha,
  619. numOctaves: int,
  620. };
  621. /**
  622. * A map of default value types for common values
  623. */
  624. const defaultValueTypes = {
  625. ...numberValueTypes,
  626. // Color props
  627. color,
  628. backgroundColor: color,
  629. outlineColor: color,
  630. fill: color,
  631. stroke: color,
  632. // Border props
  633. borderColor: color,
  634. borderTopColor: color,
  635. borderRightColor: color,
  636. borderBottomColor: color,
  637. borderLeftColor: color,
  638. filter,
  639. WebkitFilter: filter,
  640. };
  641. /**
  642. * Gets the default ValueType for the provided value key
  643. */
  644. const getDefaultValueType = (key) => defaultValueTypes[key];
  645. function getAnimatableNone(key, value) {
  646. let defaultValueType = getDefaultValueType(key);
  647. if (defaultValueType !== filter)
  648. defaultValueType = complex;
  649. // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
  650. return defaultValueType.getAnimatableNone
  651. ? defaultValueType.getAnimatableNone(value)
  652. : undefined;
  653. }
  654. /**
  655. * If we encounter keyframes like "none" or "0" and we also have keyframes like
  656. * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
  657. * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
  658. * zero equivalents, i.e. "#fff0" or "0px 0px".
  659. */
  660. const invalidTemplates = new Set(["auto", "none", "0"]);
  661. function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
  662. let i = 0;
  663. let animatableTemplate = undefined;
  664. while (i < unresolvedKeyframes.length && !animatableTemplate) {
  665. const keyframe = unresolvedKeyframes[i];
  666. if (typeof keyframe === "string" &&
  667. !invalidTemplates.has(keyframe) &&
  668. analyseComplexValue(keyframe).values.length) {
  669. animatableTemplate = unresolvedKeyframes[i];
  670. }
  671. i++;
  672. }
  673. if (animatableTemplate && name) {
  674. for (const noneIndex of noneKeyframeIndexes) {
  675. unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
  676. }
  677. }
  678. }
  679. const radToDeg = (rad) => (rad * 180) / Math.PI;
  680. const rotate = (v) => {
  681. const angle = radToDeg(Math.atan2(v[1], v[0]));
  682. return rebaseAngle(angle);
  683. };
  684. const matrix2dParsers = {
  685. x: 4,
  686. y: 5,
  687. translateX: 4,
  688. translateY: 5,
  689. scaleX: 0,
  690. scaleY: 3,
  691. scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
  692. rotate,
  693. rotateZ: rotate,
  694. skewX: (v) => radToDeg(Math.atan(v[1])),
  695. skewY: (v) => radToDeg(Math.atan(v[2])),
  696. skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
  697. };
  698. const rebaseAngle = (angle) => {
  699. angle = angle % 360;
  700. if (angle < 0)
  701. angle += 360;
  702. return angle;
  703. };
  704. const rotateZ = rotate;
  705. const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  706. const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
  707. const matrix3dParsers = {
  708. x: 12,
  709. y: 13,
  710. z: 14,
  711. translateX: 12,
  712. translateY: 13,
  713. translateZ: 14,
  714. scaleX,
  715. scaleY,
  716. scale: (v) => (scaleX(v) + scaleY(v)) / 2,
  717. rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
  718. rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
  719. rotateZ,
  720. rotate: rotateZ,
  721. skewX: (v) => radToDeg(Math.atan(v[4])),
  722. skewY: (v) => radToDeg(Math.atan(v[1])),
  723. skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
  724. };
  725. function defaultTransformValue(name) {
  726. return name.includes("scale") ? 1 : 0;
  727. }
  728. function parseValueFromTransform(transform, name) {
  729. if (!transform || transform === "none") {
  730. return defaultTransformValue(name);
  731. }
  732. const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
  733. let parsers;
  734. let match;
  735. if (matrix3dMatch) {
  736. parsers = matrix3dParsers;
  737. match = matrix3dMatch;
  738. }
  739. else {
  740. const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
  741. parsers = matrix2dParsers;
  742. match = matrix2dMatch;
  743. }
  744. if (!match) {
  745. return defaultTransformValue(name);
  746. }
  747. const valueParser = parsers[name];
  748. const values = match[1].split(",").map(convertTransformToNumber);
  749. return typeof valueParser === "function"
  750. ? valueParser(values)
  751. : values[valueParser];
  752. }
  753. const readTransformValue = (instance, name) => {
  754. const { transform = "none" } = getComputedStyle(instance);
  755. return parseValueFromTransform(transform, name);
  756. };
  757. function convertTransformToNumber(value) {
  758. return parseFloat(value.trim());
  759. }
  760. const isNumOrPxType = (v) => v === number || v === px;
  761. const transformKeys = new Set(["x", "y", "z"]);
  762. const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
  763. function removeNonTranslationalTransform(visualElement) {
  764. const removedTransforms = [];
  765. nonTranslationalTransformKeys.forEach((key) => {
  766. const value = visualElement.getValue(key);
  767. if (value !== undefined) {
  768. removedTransforms.push([key, value.get()]);
  769. value.set(key.startsWith("scale") ? 1 : 0);
  770. }
  771. });
  772. return removedTransforms;
  773. }
  774. const positionalValues = {
  775. // Dimensions
  776. width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
  777. height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
  778. top: (_bbox, { top }) => parseFloat(top),
  779. left: (_bbox, { left }) => parseFloat(left),
  780. bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
  781. right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
  782. // Transform
  783. x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
  784. y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
  785. };
  786. // Alias translate longform names
  787. positionalValues.translateX = positionalValues.x;
  788. positionalValues.translateY = positionalValues.y;
  789. const toResolve = new Set();
  790. let isScheduled = false;
  791. let anyNeedsMeasurement = false;
  792. function measureAllKeyframes() {
  793. if (anyNeedsMeasurement) {
  794. const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
  795. const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
  796. const transformsToRestore = new Map();
  797. /**
  798. * Write pass
  799. * If we're measuring elements we want to remove bounding box-changing transforms.
  800. */
  801. elementsToMeasure.forEach((element) => {
  802. const removedTransforms = removeNonTranslationalTransform(element);
  803. if (!removedTransforms.length)
  804. return;
  805. transformsToRestore.set(element, removedTransforms);
  806. element.render();
  807. });
  808. // Read
  809. resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
  810. // Write
  811. elementsToMeasure.forEach((element) => {
  812. element.render();
  813. const restore = transformsToRestore.get(element);
  814. if (restore) {
  815. restore.forEach(([key, value]) => {
  816. element.getValue(key)?.set(value);
  817. });
  818. }
  819. });
  820. // Read
  821. resolversToMeasure.forEach((resolver) => resolver.measureEndState());
  822. // Write
  823. resolversToMeasure.forEach((resolver) => {
  824. if (resolver.suspendedScrollY !== undefined) {
  825. window.scrollTo(0, resolver.suspendedScrollY);
  826. }
  827. });
  828. }
  829. anyNeedsMeasurement = false;
  830. isScheduled = false;
  831. toResolve.forEach((resolver) => resolver.complete());
  832. toResolve.clear();
  833. }
  834. function readAllKeyframes() {
  835. toResolve.forEach((resolver) => {
  836. resolver.readKeyframes();
  837. if (resolver.needsMeasurement) {
  838. anyNeedsMeasurement = true;
  839. }
  840. });
  841. }
  842. function flushKeyframeResolvers() {
  843. readAllKeyframes();
  844. measureAllKeyframes();
  845. }
  846. class KeyframeResolver {
  847. constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
  848. /**
  849. * Track whether this resolver has completed. Once complete, it never
  850. * needs to attempt keyframe resolution again.
  851. */
  852. this.isComplete = false;
  853. /**
  854. * Track whether this resolver is async. If it is, it'll be added to the
  855. * resolver queue and flushed in the next frame. Resolvers that aren't going
  856. * to trigger read/write thrashing don't need to be async.
  857. */
  858. this.isAsync = false;
  859. /**
  860. * Track whether this resolver needs to perform a measurement
  861. * to resolve its keyframes.
  862. */
  863. this.needsMeasurement = false;
  864. /**
  865. * Track whether this resolver is currently scheduled to resolve
  866. * to allow it to be cancelled and resumed externally.
  867. */
  868. this.isScheduled = false;
  869. this.unresolvedKeyframes = [...unresolvedKeyframes];
  870. this.onComplete = onComplete;
  871. this.name = name;
  872. this.motionValue = motionValue;
  873. this.element = element;
  874. this.isAsync = isAsync;
  875. }
  876. scheduleResolve() {
  877. this.isScheduled = true;
  878. if (this.isAsync) {
  879. toResolve.add(this);
  880. if (!isScheduled) {
  881. isScheduled = true;
  882. motionDom.frame.read(readAllKeyframes);
  883. motionDom.frame.resolveKeyframes(measureAllKeyframes);
  884. }
  885. }
  886. else {
  887. this.readKeyframes();
  888. this.complete();
  889. }
  890. }
  891. readKeyframes() {
  892. const { unresolvedKeyframes, name, element, motionValue } = this;
  893. /**
  894. * If a keyframe is null, we hydrate it either by reading it from
  895. * the instance, or propagating from previous keyframes.
  896. */
  897. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  898. if (unresolvedKeyframes[i] === null) {
  899. /**
  900. * If the first keyframe is null, we need to find its value by sampling the element
  901. */
  902. if (i === 0) {
  903. const currentValue = motionValue?.get();
  904. const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
  905. if (currentValue !== undefined) {
  906. unresolvedKeyframes[0] = currentValue;
  907. }
  908. else if (element && name) {
  909. const valueAsRead = element.readValue(name, finalKeyframe);
  910. if (valueAsRead !== undefined && valueAsRead !== null) {
  911. unresolvedKeyframes[0] = valueAsRead;
  912. }
  913. }
  914. if (unresolvedKeyframes[0] === undefined) {
  915. unresolvedKeyframes[0] = finalKeyframe;
  916. }
  917. if (motionValue && currentValue === undefined) {
  918. motionValue.set(unresolvedKeyframes[0]);
  919. }
  920. }
  921. else {
  922. unresolvedKeyframes[i] = unresolvedKeyframes[i - 1];
  923. }
  924. }
  925. }
  926. }
  927. setFinalKeyframe() { }
  928. measureInitialState() { }
  929. renderEndStyles() { }
  930. measureEndState() { }
  931. complete() {
  932. this.isComplete = true;
  933. this.onComplete(this.unresolvedKeyframes, this.finalKeyframe);
  934. toResolve.delete(this);
  935. }
  936. cancel() {
  937. if (!this.isComplete) {
  938. this.isScheduled = false;
  939. toResolve.delete(this);
  940. }
  941. }
  942. resume() {
  943. if (!this.isComplete)
  944. this.scheduleResolve();
  945. }
  946. }
  947. /**
  948. * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
  949. */
  950. const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
  951. const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
  952. const isCSSVariableName =
  953. /*@__PURE__*/ checkStringStartsWith("--");
  954. const startsAsVariableToken =
  955. /*@__PURE__*/ checkStringStartsWith("var(--");
  956. const isCSSVariableToken = (value) => {
  957. const startsWithToken = startsAsVariableToken(value);
  958. if (!startsWithToken)
  959. return false;
  960. // Ensure any comments are stripped from the value as this can harm performance of the regex.
  961. return singleCssVariableRegex.test(value.split("/*")[0].trim());
  962. };
  963. const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu;
  964. /**
  965. * Parse Framer's special CSS variable format into a CSS token and a fallback.
  966. *
  967. * ```
  968. * `var(--foo, #fff)` => [`--foo`, '#fff']
  969. * ```
  970. *
  971. * @param current
  972. */
  973. const splitCSSVariableRegex =
  974. // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
  975. /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
  976. function parseCSSVariable(current) {
  977. const match = splitCSSVariableRegex.exec(current);
  978. if (!match)
  979. return [,];
  980. const [, token1, token2, fallback] = match;
  981. return [`--${token1 ?? token2}`, fallback];
  982. }
  983. const maxDepth = 4;
  984. function getVariableValue(current, element, depth = 1) {
  985. motionUtils.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
  986. const [token, fallback] = parseCSSVariable(current);
  987. // No CSS variable detected
  988. if (!token)
  989. return;
  990. // Attempt to read this CSS variable off the element
  991. const resolved = window.getComputedStyle(element).getPropertyValue(token);
  992. if (resolved) {
  993. const trimmed = resolved.trim();
  994. return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
  995. }
  996. return isCSSVariableToken(fallback)
  997. ? getVariableValue(fallback, element, depth + 1)
  998. : fallback;
  999. }
  1000. /**
  1001. * Tests a provided value against a ValueType
  1002. */
  1003. const testValueType = (v) => (type) => type.test(v);
  1004. /**
  1005. * ValueType for "auto"
  1006. */
  1007. const auto = {
  1008. test: (v) => v === "auto",
  1009. parse: (v) => v,
  1010. };
  1011. /**
  1012. * A list of value types commonly used for dimensions
  1013. */
  1014. const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
  1015. /**
  1016. * Tests a dimensional value against the list of dimension ValueTypes
  1017. */
  1018. const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
  1019. class DOMKeyframesResolver extends KeyframeResolver {
  1020. constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
  1021. super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
  1022. }
  1023. readKeyframes() {
  1024. const { unresolvedKeyframes, element, name } = this;
  1025. if (!element || !element.current)
  1026. return;
  1027. super.readKeyframes();
  1028. /**
  1029. * If any keyframe is a CSS variable, we need to find its value by sampling the element
  1030. */
  1031. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1032. let keyframe = unresolvedKeyframes[i];
  1033. if (typeof keyframe === "string") {
  1034. keyframe = keyframe.trim();
  1035. if (isCSSVariableToken(keyframe)) {
  1036. const resolved = getVariableValue(keyframe, element.current);
  1037. if (resolved !== undefined) {
  1038. unresolvedKeyframes[i] = resolved;
  1039. }
  1040. if (i === unresolvedKeyframes.length - 1) {
  1041. this.finalKeyframe = keyframe;
  1042. }
  1043. }
  1044. }
  1045. }
  1046. /**
  1047. * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
  1048. * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
  1049. * have a far bigger performance impact.
  1050. */
  1051. this.resolveNoneKeyframes();
  1052. /**
  1053. * Check to see if unit type has changed. If so schedule jobs that will
  1054. * temporarily set styles to the destination keyframes.
  1055. * Skip if we have more than two keyframes or this isn't a positional value.
  1056. * TODO: We can throw if there are multiple keyframes and the value type changes.
  1057. */
  1058. if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
  1059. return;
  1060. }
  1061. const [origin, target] = unresolvedKeyframes;
  1062. const originType = findDimensionValueType(origin);
  1063. const targetType = findDimensionValueType(target);
  1064. /**
  1065. * Either we don't recognise these value types or we can animate between them.
  1066. */
  1067. if (originType === targetType)
  1068. return;
  1069. /**
  1070. * If both values are numbers or pixels, we can animate between them by
  1071. * converting them to numbers.
  1072. */
  1073. if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
  1074. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1075. const value = unresolvedKeyframes[i];
  1076. if (typeof value === "string") {
  1077. unresolvedKeyframes[i] = parseFloat(value);
  1078. }
  1079. }
  1080. }
  1081. else {
  1082. /**
  1083. * Else, the only way to resolve this is by measuring the element.
  1084. */
  1085. this.needsMeasurement = true;
  1086. }
  1087. }
  1088. resolveNoneKeyframes() {
  1089. const { unresolvedKeyframes, name } = this;
  1090. const noneKeyframeIndexes = [];
  1091. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1092. if (isNone(unresolvedKeyframes[i])) {
  1093. noneKeyframeIndexes.push(i);
  1094. }
  1095. }
  1096. if (noneKeyframeIndexes.length) {
  1097. makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
  1098. }
  1099. }
  1100. measureInitialState() {
  1101. const { element, unresolvedKeyframes, name } = this;
  1102. if (!element || !element.current)
  1103. return;
  1104. if (name === "height") {
  1105. this.suspendedScrollY = window.pageYOffset;
  1106. }
  1107. this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  1108. unresolvedKeyframes[0] = this.measuredOrigin;
  1109. // Set final key frame to measure after next render
  1110. const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
  1111. if (measureKeyframe !== undefined) {
  1112. element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
  1113. }
  1114. }
  1115. measureEndState() {
  1116. const { element, name, unresolvedKeyframes } = this;
  1117. if (!element || !element.current)
  1118. return;
  1119. const value = element.getValue(name);
  1120. value && value.jump(this.measuredOrigin, false);
  1121. const finalKeyframeIndex = unresolvedKeyframes.length - 1;
  1122. const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
  1123. unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  1124. if (finalKeyframe !== null && this.finalKeyframe === undefined) {
  1125. this.finalKeyframe = finalKeyframe;
  1126. }
  1127. // If we removed transform values, reapply them before the next render
  1128. if (this.removedTransforms?.length) {
  1129. this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
  1130. element
  1131. .getValue(unsetTransformName)
  1132. .set(unsetTransformValue);
  1133. });
  1134. }
  1135. this.resolveNoneKeyframes();
  1136. }
  1137. }
  1138. /**
  1139. * Check if a value is animatable. Examples:
  1140. *
  1141. * ✅: 100, "100px", "#fff"
  1142. * ❌: "block", "url(2.jpg)"
  1143. * @param value
  1144. *
  1145. * @internal
  1146. */
  1147. const isAnimatable = (value, name) => {
  1148. // If the list of keys tat might be non-animatable grows, replace with Set
  1149. if (name === "zIndex")
  1150. return false;
  1151. // If it's a number or a keyframes array, we can animate it. We might at some point
  1152. // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
  1153. // but for now lets leave it like this for performance reasons
  1154. if (typeof value === "number" || Array.isArray(value))
  1155. return true;
  1156. if (typeof value === "string" && // It's animatable if we have a string
  1157. (complex.test(value) || value === "0") && // And it contains numbers and/or colors
  1158. !value.startsWith("url(") // Unless it starts with "url("
  1159. ) {
  1160. return true;
  1161. }
  1162. return false;
  1163. };
  1164. function hasKeyframesChanged(keyframes) {
  1165. const current = keyframes[0];
  1166. if (keyframes.length === 1)
  1167. return true;
  1168. for (let i = 0; i < keyframes.length; i++) {
  1169. if (keyframes[i] !== current)
  1170. return true;
  1171. }
  1172. }
  1173. function canAnimate(keyframes, name, type, velocity) {
  1174. /**
  1175. * Check if we're able to animate between the start and end keyframes,
  1176. * and throw a warning if we're attempting to animate between one that's
  1177. * animatable and another that isn't.
  1178. */
  1179. const originKeyframe = keyframes[0];
  1180. if (originKeyframe === null)
  1181. return false;
  1182. /**
  1183. * These aren't traditionally animatable but we do support them.
  1184. * In future we could look into making this more generic or replacing
  1185. * this function with mix() === mixImmediate
  1186. */
  1187. if (name === "display" || name === "visibility")
  1188. return true;
  1189. const targetKeyframe = keyframes[keyframes.length - 1];
  1190. const isOriginAnimatable = isAnimatable(originKeyframe, name);
  1191. const isTargetAnimatable = isAnimatable(targetKeyframe, name);
  1192. motionUtils.warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
  1193. // Always skip if any of these are true
  1194. if (!isOriginAnimatable || !isTargetAnimatable) {
  1195. return false;
  1196. }
  1197. return (hasKeyframesChanged(keyframes) ||
  1198. ((type === "spring" || motionDom.isGenerator(type)) && velocity));
  1199. }
  1200. const isNotNull = (value) => value !== null;
  1201. function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
  1202. const resolvedKeyframes = keyframes.filter(isNotNull);
  1203. const index = repeat && repeatType !== "loop" && repeat % 2 === 1
  1204. ? 0
  1205. : resolvedKeyframes.length - 1;
  1206. return !index || finalKeyframe === undefined
  1207. ? resolvedKeyframes[index]
  1208. : finalKeyframe;
  1209. }
  1210. /**
  1211. * Maximum time allowed between an animation being created and it being
  1212. * resolved for us to use the latter as the start time.
  1213. *
  1214. * This is to ensure that while we prefer to "start" an animation as soon
  1215. * as it's triggered, we also want to avoid a visual jump if there's a big delay
  1216. * between these two moments.
  1217. */
  1218. const MAX_RESOLVE_DELAY = 40;
  1219. class BaseAnimation {
  1220. constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", ...options }) {
  1221. // Track whether the animation has been stopped. Stopped animations won't restart.
  1222. this.isStopped = false;
  1223. this.hasAttemptedResolve = false;
  1224. this.createdAt = motionDom.time.now();
  1225. this.options = {
  1226. autoplay,
  1227. delay,
  1228. type,
  1229. repeat,
  1230. repeatDelay,
  1231. repeatType,
  1232. ...options,
  1233. };
  1234. this.updateFinishedPromise();
  1235. }
  1236. /**
  1237. * This method uses the createdAt and resolvedAt to calculate the
  1238. * animation startTime. *Ideally*, we would use the createdAt time as t=0
  1239. * as the following frame would then be the first frame of the animation in
  1240. * progress, which would feel snappier.
  1241. *
  1242. * However, if there's a delay (main thread work) between the creation of
  1243. * the animation and the first commited frame, we prefer to use resolvedAt
  1244. * to avoid a sudden jump into the animation.
  1245. */
  1246. calcStartTime() {
  1247. if (!this.resolvedAt)
  1248. return this.createdAt;
  1249. return this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
  1250. ? this.resolvedAt
  1251. : this.createdAt;
  1252. }
  1253. /**
  1254. * A getter for resolved data. If keyframes are not yet resolved, accessing
  1255. * this.resolved will synchronously flush all pending keyframe resolvers.
  1256. * This is a deoptimisation, but at its worst still batches read/writes.
  1257. */
  1258. get resolved() {
  1259. if (!this._resolved && !this.hasAttemptedResolve) {
  1260. flushKeyframeResolvers();
  1261. }
  1262. return this._resolved;
  1263. }
  1264. /**
  1265. * A method to be called when the keyframes resolver completes. This method
  1266. * will check if its possible to run the animation and, if not, skip it.
  1267. * Otherwise, it will call initPlayback on the implementing class.
  1268. */
  1269. onKeyframesResolved(keyframes, finalKeyframe) {
  1270. this.resolvedAt = motionDom.time.now();
  1271. this.hasAttemptedResolve = true;
  1272. const { name, type, velocity, delay, onComplete, onUpdate, isGenerator, } = this.options;
  1273. /**
  1274. * If we can't animate this value with the resolved keyframes
  1275. * then we should complete it immediately.
  1276. */
  1277. if (!isGenerator && !canAnimate(keyframes, name, type, velocity)) {
  1278. // Finish immediately
  1279. if (instantAnimationState.current || !delay) {
  1280. onUpdate &&
  1281. onUpdate(getFinalKeyframe(keyframes, this.options, finalKeyframe));
  1282. onComplete && onComplete();
  1283. this.resolveFinishedPromise();
  1284. return;
  1285. }
  1286. // Finish after a delay
  1287. else {
  1288. this.options.duration = 0;
  1289. }
  1290. }
  1291. const resolvedAnimation = this.initPlayback(keyframes, finalKeyframe);
  1292. if (resolvedAnimation === false)
  1293. return;
  1294. this._resolved = {
  1295. keyframes,
  1296. finalKeyframe,
  1297. ...resolvedAnimation,
  1298. };
  1299. this.onPostResolved();
  1300. }
  1301. onPostResolved() { }
  1302. /**
  1303. * Allows the returned animation to be awaited or promise-chained. Currently
  1304. * resolves when the animation finishes at all but in a future update could/should
  1305. * reject if its cancels.
  1306. */
  1307. then(resolve, reject) {
  1308. return this.currentFinishedPromise.then(resolve, reject);
  1309. }
  1310. flatten() {
  1311. if (!this.options.allowFlatten)
  1312. return;
  1313. this.options.type = "keyframes";
  1314. this.options.ease = "linear";
  1315. }
  1316. updateFinishedPromise() {
  1317. this.currentFinishedPromise = new Promise((resolve) => {
  1318. this.resolveFinishedPromise = resolve;
  1319. });
  1320. }
  1321. }
  1322. // Adapted from https://gist.github.com/mjackson/5311256
  1323. function hueToRgb(p, q, t) {
  1324. if (t < 0)
  1325. t += 1;
  1326. if (t > 1)
  1327. t -= 1;
  1328. if (t < 1 / 6)
  1329. return p + (q - p) * 6 * t;
  1330. if (t < 1 / 2)
  1331. return q;
  1332. if (t < 2 / 3)
  1333. return p + (q - p) * (2 / 3 - t) * 6;
  1334. return p;
  1335. }
  1336. function hslaToRgba({ hue, saturation, lightness, alpha }) {
  1337. hue /= 360;
  1338. saturation /= 100;
  1339. lightness /= 100;
  1340. let red = 0;
  1341. let green = 0;
  1342. let blue = 0;
  1343. if (!saturation) {
  1344. red = green = blue = lightness;
  1345. }
  1346. else {
  1347. const q = lightness < 0.5
  1348. ? lightness * (1 + saturation)
  1349. : lightness + saturation - lightness * saturation;
  1350. const p = 2 * lightness - q;
  1351. red = hueToRgb(p, q, hue + 1 / 3);
  1352. green = hueToRgb(p, q, hue);
  1353. blue = hueToRgb(p, q, hue - 1 / 3);
  1354. }
  1355. return {
  1356. red: Math.round(red * 255),
  1357. green: Math.round(green * 255),
  1358. blue: Math.round(blue * 255),
  1359. alpha,
  1360. };
  1361. }
  1362. function mixImmediate(a, b) {
  1363. return (p) => (p > 0 ? b : a);
  1364. }
  1365. // Linear color space blending
  1366. // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
  1367. // Demonstrated http://codepen.io/osublake/pen/xGVVaN
  1368. const mixLinearColor = (from, to, v) => {
  1369. const fromExpo = from * from;
  1370. const expo = v * (to * to - fromExpo) + fromExpo;
  1371. return expo < 0 ? 0 : Math.sqrt(expo);
  1372. };
  1373. const colorTypes = [hex, rgba, hsla];
  1374. const getColorType = (v) => colorTypes.find((type) => type.test(v));
  1375. function asRGBA(color) {
  1376. const type = getColorType(color);
  1377. motionUtils.warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
  1378. if (!Boolean(type))
  1379. return false;
  1380. let model = type.parse(color);
  1381. if (type === hsla) {
  1382. // TODO Remove this cast - needed since Motion's stricter typing
  1383. model = hslaToRgba(model);
  1384. }
  1385. return model;
  1386. }
  1387. const mixColor = (from, to) => {
  1388. const fromRGBA = asRGBA(from);
  1389. const toRGBA = asRGBA(to);
  1390. if (!fromRGBA || !toRGBA) {
  1391. return mixImmediate(from, to);
  1392. }
  1393. const blended = { ...fromRGBA };
  1394. return (v) => {
  1395. blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
  1396. blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
  1397. blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
  1398. blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v);
  1399. return rgba.transform(blended);
  1400. };
  1401. };
  1402. /**
  1403. * Pipe
  1404. * Compose other transformers to run linearily
  1405. * pipe(min(20), max(40))
  1406. * @param {...functions} transformers
  1407. * @return {function}
  1408. */
  1409. const combineFunctions = (a, b) => (v) => b(a(v));
  1410. const pipe = (...transformers) => transformers.reduce(combineFunctions);
  1411. const invisibleValues = new Set(["none", "hidden"]);
  1412. /**
  1413. * Returns a function that, when provided a progress value between 0 and 1,
  1414. * will return the "none" or "hidden" string only when the progress is that of
  1415. * the origin or target.
  1416. */
  1417. function mixVisibility(origin, target) {
  1418. if (invisibleValues.has(origin)) {
  1419. return (p) => (p <= 0 ? origin : target);
  1420. }
  1421. else {
  1422. return (p) => (p >= 1 ? target : origin);
  1423. }
  1424. }
  1425. function mixNumber(a, b) {
  1426. return (p) => mixNumber$1(a, b, p);
  1427. }
  1428. function getMixer(a) {
  1429. if (typeof a === "number") {
  1430. return mixNumber;
  1431. }
  1432. else if (typeof a === "string") {
  1433. return isCSSVariableToken(a)
  1434. ? mixImmediate
  1435. : color.test(a)
  1436. ? mixColor
  1437. : mixComplex;
  1438. }
  1439. else if (Array.isArray(a)) {
  1440. return mixArray;
  1441. }
  1442. else if (typeof a === "object") {
  1443. return color.test(a) ? mixColor : mixObject;
  1444. }
  1445. return mixImmediate;
  1446. }
  1447. function mixArray(a, b) {
  1448. const output = [...a];
  1449. const numValues = output.length;
  1450. const blendValue = a.map((v, i) => getMixer(v)(v, b[i]));
  1451. return (p) => {
  1452. for (let i = 0; i < numValues; i++) {
  1453. output[i] = blendValue[i](p);
  1454. }
  1455. return output;
  1456. };
  1457. }
  1458. function mixObject(a, b) {
  1459. const output = { ...a, ...b };
  1460. const blendValue = {};
  1461. for (const key in output) {
  1462. if (a[key] !== undefined && b[key] !== undefined) {
  1463. blendValue[key] = getMixer(a[key])(a[key], b[key]);
  1464. }
  1465. }
  1466. return (v) => {
  1467. for (const key in blendValue) {
  1468. output[key] = blendValue[key](v);
  1469. }
  1470. return output;
  1471. };
  1472. }
  1473. function matchOrder(origin, target) {
  1474. const orderedOrigin = [];
  1475. const pointers = { color: 0, var: 0, number: 0 };
  1476. for (let i = 0; i < target.values.length; i++) {
  1477. const type = target.types[i];
  1478. const originIndex = origin.indexes[type][pointers[type]];
  1479. const originValue = origin.values[originIndex] ?? 0;
  1480. orderedOrigin[i] = originValue;
  1481. pointers[type]++;
  1482. }
  1483. return orderedOrigin;
  1484. }
  1485. const mixComplex = (origin, target) => {
  1486. const template = complex.createTransformer(target);
  1487. const originStats = analyseComplexValue(origin);
  1488. const targetStats = analyseComplexValue(target);
  1489. const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length &&
  1490. originStats.indexes.color.length === targetStats.indexes.color.length &&
  1491. originStats.indexes.number.length >= targetStats.indexes.number.length;
  1492. if (canInterpolate) {
  1493. if ((invisibleValues.has(origin) &&
  1494. !targetStats.values.length) ||
  1495. (invisibleValues.has(target) &&
  1496. !originStats.values.length)) {
  1497. return mixVisibility(origin, target);
  1498. }
  1499. return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template);
  1500. }
  1501. else {
  1502. motionUtils.warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
  1503. return mixImmediate(origin, target);
  1504. }
  1505. };
  1506. function mix(from, to, p) {
  1507. if (typeof from === "number" &&
  1508. typeof to === "number" &&
  1509. typeof p === "number") {
  1510. return mixNumber$1(from, to, p);
  1511. }
  1512. const mixer = getMixer(from);
  1513. return mixer(from, to);
  1514. }
  1515. const velocitySampleDuration = 5; // ms
  1516. function calcGeneratorVelocity(resolveValue, t, current) {
  1517. const prevT = Math.max(t - velocitySampleDuration, 0);
  1518. return motionUtils.velocityPerSecond(current - resolveValue(prevT), t - prevT);
  1519. }
  1520. const springDefaults = {
  1521. // Default spring physics
  1522. stiffness: 100,
  1523. damping: 10,
  1524. mass: 1.0,
  1525. velocity: 0.0,
  1526. // Default duration/bounce-based options
  1527. duration: 800, // in ms
  1528. bounce: 0.3,
  1529. visualDuration: 0.3, // in seconds
  1530. // Rest thresholds
  1531. restSpeed: {
  1532. granular: 0.01,
  1533. default: 2,
  1534. },
  1535. restDelta: {
  1536. granular: 0.005,
  1537. default: 0.5,
  1538. },
  1539. // Limits
  1540. minDuration: 0.01, // in seconds
  1541. maxDuration: 10.0, // in seconds
  1542. minDamping: 0.05,
  1543. maxDamping: 1,
  1544. };
  1545. const safeMin = 0.001;
  1546. function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
  1547. let envelope;
  1548. let derivative;
  1549. motionUtils.warning(duration <= motionUtils.secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
  1550. let dampingRatio = 1 - bounce;
  1551. /**
  1552. * Restrict dampingRatio and duration to within acceptable ranges.
  1553. */
  1554. dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
  1555. duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, motionUtils.millisecondsToSeconds(duration));
  1556. if (dampingRatio < 1) {
  1557. /**
  1558. * Underdamped spring
  1559. */
  1560. envelope = (undampedFreq) => {
  1561. const exponentialDecay = undampedFreq * dampingRatio;
  1562. const delta = exponentialDecay * duration;
  1563. const a = exponentialDecay - velocity;
  1564. const b = calcAngularFreq(undampedFreq, dampingRatio);
  1565. const c = Math.exp(-delta);
  1566. return safeMin - (a / b) * c;
  1567. };
  1568. derivative = (undampedFreq) => {
  1569. const exponentialDecay = undampedFreq * dampingRatio;
  1570. const delta = exponentialDecay * duration;
  1571. const d = delta * velocity + velocity;
  1572. const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
  1573. const f = Math.exp(-delta);
  1574. const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
  1575. const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
  1576. return (factor * ((d - e) * f)) / g;
  1577. };
  1578. }
  1579. else {
  1580. /**
  1581. * Critically-damped spring
  1582. */
  1583. envelope = (undampedFreq) => {
  1584. const a = Math.exp(-undampedFreq * duration);
  1585. const b = (undampedFreq - velocity) * duration + 1;
  1586. return -safeMin + a * b;
  1587. };
  1588. derivative = (undampedFreq) => {
  1589. const a = Math.exp(-undampedFreq * duration);
  1590. const b = (velocity - undampedFreq) * (duration * duration);
  1591. return a * b;
  1592. };
  1593. }
  1594. const initialGuess = 5 / duration;
  1595. const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
  1596. duration = motionUtils.secondsToMilliseconds(duration);
  1597. if (isNaN(undampedFreq)) {
  1598. return {
  1599. stiffness: springDefaults.stiffness,
  1600. damping: springDefaults.damping,
  1601. duration,
  1602. };
  1603. }
  1604. else {
  1605. const stiffness = Math.pow(undampedFreq, 2) * mass;
  1606. return {
  1607. stiffness,
  1608. damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
  1609. duration,
  1610. };
  1611. }
  1612. }
  1613. const rootIterations = 12;
  1614. function approximateRoot(envelope, derivative, initialGuess) {
  1615. let result = initialGuess;
  1616. for (let i = 1; i < rootIterations; i++) {
  1617. result = result - envelope(result) / derivative(result);
  1618. }
  1619. return result;
  1620. }
  1621. function calcAngularFreq(undampedFreq, dampingRatio) {
  1622. return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
  1623. }
  1624. const durationKeys = ["duration", "bounce"];
  1625. const physicsKeys = ["stiffness", "damping", "mass"];
  1626. function isSpringType(options, keys) {
  1627. return keys.some((key) => options[key] !== undefined);
  1628. }
  1629. function getSpringOptions(options) {
  1630. let springOptions = {
  1631. velocity: springDefaults.velocity,
  1632. stiffness: springDefaults.stiffness,
  1633. damping: springDefaults.damping,
  1634. mass: springDefaults.mass,
  1635. isResolvedFromDuration: false,
  1636. ...options,
  1637. };
  1638. // stiffness/damping/mass overrides duration/bounce
  1639. if (!isSpringType(options, physicsKeys) &&
  1640. isSpringType(options, durationKeys)) {
  1641. if (options.visualDuration) {
  1642. const visualDuration = options.visualDuration;
  1643. const root = (2 * Math.PI) / (visualDuration * 1.2);
  1644. const stiffness = root * root;
  1645. const damping = 2 *
  1646. clamp(0.05, 1, 1 - (options.bounce || 0)) *
  1647. Math.sqrt(stiffness);
  1648. springOptions = {
  1649. ...springOptions,
  1650. mass: springDefaults.mass,
  1651. stiffness,
  1652. damping,
  1653. };
  1654. }
  1655. else {
  1656. const derived = findSpring(options);
  1657. springOptions = {
  1658. ...springOptions,
  1659. ...derived,
  1660. mass: springDefaults.mass,
  1661. };
  1662. springOptions.isResolvedFromDuration = true;
  1663. }
  1664. }
  1665. return springOptions;
  1666. }
  1667. function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
  1668. const options = typeof optionsOrVisualDuration !== "object"
  1669. ? {
  1670. visualDuration: optionsOrVisualDuration,
  1671. keyframes: [0, 1],
  1672. bounce,
  1673. }
  1674. : optionsOrVisualDuration;
  1675. let { restSpeed, restDelta } = options;
  1676. const origin = options.keyframes[0];
  1677. const target = options.keyframes[options.keyframes.length - 1];
  1678. /**
  1679. * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
  1680. * to reduce GC during animation.
  1681. */
  1682. const state = { done: false, value: origin };
  1683. const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
  1684. ...options,
  1685. velocity: -motionUtils.millisecondsToSeconds(options.velocity || 0),
  1686. });
  1687. const initialVelocity = velocity || 0.0;
  1688. const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
  1689. const initialDelta = target - origin;
  1690. const undampedAngularFreq = motionUtils.millisecondsToSeconds(Math.sqrt(stiffness / mass));
  1691. /**
  1692. * If we're working on a granular scale, use smaller defaults for determining
  1693. * when the spring is finished.
  1694. *
  1695. * These defaults have been selected emprically based on what strikes a good
  1696. * ratio between feeling good and finishing as soon as changes are imperceptible.
  1697. */
  1698. const isGranularScale = Math.abs(initialDelta) < 5;
  1699. restSpeed || (restSpeed = isGranularScale
  1700. ? springDefaults.restSpeed.granular
  1701. : springDefaults.restSpeed.default);
  1702. restDelta || (restDelta = isGranularScale
  1703. ? springDefaults.restDelta.granular
  1704. : springDefaults.restDelta.default);
  1705. let resolveSpring;
  1706. if (dampingRatio < 1) {
  1707. const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
  1708. // Underdamped spring
  1709. resolveSpring = (t) => {
  1710. const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
  1711. return (target -
  1712. envelope *
  1713. (((initialVelocity +
  1714. dampingRatio * undampedAngularFreq * initialDelta) /
  1715. angularFreq) *
  1716. Math.sin(angularFreq * t) +
  1717. initialDelta * Math.cos(angularFreq * t)));
  1718. };
  1719. }
  1720. else if (dampingRatio === 1) {
  1721. // Critically damped spring
  1722. resolveSpring = (t) => target -
  1723. Math.exp(-undampedAngularFreq * t) *
  1724. (initialDelta +
  1725. (initialVelocity + undampedAngularFreq * initialDelta) * t);
  1726. }
  1727. else {
  1728. // Overdamped spring
  1729. const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
  1730. resolveSpring = (t) => {
  1731. const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
  1732. // When performing sinh or cosh values can hit Infinity so we cap them here
  1733. const freqForT = Math.min(dampedAngularFreq * t, 300);
  1734. return (target -
  1735. (envelope *
  1736. ((initialVelocity +
  1737. dampingRatio * undampedAngularFreq * initialDelta) *
  1738. Math.sinh(freqForT) +
  1739. dampedAngularFreq *
  1740. initialDelta *
  1741. Math.cosh(freqForT))) /
  1742. dampedAngularFreq);
  1743. };
  1744. }
  1745. const generator = {
  1746. calculatedDuration: isResolvedFromDuration ? duration || null : null,
  1747. next: (t) => {
  1748. const current = resolveSpring(t);
  1749. if (!isResolvedFromDuration) {
  1750. let currentVelocity = 0.0;
  1751. /**
  1752. * We only need to calculate velocity for under-damped springs
  1753. * as over- and critically-damped springs can't overshoot, so
  1754. * checking only for displacement is enough.
  1755. */
  1756. if (dampingRatio < 1) {
  1757. currentVelocity =
  1758. t === 0
  1759. ? motionUtils.secondsToMilliseconds(initialVelocity)
  1760. : calcGeneratorVelocity(resolveSpring, t, current);
  1761. }
  1762. const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
  1763. const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
  1764. state.done =
  1765. isBelowVelocityThreshold && isBelowDisplacementThreshold;
  1766. }
  1767. else {
  1768. state.done = t >= duration;
  1769. }
  1770. state.value = state.done ? target : current;
  1771. return state;
  1772. },
  1773. toString: () => {
  1774. const calculatedDuration = Math.min(motionDom.calcGeneratorDuration(generator), motionDom.maxGeneratorDuration);
  1775. const easing = motionDom.generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
  1776. return calculatedDuration + "ms " + easing;
  1777. },
  1778. toTransition: () => { },
  1779. };
  1780. return generator;
  1781. }
  1782. spring.applyToOptions = (options) => {
  1783. const generatorOptions = motionDom.createGeneratorEasing(options, 100, spring);
  1784. options.ease = motionDom.supportsLinearEasing() ? generatorOptions.ease : "easeOut";
  1785. options.duration = motionUtils.secondsToMilliseconds(generatorOptions.duration);
  1786. options.type = "keyframes";
  1787. return options;
  1788. };
  1789. function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
  1790. const origin = keyframes[0];
  1791. const state = {
  1792. done: false,
  1793. value: origin,
  1794. };
  1795. const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
  1796. const nearestBoundary = (v) => {
  1797. if (min === undefined)
  1798. return max;
  1799. if (max === undefined)
  1800. return min;
  1801. return Math.abs(min - v) < Math.abs(max - v) ? min : max;
  1802. };
  1803. let amplitude = power * velocity;
  1804. const ideal = origin + amplitude;
  1805. const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
  1806. /**
  1807. * If the target has changed we need to re-calculate the amplitude, otherwise
  1808. * the animation will start from the wrong position.
  1809. */
  1810. if (target !== ideal)
  1811. amplitude = target - origin;
  1812. const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
  1813. const calcLatest = (t) => target + calcDelta(t);
  1814. const applyFriction = (t) => {
  1815. const delta = calcDelta(t);
  1816. const latest = calcLatest(t);
  1817. state.done = Math.abs(delta) <= restDelta;
  1818. state.value = state.done ? target : latest;
  1819. };
  1820. /**
  1821. * Ideally this would resolve for t in a stateless way, we could
  1822. * do that by always precalculating the animation but as we know
  1823. * this will be done anyway we can assume that spring will
  1824. * be discovered during that.
  1825. */
  1826. let timeReachedBoundary;
  1827. let spring$1;
  1828. const checkCatchBoundary = (t) => {
  1829. if (!isOutOfBounds(state.value))
  1830. return;
  1831. timeReachedBoundary = t;
  1832. spring$1 = spring({
  1833. keyframes: [state.value, nearestBoundary(state.value)],
  1834. velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
  1835. damping: bounceDamping,
  1836. stiffness: bounceStiffness,
  1837. restDelta,
  1838. restSpeed,
  1839. });
  1840. };
  1841. checkCatchBoundary(0);
  1842. return {
  1843. calculatedDuration: null,
  1844. next: (t) => {
  1845. /**
  1846. * We need to resolve the friction to figure out if we need a
  1847. * spring but we don't want to do this twice per frame. So here
  1848. * we flag if we updated for this frame and later if we did
  1849. * we can skip doing it again.
  1850. */
  1851. let hasUpdatedFrame = false;
  1852. if (!spring$1 && timeReachedBoundary === undefined) {
  1853. hasUpdatedFrame = true;
  1854. applyFriction(t);
  1855. checkCatchBoundary(t);
  1856. }
  1857. /**
  1858. * If we have a spring and the provided t is beyond the moment the friction
  1859. * animation crossed the min/max boundary, use the spring.
  1860. */
  1861. if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
  1862. return spring$1.next(t - timeReachedBoundary);
  1863. }
  1864. else {
  1865. !hasUpdatedFrame && applyFriction(t);
  1866. return state;
  1867. }
  1868. },
  1869. };
  1870. }
  1871. const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
  1872. const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
  1873. const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
  1874. const isEasingArray = (ease) => {
  1875. return Array.isArray(ease) && typeof ease[0] !== "number";
  1876. };
  1877. const easingLookup = {
  1878. linear: motionUtils.noop,
  1879. easeIn,
  1880. easeInOut,
  1881. easeOut,
  1882. circIn,
  1883. circInOut,
  1884. circOut,
  1885. backIn,
  1886. backInOut,
  1887. backOut,
  1888. anticipate,
  1889. };
  1890. const easingDefinitionToFunction = (definition) => {
  1891. if (motionDom.isBezierDefinition(definition)) {
  1892. // If cubic bezier definition, create bezier curve
  1893. motionUtils.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
  1894. const [x1, y1, x2, y2] = definition;
  1895. return cubicBezier(x1, y1, x2, y2);
  1896. }
  1897. else if (typeof definition === "string") {
  1898. // Else lookup from table
  1899. motionUtils.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
  1900. return easingLookup[definition];
  1901. }
  1902. return definition;
  1903. };
  1904. function createMixers(output, ease, customMixer) {
  1905. const mixers = [];
  1906. const mixerFactory = customMixer || mix;
  1907. const numMixers = output.length - 1;
  1908. for (let i = 0; i < numMixers; i++) {
  1909. let mixer = mixerFactory(output[i], output[i + 1]);
  1910. if (ease) {
  1911. const easingFunction = Array.isArray(ease) ? ease[i] || motionUtils.noop : ease;
  1912. mixer = pipe(easingFunction, mixer);
  1913. }
  1914. mixers.push(mixer);
  1915. }
  1916. return mixers;
  1917. }
  1918. /**
  1919. * Create a function that maps from a numerical input array to a generic output array.
  1920. *
  1921. * Accepts:
  1922. * - Numbers
  1923. * - Colors (hex, hsl, hsla, rgb, rgba)
  1924. * - Complex (combinations of one or more numbers or strings)
  1925. *
  1926. * ```jsx
  1927. * const mixColor = interpolate([0, 1], ['#fff', '#000'])
  1928. *
  1929. * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
  1930. * ```
  1931. *
  1932. * TODO Revist this approach once we've moved to data models for values,
  1933. * probably not needed to pregenerate mixer functions.
  1934. *
  1935. * @public
  1936. */
  1937. function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
  1938. const inputLength = input.length;
  1939. motionUtils.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
  1940. /**
  1941. * If we're only provided a single input, we can just make a function
  1942. * that returns the output.
  1943. */
  1944. if (inputLength === 1)
  1945. return () => output[0];
  1946. if (inputLength === 2 && output[0] === output[1])
  1947. return () => output[1];
  1948. const isZeroDeltaRange = input[0] === input[1];
  1949. // If input runs highest -> lowest, reverse both arrays
  1950. if (input[0] > input[inputLength - 1]) {
  1951. input = [...input].reverse();
  1952. output = [...output].reverse();
  1953. }
  1954. const mixers = createMixers(output, ease, mixer);
  1955. const numMixers = mixers.length;
  1956. const interpolator = (v) => {
  1957. if (isZeroDeltaRange && v < input[0])
  1958. return output[0];
  1959. let i = 0;
  1960. if (numMixers > 1) {
  1961. for (; i < input.length - 2; i++) {
  1962. if (v < input[i + 1])
  1963. break;
  1964. }
  1965. }
  1966. const progressInRange = motionUtils.progress(input[i], input[i + 1], v);
  1967. return mixers[i](progressInRange);
  1968. };
  1969. return isClamp
  1970. ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
  1971. : interpolator;
  1972. }
  1973. function fillOffset(offset, remaining) {
  1974. const min = offset[offset.length - 1];
  1975. for (let i = 1; i <= remaining; i++) {
  1976. const offsetProgress = motionUtils.progress(0, remaining, i);
  1977. offset.push(mixNumber$1(min, 1, offsetProgress));
  1978. }
  1979. }
  1980. function defaultOffset(arr) {
  1981. const offset = [0];
  1982. fillOffset(offset, arr.length - 1);
  1983. return offset;
  1984. }
  1985. function convertOffsetToTimes(offset, duration) {
  1986. return offset.map((o) => o * duration);
  1987. }
  1988. function defaultEasing(values, easing) {
  1989. return values.map(() => easing || easeInOut).splice(0, values.length - 1);
  1990. }
  1991. function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
  1992. /**
  1993. * Easing functions can be externally defined as strings. Here we convert them
  1994. * into actual functions.
  1995. */
  1996. const easingFunctions = isEasingArray(ease)
  1997. ? ease.map(easingDefinitionToFunction)
  1998. : easingDefinitionToFunction(ease);
  1999. /**
  2000. * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
  2001. * to reduce GC during animation.
  2002. */
  2003. const state = {
  2004. done: false,
  2005. value: keyframeValues[0],
  2006. };
  2007. /**
  2008. * Create a times array based on the provided 0-1 offsets
  2009. */
  2010. const absoluteTimes = convertOffsetToTimes(
  2011. // Only use the provided offsets if they're the correct length
  2012. // TODO Maybe we should warn here if there's a length mismatch
  2013. times && times.length === keyframeValues.length
  2014. ? times
  2015. : defaultOffset(keyframeValues), duration);
  2016. const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
  2017. ease: Array.isArray(easingFunctions)
  2018. ? easingFunctions
  2019. : defaultEasing(keyframeValues, easingFunctions),
  2020. });
  2021. return {
  2022. calculatedDuration: duration,
  2023. next: (t) => {
  2024. state.value = mapTimeToKeyframe(t);
  2025. state.done = t >= duration;
  2026. return state;
  2027. },
  2028. };
  2029. }
  2030. const frameloopDriver = (update) => {
  2031. const passTimestamp = ({ timestamp }) => update(timestamp);
  2032. return {
  2033. start: () => motionDom.frame.update(passTimestamp, true),
  2034. stop: () => motionDom.cancelFrame(passTimestamp),
  2035. /**
  2036. * If we're processing this frame we can use the
  2037. * framelocked timestamp to keep things in sync.
  2038. */
  2039. now: () => (motionDom.frameData.isProcessing ? motionDom.frameData.timestamp : motionDom.time.now()),
  2040. };
  2041. };
  2042. const generators = {
  2043. decay: inertia,
  2044. inertia,
  2045. tween: keyframes,
  2046. keyframes: keyframes,
  2047. spring,
  2048. };
  2049. const percentToProgress = (percent) => percent / 100;
  2050. /**
  2051. * Animation that runs on the main thread. Designed to be WAAPI-spec in the subset of
  2052. * features we expose publically. Mostly the compatibility is to ensure visual identity
  2053. * between both WAAPI and main thread animations.
  2054. */
  2055. class MainThreadAnimation extends BaseAnimation {
  2056. constructor(options) {
  2057. super(options);
  2058. /**
  2059. * The time at which the animation was paused.
  2060. */
  2061. this.holdTime = null;
  2062. /**
  2063. * The time at which the animation was cancelled.
  2064. */
  2065. this.cancelTime = null;
  2066. /**
  2067. * The current time of the animation.
  2068. */
  2069. this.currentTime = 0;
  2070. /**
  2071. * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
  2072. */
  2073. this.playbackSpeed = 1;
  2074. /**
  2075. * The state of the animation to apply when the animation is resolved. This
  2076. * allows calls to the public API to control the animation before it is resolved,
  2077. * without us having to resolve it first.
  2078. */
  2079. this.pendingPlayState = "running";
  2080. /**
  2081. * The time at which the animation was started.
  2082. */
  2083. this.startTime = null;
  2084. this.state = "idle";
  2085. /**
  2086. * This method is bound to the instance to fix a pattern where
  2087. * animation.stop is returned as a reference from a useEffect.
  2088. */
  2089. this.stop = () => {
  2090. this.resolver.cancel();
  2091. this.isStopped = true;
  2092. if (this.state === "idle")
  2093. return;
  2094. this.teardown();
  2095. const { onStop } = this.options;
  2096. onStop && onStop();
  2097. };
  2098. const { name, motionValue, element, keyframes } = this.options;
  2099. const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
  2100. const onResolved = (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe);
  2101. this.resolver = new KeyframeResolver$1(keyframes, onResolved, name, motionValue, element);
  2102. this.resolver.scheduleResolve();
  2103. }
  2104. flatten() {
  2105. super.flatten();
  2106. // If we've already resolved the animation, re-initialise it
  2107. if (this._resolved) {
  2108. Object.assign(this._resolved, this.initPlayback(this._resolved.keyframes));
  2109. }
  2110. }
  2111. initPlayback(keyframes$1) {
  2112. const { type = "keyframes", repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = this.options;
  2113. const generatorFactory = motionDom.isGenerator(type)
  2114. ? type
  2115. : generators[type] || keyframes;
  2116. /**
  2117. * If our generator doesn't support mixing numbers, we need to replace keyframes with
  2118. * [0, 100] and then make a function that maps that to the actual keyframes.
  2119. *
  2120. * 100 is chosen instead of 1 as it works nicer with spring animations.
  2121. */
  2122. let mapPercentToKeyframes;
  2123. let mirroredGenerator;
  2124. if (process.env.NODE_ENV !== "production" &&
  2125. generatorFactory !== keyframes) {
  2126. motionUtils.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
  2127. }
  2128. if (generatorFactory !== keyframes &&
  2129. typeof keyframes$1[0] !== "number") {
  2130. mapPercentToKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
  2131. keyframes$1 = [0, 100];
  2132. }
  2133. const generator = generatorFactory({ ...this.options, keyframes: keyframes$1 });
  2134. /**
  2135. * If we have a mirror repeat type we need to create a second generator that outputs the
  2136. * mirrored (not reversed) animation and later ping pong between the two generators.
  2137. */
  2138. if (repeatType === "mirror") {
  2139. mirroredGenerator = generatorFactory({
  2140. ...this.options,
  2141. keyframes: [...keyframes$1].reverse(),
  2142. velocity: -velocity,
  2143. });
  2144. }
  2145. /**
  2146. * If duration is undefined and we have repeat options,
  2147. * we need to calculate a duration from the generator.
  2148. *
  2149. * We set it to the generator itself to cache the duration.
  2150. * Any timeline resolver will need to have already precalculated
  2151. * the duration by this step.
  2152. */
  2153. if (generator.calculatedDuration === null) {
  2154. generator.calculatedDuration = motionDom.calcGeneratorDuration(generator);
  2155. }
  2156. const { calculatedDuration } = generator;
  2157. const resolvedDuration = calculatedDuration + repeatDelay;
  2158. const totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
  2159. return {
  2160. generator,
  2161. mirroredGenerator,
  2162. mapPercentToKeyframes,
  2163. calculatedDuration,
  2164. resolvedDuration,
  2165. totalDuration,
  2166. };
  2167. }
  2168. onPostResolved() {
  2169. const { autoplay = true } = this.options;
  2170. motionDom.activeAnimations.mainThread++;
  2171. this.play();
  2172. if (this.pendingPlayState === "paused" || !autoplay) {
  2173. this.pause();
  2174. }
  2175. else {
  2176. this.state = this.pendingPlayState;
  2177. }
  2178. }
  2179. tick(timestamp, sample = false) {
  2180. const { resolved } = this;
  2181. // If the animations has failed to resolve, return the final keyframe.
  2182. if (!resolved) {
  2183. const { keyframes } = this.options;
  2184. return { done: true, value: keyframes[keyframes.length - 1] };
  2185. }
  2186. const { finalKeyframe, generator, mirroredGenerator, mapPercentToKeyframes, keyframes, calculatedDuration, totalDuration, resolvedDuration, } = resolved;
  2187. if (this.startTime === null)
  2188. return generator.next(0);
  2189. const { delay, repeat, repeatType, repeatDelay, onUpdate } = this.options;
  2190. /**
  2191. * requestAnimationFrame timestamps can come through as lower than
  2192. * the startTime as set by performance.now(). Here we prevent this,
  2193. * though in the future it could be possible to make setting startTime
  2194. * a pending operation that gets resolved here.
  2195. */
  2196. if (this.speed > 0) {
  2197. this.startTime = Math.min(this.startTime, timestamp);
  2198. }
  2199. else if (this.speed < 0) {
  2200. this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
  2201. }
  2202. // Update currentTime
  2203. if (sample) {
  2204. this.currentTime = timestamp;
  2205. }
  2206. else if (this.holdTime !== null) {
  2207. this.currentTime = this.holdTime;
  2208. }
  2209. else {
  2210. // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
  2211. // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
  2212. // example.
  2213. this.currentTime =
  2214. Math.round(timestamp - this.startTime) * this.speed;
  2215. }
  2216. // Rebase on delay
  2217. const timeWithoutDelay = this.currentTime - delay * (this.speed >= 0 ? 1 : -1);
  2218. const isInDelayPhase = this.speed >= 0
  2219. ? timeWithoutDelay < 0
  2220. : timeWithoutDelay > totalDuration;
  2221. this.currentTime = Math.max(timeWithoutDelay, 0);
  2222. // If this animation has finished, set the current time to the total duration.
  2223. if (this.state === "finished" && this.holdTime === null) {
  2224. this.currentTime = totalDuration;
  2225. }
  2226. let elapsed = this.currentTime;
  2227. let frameGenerator = generator;
  2228. if (repeat) {
  2229. /**
  2230. * Get the current progress (0-1) of the animation. If t is >
  2231. * than duration we'll get values like 2.5 (midway through the
  2232. * third iteration)
  2233. */
  2234. const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
  2235. /**
  2236. * Get the current iteration (0 indexed). For instance the floor of
  2237. * 2.5 is 2.
  2238. */
  2239. let currentIteration = Math.floor(progress);
  2240. /**
  2241. * Get the current progress of the iteration by taking the remainder
  2242. * so 2.5 is 0.5 through iteration 2
  2243. */
  2244. let iterationProgress = progress % 1.0;
  2245. /**
  2246. * If iteration progress is 1 we count that as the end
  2247. * of the previous iteration.
  2248. */
  2249. if (!iterationProgress && progress >= 1) {
  2250. iterationProgress = 1;
  2251. }
  2252. iterationProgress === 1 && currentIteration--;
  2253. currentIteration = Math.min(currentIteration, repeat + 1);
  2254. /**
  2255. * Reverse progress if we're not running in "normal" direction
  2256. */
  2257. const isOddIteration = Boolean(currentIteration % 2);
  2258. if (isOddIteration) {
  2259. if (repeatType === "reverse") {
  2260. iterationProgress = 1 - iterationProgress;
  2261. if (repeatDelay) {
  2262. iterationProgress -= repeatDelay / resolvedDuration;
  2263. }
  2264. }
  2265. else if (repeatType === "mirror") {
  2266. frameGenerator = mirroredGenerator;
  2267. }
  2268. }
  2269. elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
  2270. }
  2271. /**
  2272. * If we're in negative time, set state as the initial keyframe.
  2273. * This prevents delay: x, duration: 0 animations from finishing
  2274. * instantly.
  2275. */
  2276. const state = isInDelayPhase
  2277. ? { done: false, value: keyframes[0] }
  2278. : frameGenerator.next(elapsed);
  2279. if (mapPercentToKeyframes) {
  2280. state.value = mapPercentToKeyframes(state.value);
  2281. }
  2282. let { done } = state;
  2283. if (!isInDelayPhase && calculatedDuration !== null) {
  2284. done =
  2285. this.speed >= 0
  2286. ? this.currentTime >= totalDuration
  2287. : this.currentTime <= 0;
  2288. }
  2289. const isAnimationFinished = this.holdTime === null &&
  2290. (this.state === "finished" || (this.state === "running" && done));
  2291. if (isAnimationFinished && finalKeyframe !== undefined) {
  2292. state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe);
  2293. }
  2294. if (onUpdate) {
  2295. onUpdate(state.value);
  2296. }
  2297. if (isAnimationFinished) {
  2298. this.finish();
  2299. }
  2300. return state;
  2301. }
  2302. get duration() {
  2303. const { resolved } = this;
  2304. return resolved ? motionUtils.millisecondsToSeconds(resolved.calculatedDuration) : 0;
  2305. }
  2306. get time() {
  2307. return motionUtils.millisecondsToSeconds(this.currentTime);
  2308. }
  2309. set time(newTime) {
  2310. newTime = motionUtils.secondsToMilliseconds(newTime);
  2311. this.currentTime = newTime;
  2312. if (this.holdTime !== null || this.speed === 0) {
  2313. this.holdTime = newTime;
  2314. }
  2315. else if (this.driver) {
  2316. this.startTime = this.driver.now() - newTime / this.speed;
  2317. }
  2318. }
  2319. get speed() {
  2320. return this.playbackSpeed;
  2321. }
  2322. set speed(newSpeed) {
  2323. const hasChanged = this.playbackSpeed !== newSpeed;
  2324. this.playbackSpeed = newSpeed;
  2325. if (hasChanged) {
  2326. this.time = motionUtils.millisecondsToSeconds(this.currentTime);
  2327. }
  2328. }
  2329. play() {
  2330. if (!this.resolver.isScheduled) {
  2331. this.resolver.resume();
  2332. }
  2333. if (!this._resolved) {
  2334. this.pendingPlayState = "running";
  2335. return;
  2336. }
  2337. if (this.isStopped)
  2338. return;
  2339. const { driver = frameloopDriver, onPlay, startTime } = this.options;
  2340. if (!this.driver) {
  2341. this.driver = driver((timestamp) => this.tick(timestamp));
  2342. }
  2343. onPlay && onPlay();
  2344. const now = this.driver.now();
  2345. if (this.holdTime !== null) {
  2346. this.startTime = now - this.holdTime;
  2347. }
  2348. else if (!this.startTime) {
  2349. this.startTime = startTime ?? this.calcStartTime();
  2350. }
  2351. else if (this.state === "finished") {
  2352. this.startTime = now;
  2353. }
  2354. if (this.state === "finished") {
  2355. this.updateFinishedPromise();
  2356. }
  2357. this.cancelTime = this.startTime;
  2358. this.holdTime = null;
  2359. /**
  2360. * Set playState to running only after we've used it in
  2361. * the previous logic.
  2362. */
  2363. this.state = "running";
  2364. this.driver.start();
  2365. }
  2366. pause() {
  2367. if (!this._resolved) {
  2368. this.pendingPlayState = "paused";
  2369. return;
  2370. }
  2371. this.state = "paused";
  2372. this.holdTime = this.currentTime ?? 0;
  2373. }
  2374. complete() {
  2375. if (this.state !== "running") {
  2376. this.play();
  2377. }
  2378. this.pendingPlayState = this.state = "finished";
  2379. this.holdTime = null;
  2380. }
  2381. finish() {
  2382. this.teardown();
  2383. this.state = "finished";
  2384. const { onComplete } = this.options;
  2385. onComplete && onComplete();
  2386. }
  2387. cancel() {
  2388. if (this.cancelTime !== null) {
  2389. this.tick(this.cancelTime);
  2390. }
  2391. this.teardown();
  2392. this.updateFinishedPromise();
  2393. }
  2394. teardown() {
  2395. this.state = "idle";
  2396. this.stopDriver();
  2397. this.resolveFinishedPromise();
  2398. this.updateFinishedPromise();
  2399. this.startTime = this.cancelTime = null;
  2400. this.resolver.cancel();
  2401. motionDom.activeAnimations.mainThread--;
  2402. }
  2403. stopDriver() {
  2404. if (!this.driver)
  2405. return;
  2406. this.driver.stop();
  2407. this.driver = undefined;
  2408. }
  2409. sample(time) {
  2410. this.startTime = 0;
  2411. return this.tick(time, true);
  2412. }
  2413. get finished() {
  2414. return this.currentFinishedPromise;
  2415. }
  2416. }
  2417. // Legacy interface
  2418. function animateValue(options) {
  2419. return new MainThreadAnimation(options);
  2420. }
  2421. /**
  2422. * A list of values that can be hardware-accelerated.
  2423. */
  2424. const acceleratedValues = new Set([
  2425. "opacity",
  2426. "clipPath",
  2427. "filter",
  2428. "transform",
  2429. // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
  2430. // or until we implement support for linear() easing.
  2431. // "background-color"
  2432. ]);
  2433. const supportsWaapi = /*@__PURE__*/ motionUtils.memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
  2434. /**
  2435. * 10ms is chosen here as it strikes a balance between smooth
  2436. * results (more than one keyframe per frame at 60fps) and
  2437. * keyframe quantity.
  2438. */
  2439. const sampleDelta = 10; //ms
  2440. /**
  2441. * Implement a practical max duration for keyframe generation
  2442. * to prevent infinite loops
  2443. */
  2444. const maxDuration = 20000;
  2445. /**
  2446. * Check if an animation can run natively via WAAPI or requires pregenerated keyframes.
  2447. * WAAPI doesn't support spring or function easings so we run these as JS animation before
  2448. * handing off.
  2449. */
  2450. function requiresPregeneratedKeyframes(options) {
  2451. return (motionDom.isGenerator(options.type) ||
  2452. options.type === "spring" ||
  2453. !motionDom.isWaapiSupportedEasing(options.ease));
  2454. }
  2455. function pregenerateKeyframes(keyframes, options) {
  2456. /**
  2457. * Create a main-thread animation to pregenerate keyframes.
  2458. * We sample this at regular intervals to generate keyframes that we then
  2459. * linearly interpolate between.
  2460. */
  2461. const sampleAnimation = new MainThreadAnimation({
  2462. ...options,
  2463. keyframes,
  2464. repeat: 0,
  2465. delay: 0,
  2466. isGenerator: true,
  2467. });
  2468. let state = { done: false, value: keyframes[0] };
  2469. const pregeneratedKeyframes = [];
  2470. /**
  2471. * Bail after 20 seconds of pre-generated keyframes as it's likely
  2472. * we're heading for an infinite loop.
  2473. */
  2474. let t = 0;
  2475. while (!state.done && t < maxDuration) {
  2476. state = sampleAnimation.sample(t);
  2477. pregeneratedKeyframes.push(state.value);
  2478. t += sampleDelta;
  2479. }
  2480. return {
  2481. times: undefined,
  2482. keyframes: pregeneratedKeyframes,
  2483. duration: t - sampleDelta,
  2484. ease: "linear",
  2485. };
  2486. }
  2487. const unsupportedEasingFunctions = {
  2488. anticipate,
  2489. backInOut,
  2490. circInOut,
  2491. };
  2492. function isUnsupportedEase(key) {
  2493. return key in unsupportedEasingFunctions;
  2494. }
  2495. class AcceleratedAnimation extends BaseAnimation {
  2496. constructor(options) {
  2497. super(options);
  2498. const { name, motionValue, element, keyframes } = this.options;
  2499. this.resolver = new DOMKeyframesResolver(keyframes, (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe), name, motionValue, element);
  2500. this.resolver.scheduleResolve();
  2501. }
  2502. initPlayback(keyframes, finalKeyframe) {
  2503. let { duration = 300, times, ease, type, motionValue, name, startTime, } = this.options;
  2504. /**
  2505. * If element has since been unmounted, return false to indicate
  2506. * the animation failed to initialised.
  2507. */
  2508. if (!motionValue.owner || !motionValue.owner.current) {
  2509. return false;
  2510. }
  2511. /**
  2512. * If the user has provided an easing function name that isn't supported
  2513. * by WAAPI (like "anticipate"), we need to provide the corressponding
  2514. * function. This will later get converted to a linear() easing function.
  2515. */
  2516. if (typeof ease === "string" &&
  2517. motionDom.supportsLinearEasing() &&
  2518. isUnsupportedEase(ease)) {
  2519. ease = unsupportedEasingFunctions[ease];
  2520. }
  2521. /**
  2522. * If this animation needs pre-generated keyframes then generate.
  2523. */
  2524. if (requiresPregeneratedKeyframes(this.options)) {
  2525. const { onComplete, onUpdate, motionValue, element, ...options } = this.options;
  2526. const pregeneratedAnimation = pregenerateKeyframes(keyframes, options);
  2527. keyframes = pregeneratedAnimation.keyframes;
  2528. // If this is a very short animation, ensure we have
  2529. // at least two keyframes to animate between as older browsers
  2530. // can't animate between a single keyframe.
  2531. if (keyframes.length === 1) {
  2532. keyframes[1] = keyframes[0];
  2533. }
  2534. duration = pregeneratedAnimation.duration;
  2535. times = pregeneratedAnimation.times;
  2536. ease = pregeneratedAnimation.ease;
  2537. type = "keyframes";
  2538. }
  2539. const animation = motionDom.startWaapiAnimation(motionValue.owner.current, name, keyframes, { ...this.options, duration, times, ease });
  2540. // Override the browser calculated startTime with one synchronised to other JS
  2541. // and WAAPI animations starting this event loop.
  2542. animation.startTime = startTime ?? this.calcStartTime();
  2543. if (this.pendingTimeline) {
  2544. motionDom.attachTimeline(animation, this.pendingTimeline);
  2545. this.pendingTimeline = undefined;
  2546. }
  2547. else {
  2548. /**
  2549. * Prefer the `onfinish` prop as it's more widely supported than
  2550. * the `finished` promise.
  2551. *
  2552. * Here, we synchronously set the provided MotionValue to the end
  2553. * keyframe. If we didn't, when the WAAPI animation is finished it would
  2554. * be removed from the element which would then revert to its old styles.
  2555. */
  2556. animation.onfinish = () => {
  2557. const { onComplete } = this.options;
  2558. motionValue.set(getFinalKeyframe(keyframes, this.options, finalKeyframe));
  2559. onComplete && onComplete();
  2560. this.cancel();
  2561. this.resolveFinishedPromise();
  2562. };
  2563. }
  2564. return {
  2565. animation,
  2566. duration,
  2567. times,
  2568. type,
  2569. ease,
  2570. keyframes: keyframes,
  2571. };
  2572. }
  2573. get duration() {
  2574. const { resolved } = this;
  2575. if (!resolved)
  2576. return 0;
  2577. const { duration } = resolved;
  2578. return motionUtils.millisecondsToSeconds(duration);
  2579. }
  2580. get time() {
  2581. const { resolved } = this;
  2582. if (!resolved)
  2583. return 0;
  2584. const { animation } = resolved;
  2585. return motionUtils.millisecondsToSeconds(animation.currentTime || 0);
  2586. }
  2587. set time(newTime) {
  2588. const { resolved } = this;
  2589. if (!resolved)
  2590. return;
  2591. const { animation } = resolved;
  2592. animation.currentTime = motionUtils.secondsToMilliseconds(newTime);
  2593. }
  2594. get speed() {
  2595. const { resolved } = this;
  2596. if (!resolved)
  2597. return 1;
  2598. const { animation } = resolved;
  2599. return animation.playbackRate;
  2600. }
  2601. get finished() {
  2602. return this.resolved.animation.finished;
  2603. }
  2604. set speed(newSpeed) {
  2605. const { resolved } = this;
  2606. if (!resolved)
  2607. return;
  2608. const { animation } = resolved;
  2609. animation.playbackRate = newSpeed;
  2610. }
  2611. get state() {
  2612. const { resolved } = this;
  2613. if (!resolved)
  2614. return "idle";
  2615. const { animation } = resolved;
  2616. return animation.playState;
  2617. }
  2618. get startTime() {
  2619. const { resolved } = this;
  2620. if (!resolved)
  2621. return null;
  2622. const { animation } = resolved;
  2623. // Coerce to number as TypeScript incorrectly types this
  2624. // as CSSNumberish
  2625. return animation.startTime;
  2626. }
  2627. /**
  2628. * Replace the default DocumentTimeline with another AnimationTimeline.
  2629. * Currently used for scroll animations.
  2630. */
  2631. attachTimeline(timeline) {
  2632. if (!this._resolved) {
  2633. this.pendingTimeline = timeline;
  2634. }
  2635. else {
  2636. const { resolved } = this;
  2637. if (!resolved)
  2638. return motionUtils.noop;
  2639. const { animation } = resolved;
  2640. motionDom.attachTimeline(animation, timeline);
  2641. }
  2642. return motionUtils.noop;
  2643. }
  2644. play() {
  2645. if (this.isStopped)
  2646. return;
  2647. const { resolved } = this;
  2648. if (!resolved)
  2649. return;
  2650. const { animation } = resolved;
  2651. if (animation.playState === "finished") {
  2652. this.updateFinishedPromise();
  2653. }
  2654. animation.play();
  2655. }
  2656. pause() {
  2657. const { resolved } = this;
  2658. if (!resolved)
  2659. return;
  2660. const { animation } = resolved;
  2661. animation.pause();
  2662. }
  2663. stop() {
  2664. this.resolver.cancel();
  2665. this.isStopped = true;
  2666. if (this.state === "idle")
  2667. return;
  2668. this.resolveFinishedPromise();
  2669. this.updateFinishedPromise();
  2670. const { resolved } = this;
  2671. if (!resolved)
  2672. return;
  2673. const { animation, keyframes, duration, type, ease, times } = resolved;
  2674. if (animation.playState === "idle" ||
  2675. animation.playState === "finished") {
  2676. return;
  2677. }
  2678. /**
  2679. * WAAPI doesn't natively have any interruption capabilities.
  2680. *
  2681. * Rather than read commited styles back out of the DOM, we can
  2682. * create a renderless JS animation and sample it twice to calculate
  2683. * its current value, "previous" value, and therefore allow
  2684. * Motion to calculate velocity for any subsequent animation.
  2685. */
  2686. if (this.time) {
  2687. const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
  2688. const sampleAnimation = new MainThreadAnimation({
  2689. ...options,
  2690. keyframes,
  2691. duration,
  2692. type,
  2693. ease,
  2694. times,
  2695. isGenerator: true,
  2696. });
  2697. const sampleTime = motionUtils.secondsToMilliseconds(this.time);
  2698. motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
  2699. }
  2700. const { onStop } = this.options;
  2701. onStop && onStop();
  2702. this.cancel();
  2703. }
  2704. complete() {
  2705. const { resolved } = this;
  2706. if (!resolved)
  2707. return;
  2708. resolved.animation.finish();
  2709. }
  2710. cancel() {
  2711. const { resolved } = this;
  2712. if (!resolved)
  2713. return;
  2714. resolved.animation.cancel();
  2715. }
  2716. static supports(options) {
  2717. const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
  2718. if (!motionValue ||
  2719. !motionValue.owner ||
  2720. !(motionValue.owner.current instanceof HTMLElement)) {
  2721. return false;
  2722. }
  2723. const { onUpdate, transformTemplate } = motionValue.owner.getProps();
  2724. return (supportsWaapi() &&
  2725. name &&
  2726. acceleratedValues.has(name) &&
  2727. (name !== "transform" || !transformTemplate) &&
  2728. /**
  2729. * If we're outputting values to onUpdate then we can't use WAAPI as there's
  2730. * no way to read the value from WAAPI every frame.
  2731. */
  2732. !onUpdate &&
  2733. !repeatDelay &&
  2734. repeatType !== "mirror" &&
  2735. damping !== 0 &&
  2736. type !== "inertia");
  2737. }
  2738. }
  2739. const underDampedSpring = {
  2740. type: "spring",
  2741. stiffness: 500,
  2742. damping: 25,
  2743. restSpeed: 10,
  2744. };
  2745. const criticallyDampedSpring = (target) => ({
  2746. type: "spring",
  2747. stiffness: 550,
  2748. damping: target === 0 ? 2 * Math.sqrt(550) : 30,
  2749. restSpeed: 10,
  2750. });
  2751. const keyframesTransition = {
  2752. type: "keyframes",
  2753. duration: 0.8,
  2754. };
  2755. /**
  2756. * Default easing curve is a slightly shallower version of
  2757. * the default browser easing curve.
  2758. */
  2759. const ease = {
  2760. type: "keyframes",
  2761. ease: [0.25, 0.1, 0.35, 1],
  2762. duration: 0.3,
  2763. };
  2764. const getDefaultTransition = (valueKey, { keyframes }) => {
  2765. if (keyframes.length > 2) {
  2766. return keyframesTransition;
  2767. }
  2768. else if (transformProps.has(valueKey)) {
  2769. return valueKey.startsWith("scale")
  2770. ? criticallyDampedSpring(keyframes[1])
  2771. : underDampedSpring;
  2772. }
  2773. return ease;
  2774. };
  2775. /**
  2776. * Decide whether a transition is defined on a given Transition.
  2777. * This filters out orchestration options and returns true
  2778. * if any options are left.
  2779. */
  2780. function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
  2781. return !!Object.keys(transition).length;
  2782. }
  2783. const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
  2784. const valueTransition = motionDom.getValueTransition(transition, name) || {};
  2785. /**
  2786. * Most transition values are currently completely overwritten by value-specific
  2787. * transitions. In the future it'd be nicer to blend these transitions. But for now
  2788. * delay actually does inherit from the root transition if not value-specific.
  2789. */
  2790. const delay = valueTransition.delay || transition.delay || 0;
  2791. /**
  2792. * Elapsed isn't a public transition option but can be passed through from
  2793. * optimized appear effects in milliseconds.
  2794. */
  2795. let { elapsed = 0 } = transition;
  2796. elapsed = elapsed - motionUtils.secondsToMilliseconds(delay);
  2797. let options = {
  2798. keyframes: Array.isArray(target) ? target : [null, target],
  2799. ease: "easeOut",
  2800. velocity: value.getVelocity(),
  2801. ...valueTransition,
  2802. delay: -elapsed,
  2803. onUpdate: (v) => {
  2804. value.set(v);
  2805. valueTransition.onUpdate && valueTransition.onUpdate(v);
  2806. },
  2807. onComplete: () => {
  2808. onComplete();
  2809. valueTransition.onComplete && valueTransition.onComplete();
  2810. },
  2811. name,
  2812. motionValue: value,
  2813. element: isHandoff ? undefined : element,
  2814. };
  2815. /**
  2816. * If there's no transition defined for this value, we can generate
  2817. * unique transition settings for this value.
  2818. */
  2819. if (!isTransitionDefined(valueTransition)) {
  2820. options = {
  2821. ...options,
  2822. ...getDefaultTransition(name, options),
  2823. };
  2824. }
  2825. /**
  2826. * Both WAAPI and our internal animation functions use durations
  2827. * as defined by milliseconds, while our external API defines them
  2828. * as seconds.
  2829. */
  2830. if (options.duration) {
  2831. options.duration = motionUtils.secondsToMilliseconds(options.duration);
  2832. }
  2833. if (options.repeatDelay) {
  2834. options.repeatDelay = motionUtils.secondsToMilliseconds(options.repeatDelay);
  2835. }
  2836. if (options.from !== undefined) {
  2837. options.keyframes[0] = options.from;
  2838. }
  2839. let shouldSkip = false;
  2840. if (options.type === false ||
  2841. (options.duration === 0 && !options.repeatDelay)) {
  2842. options.duration = 0;
  2843. if (options.delay === 0) {
  2844. shouldSkip = true;
  2845. }
  2846. }
  2847. if (instantAnimationState.current ||
  2848. motionUtils.MotionGlobalConfig.skipAnimations) {
  2849. shouldSkip = true;
  2850. options.duration = 0;
  2851. options.delay = 0;
  2852. }
  2853. /**
  2854. * If the transition type or easing has been explicitly set by the user
  2855. * then we don't want to allow flattening the animation.
  2856. */
  2857. options.allowFlatten = !valueTransition.type && !valueTransition.ease;
  2858. /**
  2859. * If we can or must skip creating the animation, and apply only
  2860. * the final keyframe, do so. We also check once keyframes are resolved but
  2861. * this early check prevents the need to create an animation at all.
  2862. */
  2863. if (shouldSkip && !isHandoff && value.get() !== undefined) {
  2864. const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
  2865. if (finalKeyframe !== undefined) {
  2866. motionDom.frame.update(() => {
  2867. options.onUpdate(finalKeyframe);
  2868. options.onComplete();
  2869. });
  2870. // We still want to return some animation controls here rather
  2871. // than returning undefined
  2872. return new motionDom.GroupAnimationWithThen([]);
  2873. }
  2874. }
  2875. /**
  2876. * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
  2877. * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
  2878. * optimised animation.
  2879. */
  2880. if (!isHandoff && AcceleratedAnimation.supports(options)) {
  2881. return new AcceleratedAnimation(options);
  2882. }
  2883. else {
  2884. return new MainThreadAnimation(options);
  2885. }
  2886. };
  2887. function animateSingleValue(value, keyframes, options) {
  2888. const motionValue = isMotionValue(value) ? value : motionDom.motionValue(value);
  2889. motionValue.start(animateMotionValue("", motionValue, keyframes, options));
  2890. return motionValue.animation;
  2891. }
  2892. /**
  2893. * Convert camelCase to dash-case properties.
  2894. */
  2895. const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
  2896. const optimizedAppearDataId = "framerAppearId";
  2897. const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
  2898. function getOptimisedAppearId(visualElement) {
  2899. return visualElement.props[optimizedAppearDataAttribute];
  2900. }
  2901. function isSVGElement(element) {
  2902. return element instanceof SVGElement && element.tagName !== "svg";
  2903. }
  2904. const compareByDepth = (a, b) => a.depth - b.depth;
  2905. class FlatTree {
  2906. constructor() {
  2907. this.children = [];
  2908. this.isDirty = false;
  2909. }
  2910. add(child) {
  2911. motionUtils.addUniqueItem(this.children, child);
  2912. this.isDirty = true;
  2913. }
  2914. remove(child) {
  2915. motionUtils.removeItem(this.children, child);
  2916. this.isDirty = true;
  2917. }
  2918. forEach(callback) {
  2919. this.isDirty && this.children.sort(compareByDepth);
  2920. this.isDirty = false;
  2921. this.children.forEach(callback);
  2922. }
  2923. }
  2924. /**
  2925. * Timeout defined in ms
  2926. */
  2927. function delay(callback, timeout) {
  2928. const start = motionDom.time.now();
  2929. const checkElapsed = ({ timestamp }) => {
  2930. const elapsed = timestamp - start;
  2931. if (elapsed >= timeout) {
  2932. motionDom.cancelFrame(checkElapsed);
  2933. callback(elapsed - timeout);
  2934. }
  2935. };
  2936. motionDom.frame.read(checkElapsed, true);
  2937. return () => motionDom.cancelFrame(checkElapsed);
  2938. }
  2939. const isKeyframesTarget = (v) => {
  2940. return Array.isArray(v);
  2941. };
  2942. const isCustomValue = (v) => {
  2943. return Boolean(v && typeof v === "object" && v.mix && v.toValue);
  2944. };
  2945. const resolveFinalValueInKeyframes = (v) => {
  2946. // TODO maybe throw if v.length - 1 is placeholder token?
  2947. return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
  2948. };
  2949. /**
  2950. * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
  2951. *
  2952. * TODO: Remove and move to library
  2953. */
  2954. function resolveMotionValue(value) {
  2955. const unwrappedValue = isMotionValue(value) ? value.get() : value;
  2956. return isCustomValue(unwrappedValue)
  2957. ? unwrappedValue.toValue()
  2958. : unwrappedValue;
  2959. }
  2960. const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
  2961. const numBorders = borders.length;
  2962. const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
  2963. const isPx = (value) => typeof value === "number" || px.test(value);
  2964. function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
  2965. if (shouldCrossfadeOpacity) {
  2966. target.opacity = mixNumber$1(0, lead.opacity ?? 1, easeCrossfadeIn(progress));
  2967. target.opacityExit = mixNumber$1(follow.opacity ?? 1, 0, easeCrossfadeOut(progress));
  2968. }
  2969. else if (isOnlyMember) {
  2970. target.opacity = mixNumber$1(follow.opacity ?? 1, lead.opacity ?? 1, progress);
  2971. }
  2972. /**
  2973. * Mix border radius
  2974. */
  2975. for (let i = 0; i < numBorders; i++) {
  2976. const borderLabel = `border${borders[i]}Radius`;
  2977. let followRadius = getRadius(follow, borderLabel);
  2978. let leadRadius = getRadius(lead, borderLabel);
  2979. if (followRadius === undefined && leadRadius === undefined)
  2980. continue;
  2981. followRadius || (followRadius = 0);
  2982. leadRadius || (leadRadius = 0);
  2983. const canMix = followRadius === 0 ||
  2984. leadRadius === 0 ||
  2985. isPx(followRadius) === isPx(leadRadius);
  2986. if (canMix) {
  2987. target[borderLabel] = Math.max(mixNumber$1(asNumber(followRadius), asNumber(leadRadius), progress), 0);
  2988. if (percent.test(leadRadius) || percent.test(followRadius)) {
  2989. target[borderLabel] += "%";
  2990. }
  2991. }
  2992. else {
  2993. target[borderLabel] = leadRadius;
  2994. }
  2995. }
  2996. /**
  2997. * Mix rotation
  2998. */
  2999. if (follow.rotate || lead.rotate) {
  3000. target.rotate = mixNumber$1(follow.rotate || 0, lead.rotate || 0, progress);
  3001. }
  3002. }
  3003. function getRadius(values, radiusName) {
  3004. return values[radiusName] !== undefined
  3005. ? values[radiusName]
  3006. : values.borderRadius;
  3007. }
  3008. // /**
  3009. // * We only want to mix the background color if there's a follow element
  3010. // * that we're not crossfading opacity between. For instance with switch
  3011. // * AnimateSharedLayout animations, this helps the illusion of a continuous
  3012. // * element being animated but also cuts down on the number of paints triggered
  3013. // * for elements where opacity is doing that work for us.
  3014. // */
  3015. // if (
  3016. // !hasFollowElement &&
  3017. // latestLeadValues.backgroundColor &&
  3018. // latestFollowValues.backgroundColor
  3019. // ) {
  3020. // /**
  3021. // * This isn't ideal performance-wise as mixColor is creating a new function every frame.
  3022. // * We could probably create a mixer that runs at the start of the animation but
  3023. // * the idea behind the crossfader is that it runs dynamically between two potentially
  3024. // * changing targets (ie opacity or borderRadius may be animating independently via variants)
  3025. // */
  3026. // leadState.backgroundColor = followState.backgroundColor = mixColor(
  3027. // latestFollowValues.backgroundColor as string,
  3028. // latestLeadValues.backgroundColor as string
  3029. // )(p)
  3030. // }
  3031. const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
  3032. const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, motionUtils.noop);
  3033. function compress(min, max, easing) {
  3034. return (p) => {
  3035. // Could replace ifs with clamp
  3036. if (p < min)
  3037. return 0;
  3038. if (p > max)
  3039. return 1;
  3040. return easing(motionUtils.progress(min, max, p));
  3041. };
  3042. }
  3043. /**
  3044. * Reset an axis to the provided origin box.
  3045. *
  3046. * This is a mutative operation.
  3047. */
  3048. function copyAxisInto(axis, originAxis) {
  3049. axis.min = originAxis.min;
  3050. axis.max = originAxis.max;
  3051. }
  3052. /**
  3053. * Reset a box to the provided origin box.
  3054. *
  3055. * This is a mutative operation.
  3056. */
  3057. function copyBoxInto(box, originBox) {
  3058. copyAxisInto(box.x, originBox.x);
  3059. copyAxisInto(box.y, originBox.y);
  3060. }
  3061. /**
  3062. * Reset a delta to the provided origin box.
  3063. *
  3064. * This is a mutative operation.
  3065. */
  3066. function copyAxisDeltaInto(delta, originDelta) {
  3067. delta.translate = originDelta.translate;
  3068. delta.scale = originDelta.scale;
  3069. delta.originPoint = originDelta.originPoint;
  3070. delta.origin = originDelta.origin;
  3071. }
  3072. function isIdentityScale(scale) {
  3073. return scale === undefined || scale === 1;
  3074. }
  3075. function hasScale({ scale, scaleX, scaleY }) {
  3076. return (!isIdentityScale(scale) ||
  3077. !isIdentityScale(scaleX) ||
  3078. !isIdentityScale(scaleY));
  3079. }
  3080. function hasTransform(values) {
  3081. return (hasScale(values) ||
  3082. has2DTranslate(values) ||
  3083. values.z ||
  3084. values.rotate ||
  3085. values.rotateX ||
  3086. values.rotateY ||
  3087. values.skewX ||
  3088. values.skewY);
  3089. }
  3090. function has2DTranslate(values) {
  3091. return is2DTranslate(values.x) || is2DTranslate(values.y);
  3092. }
  3093. function is2DTranslate(value) {
  3094. return value && value !== "0%";
  3095. }
  3096. /**
  3097. * Scales a point based on a factor and an originPoint
  3098. */
  3099. function scalePoint(point, scale, originPoint) {
  3100. const distanceFromOrigin = point - originPoint;
  3101. const scaled = scale * distanceFromOrigin;
  3102. return originPoint + scaled;
  3103. }
  3104. /**
  3105. * Applies a translate/scale delta to a point
  3106. */
  3107. function applyPointDelta(point, translate, scale, originPoint, boxScale) {
  3108. if (boxScale !== undefined) {
  3109. point = scalePoint(point, boxScale, originPoint);
  3110. }
  3111. return scalePoint(point, scale, originPoint) + translate;
  3112. }
  3113. /**
  3114. * Applies a translate/scale delta to an axis
  3115. */
  3116. function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) {
  3117. axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
  3118. axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
  3119. }
  3120. /**
  3121. * Applies a translate/scale delta to a box
  3122. */
  3123. function applyBoxDelta(box, { x, y }) {
  3124. applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
  3125. applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
  3126. }
  3127. const TREE_SCALE_SNAP_MIN = 0.999999999999;
  3128. const TREE_SCALE_SNAP_MAX = 1.0000000000001;
  3129. /**
  3130. * Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
  3131. * in a tree upon our box before then calculating how to project it into our desired viewport-relative box
  3132. *
  3133. * This is the final nested loop within updateLayoutDelta for future refactoring
  3134. */
  3135. function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
  3136. const treeLength = treePath.length;
  3137. if (!treeLength)
  3138. return;
  3139. // Reset the treeScale
  3140. treeScale.x = treeScale.y = 1;
  3141. let node;
  3142. let delta;
  3143. for (let i = 0; i < treeLength; i++) {
  3144. node = treePath[i];
  3145. delta = node.projectionDelta;
  3146. /**
  3147. * TODO: Prefer to remove this, but currently we have motion components with
  3148. * display: contents in Framer.
  3149. */
  3150. const { visualElement } = node.options;
  3151. if (visualElement &&
  3152. visualElement.props.style &&
  3153. visualElement.props.style.display === "contents") {
  3154. continue;
  3155. }
  3156. if (isSharedTransition &&
  3157. node.options.layoutScroll &&
  3158. node.scroll &&
  3159. node !== node.root) {
  3160. transformBox(box, {
  3161. x: -node.scroll.offset.x,
  3162. y: -node.scroll.offset.y,
  3163. });
  3164. }
  3165. if (delta) {
  3166. // Incoporate each ancestor's scale into a culmulative treeScale for this component
  3167. treeScale.x *= delta.x.scale;
  3168. treeScale.y *= delta.y.scale;
  3169. // Apply each ancestor's calculated delta into this component's recorded layout box
  3170. applyBoxDelta(box, delta);
  3171. }
  3172. if (isSharedTransition && hasTransform(node.latestValues)) {
  3173. transformBox(box, node.latestValues);
  3174. }
  3175. }
  3176. /**
  3177. * Snap tree scale back to 1 if it's within a non-perceivable threshold.
  3178. * This will help reduce useless scales getting rendered.
  3179. */
  3180. if (treeScale.x < TREE_SCALE_SNAP_MAX &&
  3181. treeScale.x > TREE_SCALE_SNAP_MIN) {
  3182. treeScale.x = 1.0;
  3183. }
  3184. if (treeScale.y < TREE_SCALE_SNAP_MAX &&
  3185. treeScale.y > TREE_SCALE_SNAP_MIN) {
  3186. treeScale.y = 1.0;
  3187. }
  3188. }
  3189. function translateAxis(axis, distance) {
  3190. axis.min = axis.min + distance;
  3191. axis.max = axis.max + distance;
  3192. }
  3193. /**
  3194. * Apply a transform to an axis from the latest resolved motion values.
  3195. * This function basically acts as a bridge between a flat motion value map
  3196. * and applyAxisDelta
  3197. */
  3198. function transformAxis(axis, axisTranslate, axisScale, boxScale, axisOrigin = 0.5) {
  3199. const originPoint = mixNumber$1(axis.min, axis.max, axisOrigin);
  3200. // Apply the axis delta to the final axis
  3201. applyAxisDelta(axis, axisTranslate, axisScale, originPoint, boxScale);
  3202. }
  3203. /**
  3204. * Apply a transform to a box from the latest resolved motion values.
  3205. */
  3206. function transformBox(box, transform) {
  3207. transformAxis(box.x, transform.x, transform.scaleX, transform.scale, transform.originX);
  3208. transformAxis(box.y, transform.y, transform.scaleY, transform.scale, transform.originY);
  3209. }
  3210. /**
  3211. * Remove a delta from a point. This is essentially the steps of applyPointDelta in reverse
  3212. */
  3213. function removePointDelta(point, translate, scale, originPoint, boxScale) {
  3214. point -= translate;
  3215. point = scalePoint(point, 1 / scale, originPoint);
  3216. if (boxScale !== undefined) {
  3217. point = scalePoint(point, 1 / boxScale, originPoint);
  3218. }
  3219. return point;
  3220. }
  3221. /**
  3222. * Remove a delta from an axis. This is essentially the steps of applyAxisDelta in reverse
  3223. */
  3224. function removeAxisDelta(axis, translate = 0, scale = 1, origin = 0.5, boxScale, originAxis = axis, sourceAxis = axis) {
  3225. if (percent.test(translate)) {
  3226. translate = parseFloat(translate);
  3227. const relativeProgress = mixNumber$1(sourceAxis.min, sourceAxis.max, translate / 100);
  3228. translate = relativeProgress - sourceAxis.min;
  3229. }
  3230. if (typeof translate !== "number")
  3231. return;
  3232. let originPoint = mixNumber$1(originAxis.min, originAxis.max, origin);
  3233. if (axis === originAxis)
  3234. originPoint -= translate;
  3235. axis.min = removePointDelta(axis.min, translate, scale, originPoint, boxScale);
  3236. axis.max = removePointDelta(axis.max, translate, scale, originPoint, boxScale);
  3237. }
  3238. /**
  3239. * Remove a transforms from an axis. This is essentially the steps of applyAxisTransforms in reverse
  3240. * and acts as a bridge between motion values and removeAxisDelta
  3241. */
  3242. function removeAxisTransforms(axis, transforms, [key, scaleKey, originKey], origin, sourceAxis) {
  3243. removeAxisDelta(axis, transforms[key], transforms[scaleKey], transforms[originKey], transforms.scale, origin, sourceAxis);
  3244. }
  3245. /**
  3246. * The names of the motion values we want to apply as translation, scale and origin.
  3247. */
  3248. const xKeys = ["x", "scaleX", "originX"];
  3249. const yKeys = ["y", "scaleY", "originY"];
  3250. /**
  3251. * Remove a transforms from an box. This is essentially the steps of applyAxisBox in reverse
  3252. * and acts as a bridge between motion values and removeAxisDelta
  3253. */
  3254. function removeBoxTransforms(box, transforms, originBox, sourceBox) {
  3255. removeAxisTransforms(box.x, transforms, xKeys, originBox ? originBox.x : undefined, sourceBox ? sourceBox.x : undefined);
  3256. removeAxisTransforms(box.y, transforms, yKeys, originBox ? originBox.y : undefined, sourceBox ? sourceBox.y : undefined);
  3257. }
  3258. const createAxisDelta = () => ({
  3259. translate: 0,
  3260. scale: 1,
  3261. origin: 0,
  3262. originPoint: 0,
  3263. });
  3264. const createDelta = () => ({
  3265. x: createAxisDelta(),
  3266. y: createAxisDelta(),
  3267. });
  3268. const createAxis = () => ({ min: 0, max: 0 });
  3269. const createBox = () => ({
  3270. x: createAxis(),
  3271. y: createAxis(),
  3272. });
  3273. function isAxisDeltaZero(delta) {
  3274. return delta.translate === 0 && delta.scale === 1;
  3275. }
  3276. function isDeltaZero(delta) {
  3277. return isAxisDeltaZero(delta.x) && isAxisDeltaZero(delta.y);
  3278. }
  3279. function axisEquals(a, b) {
  3280. return a.min === b.min && a.max === b.max;
  3281. }
  3282. function boxEquals(a, b) {
  3283. return axisEquals(a.x, b.x) && axisEquals(a.y, b.y);
  3284. }
  3285. function axisEqualsRounded(a, b) {
  3286. return (Math.round(a.min) === Math.round(b.min) &&
  3287. Math.round(a.max) === Math.round(b.max));
  3288. }
  3289. function boxEqualsRounded(a, b) {
  3290. return axisEqualsRounded(a.x, b.x) && axisEqualsRounded(a.y, b.y);
  3291. }
  3292. function aspectRatio(box) {
  3293. return calcLength(box.x) / calcLength(box.y);
  3294. }
  3295. function axisDeltaEquals(a, b) {
  3296. return (a.translate === b.translate &&
  3297. a.scale === b.scale &&
  3298. a.originPoint === b.originPoint);
  3299. }
  3300. class NodeStack {
  3301. constructor() {
  3302. this.members = [];
  3303. }
  3304. add(node) {
  3305. motionUtils.addUniqueItem(this.members, node);
  3306. node.scheduleRender();
  3307. }
  3308. remove(node) {
  3309. motionUtils.removeItem(this.members, node);
  3310. if (node === this.prevLead) {
  3311. this.prevLead = undefined;
  3312. }
  3313. if (node === this.lead) {
  3314. const prevLead = this.members[this.members.length - 1];
  3315. if (prevLead) {
  3316. this.promote(prevLead);
  3317. }
  3318. }
  3319. }
  3320. relegate(node) {
  3321. const indexOfNode = this.members.findIndex((member) => node === member);
  3322. if (indexOfNode === 0)
  3323. return false;
  3324. /**
  3325. * Find the next projection node that is present
  3326. */
  3327. let prevLead;
  3328. for (let i = indexOfNode; i >= 0; i--) {
  3329. const member = this.members[i];
  3330. if (member.isPresent !== false) {
  3331. prevLead = member;
  3332. break;
  3333. }
  3334. }
  3335. if (prevLead) {
  3336. this.promote(prevLead);
  3337. return true;
  3338. }
  3339. else {
  3340. return false;
  3341. }
  3342. }
  3343. promote(node, preserveFollowOpacity) {
  3344. const prevLead = this.lead;
  3345. if (node === prevLead)
  3346. return;
  3347. this.prevLead = prevLead;
  3348. this.lead = node;
  3349. node.show();
  3350. if (prevLead) {
  3351. prevLead.instance && prevLead.scheduleRender();
  3352. node.scheduleRender();
  3353. node.resumeFrom = prevLead;
  3354. if (preserveFollowOpacity) {
  3355. node.resumeFrom.preserveOpacity = true;
  3356. }
  3357. if (prevLead.snapshot) {
  3358. node.snapshot = prevLead.snapshot;
  3359. node.snapshot.latestValues =
  3360. prevLead.animationValues || prevLead.latestValues;
  3361. }
  3362. if (node.root && node.root.isUpdating) {
  3363. node.isLayoutDirty = true;
  3364. }
  3365. const { crossfade } = node.options;
  3366. if (crossfade === false) {
  3367. prevLead.hide();
  3368. }
  3369. /**
  3370. * TODO:
  3371. * - Test border radius when previous node was deleted
  3372. * - boxShadow mixing
  3373. * - Shared between element A in scrolled container and element B (scroll stays the same or changes)
  3374. * - Shared between element A in transformed container and element B (transform stays the same or changes)
  3375. * - Shared between element A in scrolled page and element B (scroll stays the same or changes)
  3376. * ---
  3377. * - Crossfade opacity of root nodes
  3378. * - layoutId changes after animation
  3379. * - layoutId changes mid animation
  3380. */
  3381. }
  3382. }
  3383. exitAnimationComplete() {
  3384. this.members.forEach((node) => {
  3385. const { options, resumingFrom } = node;
  3386. options.onExitComplete && options.onExitComplete();
  3387. if (resumingFrom) {
  3388. resumingFrom.options.onExitComplete &&
  3389. resumingFrom.options.onExitComplete();
  3390. }
  3391. });
  3392. }
  3393. scheduleRender() {
  3394. this.members.forEach((node) => {
  3395. node.instance && node.scheduleRender(false);
  3396. });
  3397. }
  3398. /**
  3399. * Clear any leads that have been removed this render to prevent them from being
  3400. * used in future animations and to prevent memory leaks
  3401. */
  3402. removeLeadSnapshot() {
  3403. if (this.lead && this.lead.snapshot) {
  3404. this.lead.snapshot = undefined;
  3405. }
  3406. }
  3407. }
  3408. const scaleCorrectors = {};
  3409. function addScaleCorrector(correctors) {
  3410. for (const key in correctors) {
  3411. scaleCorrectors[key] = correctors[key];
  3412. if (isCSSVariableName(key)) {
  3413. scaleCorrectors[key].isCSSVariable = true;
  3414. }
  3415. }
  3416. }
  3417. function buildProjectionTransform(delta, treeScale, latestTransform) {
  3418. let transform = "";
  3419. /**
  3420. * The translations we use to calculate are always relative to the viewport coordinate space.
  3421. * But when we apply scales, we also scale the coordinate space of an element and its children.
  3422. * For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need
  3423. * to move an element 100 pixels, we actually need to move it 200 in within that scaled space.
  3424. */
  3425. const xTranslate = delta.x.translate / treeScale.x;
  3426. const yTranslate = delta.y.translate / treeScale.y;
  3427. const zTranslate = latestTransform?.z || 0;
  3428. if (xTranslate || yTranslate || zTranslate) {
  3429. transform = `translate3d(${xTranslate}px, ${yTranslate}px, ${zTranslate}px) `;
  3430. }
  3431. /**
  3432. * Apply scale correction for the tree transform.
  3433. * This will apply scale to the screen-orientated axes.
  3434. */
  3435. if (treeScale.x !== 1 || treeScale.y !== 1) {
  3436. transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
  3437. }
  3438. if (latestTransform) {
  3439. const { transformPerspective, rotate, rotateX, rotateY, skewX, skewY } = latestTransform;
  3440. if (transformPerspective)
  3441. transform = `perspective(${transformPerspective}px) ${transform}`;
  3442. if (rotate)
  3443. transform += `rotate(${rotate}deg) `;
  3444. if (rotateX)
  3445. transform += `rotateX(${rotateX}deg) `;
  3446. if (rotateY)
  3447. transform += `rotateY(${rotateY}deg) `;
  3448. if (skewX)
  3449. transform += `skewX(${skewX}deg) `;
  3450. if (skewY)
  3451. transform += `skewY(${skewY}deg) `;
  3452. }
  3453. /**
  3454. * Apply scale to match the size of the element to the size we want it.
  3455. * This will apply scale to the element-orientated axes.
  3456. */
  3457. const elementScaleX = delta.x.scale * treeScale.x;
  3458. const elementScaleY = delta.y.scale * treeScale.y;
  3459. if (elementScaleX !== 1 || elementScaleY !== 1) {
  3460. transform += `scale(${elementScaleX}, ${elementScaleY})`;
  3461. }
  3462. return transform || "none";
  3463. }
  3464. function eachAxis(callback) {
  3465. return [callback("x"), callback("y")];
  3466. }
  3467. /**
  3468. * This should only ever be modified on the client otherwise it'll
  3469. * persist through server requests. If we need instanced states we
  3470. * could lazy-init via root.
  3471. */
  3472. const globalProjectionState = {
  3473. /**
  3474. * Global flag as to whether the tree has animated since the last time
  3475. * we resized the window
  3476. */
  3477. hasAnimatedSinceResize: true,
  3478. /**
  3479. * We set this to true once, on the first update. Any nodes added to the tree beyond that
  3480. * update will be given a `data-projection-id` attribute.
  3481. */
  3482. hasEverUpdated: false,
  3483. };
  3484. const metrics = {
  3485. nodes: 0,
  3486. calculatedTargetDeltas: 0,
  3487. calculatedProjections: 0,
  3488. };
  3489. const transformAxes = ["", "X", "Y", "Z"];
  3490. const hiddenVisibility = { visibility: "hidden" };
  3491. /**
  3492. * We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
  3493. * which has a noticeable difference in spring animations
  3494. */
  3495. const animationTarget = 1000;
  3496. let id$1 = 0;
  3497. function resetDistortingTransform(key, visualElement, values, sharedAnimationValues) {
  3498. const { latestValues } = visualElement;
  3499. // Record the distorting transform and then temporarily set it to 0
  3500. if (latestValues[key]) {
  3501. values[key] = latestValues[key];
  3502. visualElement.setStaticValue(key, 0);
  3503. if (sharedAnimationValues) {
  3504. sharedAnimationValues[key] = 0;
  3505. }
  3506. }
  3507. }
  3508. function cancelTreeOptimisedTransformAnimations(projectionNode) {
  3509. projectionNode.hasCheckedOptimisedAppear = true;
  3510. if (projectionNode.root === projectionNode)
  3511. return;
  3512. const { visualElement } = projectionNode.options;
  3513. if (!visualElement)
  3514. return;
  3515. const appearId = getOptimisedAppearId(visualElement);
  3516. if (window.MotionHasOptimisedAnimation(appearId, "transform")) {
  3517. const { layout, layoutId } = projectionNode.options;
  3518. window.MotionCancelOptimisedAnimation(appearId, "transform", motionDom.frame, !(layout || layoutId));
  3519. }
  3520. const { parent } = projectionNode;
  3521. if (parent && !parent.hasCheckedOptimisedAppear) {
  3522. cancelTreeOptimisedTransformAnimations(parent);
  3523. }
  3524. }
  3525. function createProjectionNode$1({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
  3526. return class ProjectionNode {
  3527. constructor(latestValues = {}, parent = defaultParent?.()) {
  3528. /**
  3529. * A unique ID generated for every projection node.
  3530. */
  3531. this.id = id$1++;
  3532. /**
  3533. * An id that represents a unique session instigated by startUpdate.
  3534. */
  3535. this.animationId = 0;
  3536. /**
  3537. * A Set containing all this component's children. This is used to iterate
  3538. * through the children.
  3539. *
  3540. * TODO: This could be faster to iterate as a flat array stored on the root node.
  3541. */
  3542. this.children = new Set();
  3543. /**
  3544. * Options for the node. We use this to configure what kind of layout animations
  3545. * we should perform (if any).
  3546. */
  3547. this.options = {};
  3548. /**
  3549. * We use this to detect when its safe to shut down part of a projection tree.
  3550. * We have to keep projecting children for scale correction and relative projection
  3551. * until all their parents stop performing layout animations.
  3552. */
  3553. this.isTreeAnimating = false;
  3554. this.isAnimationBlocked = false;
  3555. /**
  3556. * Flag to true if we think this layout has been changed. We can't always know this,
  3557. * currently we set it to true every time a component renders, or if it has a layoutDependency
  3558. * if that has changed between renders. Additionally, components can be grouped by LayoutGroup
  3559. * and if one node is dirtied, they all are.
  3560. */
  3561. this.isLayoutDirty = false;
  3562. /**
  3563. * Flag to true if we think the projection calculations for this node needs
  3564. * recalculating as a result of an updated transform or layout animation.
  3565. */
  3566. this.isProjectionDirty = false;
  3567. /**
  3568. * Flag to true if the layout *or* transform has changed. This then gets propagated
  3569. * throughout the projection tree, forcing any element below to recalculate on the next frame.
  3570. */
  3571. this.isSharedProjectionDirty = false;
  3572. /**
  3573. * Flag transform dirty. This gets propagated throughout the whole tree but is only
  3574. * respected by shared nodes.
  3575. */
  3576. this.isTransformDirty = false;
  3577. /**
  3578. * Block layout updates for instant layout transitions throughout the tree.
  3579. */
  3580. this.updateManuallyBlocked = false;
  3581. this.updateBlockedByResize = false;
  3582. /**
  3583. * Set to true between the start of the first `willUpdate` call and the end of the `didUpdate`
  3584. * call.
  3585. */
  3586. this.isUpdating = false;
  3587. /**
  3588. * If this is an SVG element we currently disable projection transforms
  3589. */
  3590. this.isSVG = false;
  3591. /**
  3592. * Flag to true (during promotion) if a node doing an instant layout transition needs to reset
  3593. * its projection styles.
  3594. */
  3595. this.needsReset = false;
  3596. /**
  3597. * Flags whether this node should have its transform reset prior to measuring.
  3598. */
  3599. this.shouldResetTransform = false;
  3600. /**
  3601. * Store whether this node has been checked for optimised appear animations. As
  3602. * effects fire bottom-up, and we want to look up the tree for appear animations,
  3603. * this makes sure we only check each path once, stopping at nodes that
  3604. * have already been checked.
  3605. */
  3606. this.hasCheckedOptimisedAppear = false;
  3607. /**
  3608. * An object representing the calculated contextual/accumulated/tree scale.
  3609. * This will be used to scale calculcated projection transforms, as these are
  3610. * calculated in screen-space but need to be scaled for elements to layoutly
  3611. * make it to their calculated destinations.
  3612. *
  3613. * TODO: Lazy-init
  3614. */
  3615. this.treeScale = { x: 1, y: 1 };
  3616. /**
  3617. *
  3618. */
  3619. this.eventHandlers = new Map();
  3620. this.hasTreeAnimated = false;
  3621. // Note: Currently only running on root node
  3622. this.updateScheduled = false;
  3623. this.scheduleUpdate = () => this.update();
  3624. this.projectionUpdateScheduled = false;
  3625. this.checkUpdateFailed = () => {
  3626. if (this.isUpdating) {
  3627. this.isUpdating = false;
  3628. this.clearAllSnapshots();
  3629. }
  3630. };
  3631. /**
  3632. * This is a multi-step process as shared nodes might be of different depths. Nodes
  3633. * are sorted by depth order, so we need to resolve the entire tree before moving to
  3634. * the next step.
  3635. */
  3636. this.updateProjection = () => {
  3637. this.projectionUpdateScheduled = false;
  3638. /**
  3639. * Reset debug counts. Manually resetting rather than creating a new
  3640. * object each frame.
  3641. */
  3642. if (motionDom.statsBuffer.value) {
  3643. metrics.nodes =
  3644. metrics.calculatedTargetDeltas =
  3645. metrics.calculatedProjections =
  3646. 0;
  3647. }
  3648. this.nodes.forEach(propagateDirtyNodes);
  3649. this.nodes.forEach(resolveTargetDelta);
  3650. this.nodes.forEach(calcProjection);
  3651. this.nodes.forEach(cleanDirtyNodes);
  3652. if (motionDom.statsBuffer.addProjectionMetrics) {
  3653. motionDom.statsBuffer.addProjectionMetrics(metrics);
  3654. }
  3655. };
  3656. /**
  3657. * Frame calculations
  3658. */
  3659. this.resolvedRelativeTargetAt = 0.0;
  3660. this.hasProjected = false;
  3661. this.isVisible = true;
  3662. this.animationProgress = 0;
  3663. /**
  3664. * Shared layout
  3665. */
  3666. // TODO Only running on root node
  3667. this.sharedNodes = new Map();
  3668. this.latestValues = latestValues;
  3669. this.root = parent ? parent.root || parent : this;
  3670. this.path = parent ? [...parent.path, parent] : [];
  3671. this.parent = parent;
  3672. this.depth = parent ? parent.depth + 1 : 0;
  3673. for (let i = 0; i < this.path.length; i++) {
  3674. this.path[i].shouldResetTransform = true;
  3675. }
  3676. if (this.root === this)
  3677. this.nodes = new FlatTree();
  3678. }
  3679. addEventListener(name, handler) {
  3680. if (!this.eventHandlers.has(name)) {
  3681. this.eventHandlers.set(name, new motionUtils.SubscriptionManager());
  3682. }
  3683. return this.eventHandlers.get(name).add(handler);
  3684. }
  3685. notifyListeners(name, ...args) {
  3686. const subscriptionManager = this.eventHandlers.get(name);
  3687. subscriptionManager && subscriptionManager.notify(...args);
  3688. }
  3689. hasListeners(name) {
  3690. return this.eventHandlers.has(name);
  3691. }
  3692. /**
  3693. * Lifecycles
  3694. */
  3695. mount(instance, isLayoutDirty = this.root.hasTreeAnimated) {
  3696. if (this.instance)
  3697. return;
  3698. this.isSVG = isSVGElement(instance);
  3699. this.instance = instance;
  3700. const { layoutId, layout, visualElement } = this.options;
  3701. if (visualElement && !visualElement.current) {
  3702. visualElement.mount(instance);
  3703. }
  3704. this.root.nodes.add(this);
  3705. this.parent && this.parent.children.add(this);
  3706. if (isLayoutDirty && (layout || layoutId)) {
  3707. this.isLayoutDirty = true;
  3708. }
  3709. if (attachResizeListener) {
  3710. let cancelDelay;
  3711. const resizeUnblockUpdate = () => (this.root.updateBlockedByResize = false);
  3712. attachResizeListener(instance, () => {
  3713. this.root.updateBlockedByResize = true;
  3714. cancelDelay && cancelDelay();
  3715. cancelDelay = delay(resizeUnblockUpdate, 250);
  3716. if (globalProjectionState.hasAnimatedSinceResize) {
  3717. globalProjectionState.hasAnimatedSinceResize = false;
  3718. this.nodes.forEach(finishAnimation);
  3719. }
  3720. });
  3721. }
  3722. if (layoutId) {
  3723. this.root.registerSharedNode(layoutId, this);
  3724. }
  3725. // Only register the handler if it requires layout animation
  3726. if (this.options.animate !== false &&
  3727. visualElement &&
  3728. (layoutId || layout)) {
  3729. this.addEventListener("didUpdate", ({ delta, hasLayoutChanged, hasRelativeLayoutChanged, layout: newLayout, }) => {
  3730. if (this.isTreeAnimationBlocked()) {
  3731. this.target = undefined;
  3732. this.relativeTarget = undefined;
  3733. return;
  3734. }
  3735. // TODO: Check here if an animation exists
  3736. const layoutTransition = this.options.transition ||
  3737. visualElement.getDefaultTransition() ||
  3738. defaultLayoutTransition;
  3739. const { onLayoutAnimationStart, onLayoutAnimationComplete, } = visualElement.getProps();
  3740. /**
  3741. * The target layout of the element might stay the same,
  3742. * but its position relative to its parent has changed.
  3743. */
  3744. const hasTargetChanged = !this.targetLayout ||
  3745. !boxEqualsRounded(this.targetLayout, newLayout);
  3746. /*
  3747. * Note: Disabled to fix relative animations always triggering new
  3748. * layout animations. If this causes further issues, we can try
  3749. * a different approach to detecting relative target changes.
  3750. */
  3751. // || hasRelativeLayoutChanged
  3752. /**
  3753. * If the layout hasn't seemed to have changed, it might be that the
  3754. * element is visually in the same place in the document but its position
  3755. * relative to its parent has indeed changed. So here we check for that.
  3756. */
  3757. const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeLayoutChanged;
  3758. if (this.options.layoutRoot ||
  3759. this.resumeFrom ||
  3760. hasOnlyRelativeTargetChanged ||
  3761. (hasLayoutChanged &&
  3762. (hasTargetChanged || !this.currentAnimation))) {
  3763. if (this.resumeFrom) {
  3764. this.resumingFrom = this.resumeFrom;
  3765. this.resumingFrom.resumingFrom = undefined;
  3766. }
  3767. this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged);
  3768. const animationOptions = {
  3769. ...motionDom.getValueTransition(layoutTransition, "layout"),
  3770. onPlay: onLayoutAnimationStart,
  3771. onComplete: onLayoutAnimationComplete,
  3772. };
  3773. if (visualElement.shouldReduceMotion ||
  3774. this.options.layoutRoot) {
  3775. animationOptions.delay = 0;
  3776. animationOptions.type = false;
  3777. }
  3778. this.startAnimation(animationOptions);
  3779. }
  3780. else {
  3781. /**
  3782. * If the layout hasn't changed and we have an animation that hasn't started yet,
  3783. * finish it immediately. Otherwise it will be animating from a location
  3784. * that was probably never commited to screen and look like a jumpy box.
  3785. */
  3786. if (!hasLayoutChanged) {
  3787. finishAnimation(this);
  3788. }
  3789. if (this.isLead() && this.options.onExitComplete) {
  3790. this.options.onExitComplete();
  3791. }
  3792. }
  3793. this.targetLayout = newLayout;
  3794. });
  3795. }
  3796. }
  3797. unmount() {
  3798. this.options.layoutId && this.willUpdate();
  3799. this.root.nodes.remove(this);
  3800. const stack = this.getStack();
  3801. stack && stack.remove(this);
  3802. this.parent && this.parent.children.delete(this);
  3803. this.instance = undefined;
  3804. motionDom.cancelFrame(this.updateProjection);
  3805. }
  3806. // only on the root
  3807. blockUpdate() {
  3808. this.updateManuallyBlocked = true;
  3809. }
  3810. unblockUpdate() {
  3811. this.updateManuallyBlocked = false;
  3812. }
  3813. isUpdateBlocked() {
  3814. return this.updateManuallyBlocked || this.updateBlockedByResize;
  3815. }
  3816. isTreeAnimationBlocked() {
  3817. return (this.isAnimationBlocked ||
  3818. (this.parent && this.parent.isTreeAnimationBlocked()) ||
  3819. false);
  3820. }
  3821. // Note: currently only running on root node
  3822. startUpdate() {
  3823. if (this.isUpdateBlocked())
  3824. return;
  3825. this.isUpdating = true;
  3826. this.nodes && this.nodes.forEach(resetSkewAndRotation);
  3827. this.animationId++;
  3828. }
  3829. getTransformTemplate() {
  3830. const { visualElement } = this.options;
  3831. return visualElement && visualElement.getProps().transformTemplate;
  3832. }
  3833. willUpdate(shouldNotifyListeners = true) {
  3834. this.root.hasTreeAnimated = true;
  3835. if (this.root.isUpdateBlocked()) {
  3836. this.options.onExitComplete && this.options.onExitComplete();
  3837. return;
  3838. }
  3839. /**
  3840. * If we're running optimised appear animations then these must be
  3841. * cancelled before measuring the DOM. This is so we can measure
  3842. * the true layout of the element rather than the WAAPI animation
  3843. * which will be unaffected by the resetSkewAndRotate step.
  3844. *
  3845. * Note: This is a DOM write. Worst case scenario is this is sandwiched
  3846. * between other snapshot reads which will cause unnecessary style recalculations.
  3847. * This has to happen here though, as we don't yet know which nodes will need
  3848. * snapshots in startUpdate(), but we only want to cancel optimised animations
  3849. * if a layout animation measurement is actually going to be affected by them.
  3850. */
  3851. if (window.MotionCancelOptimisedAnimation &&
  3852. !this.hasCheckedOptimisedAppear) {
  3853. cancelTreeOptimisedTransformAnimations(this);
  3854. }
  3855. !this.root.isUpdating && this.root.startUpdate();
  3856. if (this.isLayoutDirty)
  3857. return;
  3858. this.isLayoutDirty = true;
  3859. for (let i = 0; i < this.path.length; i++) {
  3860. const node = this.path[i];
  3861. node.shouldResetTransform = true;
  3862. node.updateScroll("snapshot");
  3863. if (node.options.layoutRoot) {
  3864. node.willUpdate(false);
  3865. }
  3866. }
  3867. const { layoutId, layout } = this.options;
  3868. if (layoutId === undefined && !layout)
  3869. return;
  3870. const transformTemplate = this.getTransformTemplate();
  3871. this.prevTransformTemplateValue = transformTemplate
  3872. ? transformTemplate(this.latestValues, "")
  3873. : undefined;
  3874. this.updateSnapshot();
  3875. shouldNotifyListeners && this.notifyListeners("willUpdate");
  3876. }
  3877. update() {
  3878. this.updateScheduled = false;
  3879. const updateWasBlocked = this.isUpdateBlocked();
  3880. // When doing an instant transition, we skip the layout update,
  3881. // but should still clean up the measurements so that the next
  3882. // snapshot could be taken correctly.
  3883. if (updateWasBlocked) {
  3884. this.unblockUpdate();
  3885. this.clearAllSnapshots();
  3886. this.nodes.forEach(clearMeasurements);
  3887. return;
  3888. }
  3889. if (!this.isUpdating) {
  3890. this.nodes.forEach(clearIsLayoutDirty);
  3891. }
  3892. this.isUpdating = false;
  3893. /**
  3894. * Write
  3895. */
  3896. this.nodes.forEach(resetTransformStyle);
  3897. /**
  3898. * Read ==================
  3899. */
  3900. // Update layout measurements of updated children
  3901. this.nodes.forEach(updateLayout);
  3902. /**
  3903. * Write
  3904. */
  3905. // Notify listeners that the layout is updated
  3906. this.nodes.forEach(notifyLayoutUpdate);
  3907. this.clearAllSnapshots();
  3908. /**
  3909. * Manually flush any pending updates. Ideally
  3910. * we could leave this to the following requestAnimationFrame but this seems
  3911. * to leave a flash of incorrectly styled content.
  3912. */
  3913. const now = motionDom.time.now();
  3914. motionDom.frameData.delta = clamp(0, 1000 / 60, now - motionDom.frameData.timestamp);
  3915. motionDom.frameData.timestamp = now;
  3916. motionDom.frameData.isProcessing = true;
  3917. motionDom.frameSteps.update.process(motionDom.frameData);
  3918. motionDom.frameSteps.preRender.process(motionDom.frameData);
  3919. motionDom.frameSteps.render.process(motionDom.frameData);
  3920. motionDom.frameData.isProcessing = false;
  3921. }
  3922. didUpdate() {
  3923. if (!this.updateScheduled) {
  3924. this.updateScheduled = true;
  3925. motionDom.microtask.read(this.scheduleUpdate);
  3926. }
  3927. }
  3928. clearAllSnapshots() {
  3929. this.nodes.forEach(clearSnapshot);
  3930. this.sharedNodes.forEach(removeLeadSnapshots);
  3931. }
  3932. scheduleUpdateProjection() {
  3933. if (!this.projectionUpdateScheduled) {
  3934. this.projectionUpdateScheduled = true;
  3935. motionDom.frame.preRender(this.updateProjection, false, true);
  3936. }
  3937. }
  3938. scheduleCheckAfterUnmount() {
  3939. /**
  3940. * If the unmounting node is in a layoutGroup and did trigger a willUpdate,
  3941. * we manually call didUpdate to give a chance to the siblings to animate.
  3942. * Otherwise, cleanup all snapshots to prevents future nodes from reusing them.
  3943. */
  3944. motionDom.frame.postRender(() => {
  3945. if (this.isLayoutDirty) {
  3946. this.root.didUpdate();
  3947. }
  3948. else {
  3949. this.root.checkUpdateFailed();
  3950. }
  3951. });
  3952. }
  3953. /**
  3954. * Update measurements
  3955. */
  3956. updateSnapshot() {
  3957. if (this.snapshot || !this.instance)
  3958. return;
  3959. this.snapshot = this.measure();
  3960. if (this.snapshot &&
  3961. !calcLength(this.snapshot.measuredBox.x) &&
  3962. !calcLength(this.snapshot.measuredBox.y)) {
  3963. this.snapshot = undefined;
  3964. }
  3965. }
  3966. updateLayout() {
  3967. if (!this.instance)
  3968. return;
  3969. // TODO: Incorporate into a forwarded scroll offset
  3970. this.updateScroll();
  3971. if (!(this.options.alwaysMeasureLayout && this.isLead()) &&
  3972. !this.isLayoutDirty) {
  3973. return;
  3974. }
  3975. /**
  3976. * When a node is mounted, it simply resumes from the prevLead's
  3977. * snapshot instead of taking a new one, but the ancestors scroll
  3978. * might have updated while the prevLead is unmounted. We need to
  3979. * update the scroll again to make sure the layout we measure is
  3980. * up to date.
  3981. */
  3982. if (this.resumeFrom && !this.resumeFrom.instance) {
  3983. for (let i = 0; i < this.path.length; i++) {
  3984. const node = this.path[i];
  3985. node.updateScroll();
  3986. }
  3987. }
  3988. const prevLayout = this.layout;
  3989. this.layout = this.measure(false);
  3990. this.layoutCorrected = createBox();
  3991. this.isLayoutDirty = false;
  3992. this.projectionDelta = undefined;
  3993. this.notifyListeners("measure", this.layout.layoutBox);
  3994. const { visualElement } = this.options;
  3995. visualElement &&
  3996. visualElement.notify("LayoutMeasure", this.layout.layoutBox, prevLayout ? prevLayout.layoutBox : undefined);
  3997. }
  3998. updateScroll(phase = "measure") {
  3999. let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
  4000. if (this.scroll &&
  4001. this.scroll.animationId === this.root.animationId &&
  4002. this.scroll.phase === phase) {
  4003. needsMeasurement = false;
  4004. }
  4005. if (needsMeasurement) {
  4006. const isRoot = checkIsScrollRoot(this.instance);
  4007. this.scroll = {
  4008. animationId: this.root.animationId,
  4009. phase,
  4010. isRoot,
  4011. offset: measureScroll(this.instance),
  4012. wasRoot: this.scroll ? this.scroll.isRoot : isRoot,
  4013. };
  4014. }
  4015. }
  4016. resetTransform() {
  4017. if (!resetTransform)
  4018. return;
  4019. const isResetRequested = this.isLayoutDirty ||
  4020. this.shouldResetTransform ||
  4021. this.options.alwaysMeasureLayout;
  4022. const hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
  4023. const transformTemplate = this.getTransformTemplate();
  4024. const transformTemplateValue = transformTemplate
  4025. ? transformTemplate(this.latestValues, "")
  4026. : undefined;
  4027. const transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
  4028. if (isResetRequested &&
  4029. (hasProjection ||
  4030. hasTransform(this.latestValues) ||
  4031. transformTemplateHasChanged)) {
  4032. resetTransform(this.instance, transformTemplateValue);
  4033. this.shouldResetTransform = false;
  4034. this.scheduleRender();
  4035. }
  4036. }
  4037. measure(removeTransform = true) {
  4038. const pageBox = this.measurePageBox();
  4039. let layoutBox = this.removeElementScroll(pageBox);
  4040. /**
  4041. * Measurements taken during the pre-render stage
  4042. * still have transforms applied so we remove them
  4043. * via calculation.
  4044. */
  4045. if (removeTransform) {
  4046. layoutBox = this.removeTransform(layoutBox);
  4047. }
  4048. roundBox(layoutBox);
  4049. return {
  4050. animationId: this.root.animationId,
  4051. measuredBox: pageBox,
  4052. layoutBox,
  4053. latestValues: {},
  4054. source: this.id,
  4055. };
  4056. }
  4057. measurePageBox() {
  4058. const { visualElement } = this.options;
  4059. if (!visualElement)
  4060. return createBox();
  4061. const box = visualElement.measureViewportBox();
  4062. const wasInScrollRoot = this.scroll?.wasRoot || this.path.some(checkNodeWasScrollRoot);
  4063. if (!wasInScrollRoot) {
  4064. // Remove viewport scroll to give page-relative coordinates
  4065. const { scroll } = this.root;
  4066. if (scroll) {
  4067. translateAxis(box.x, scroll.offset.x);
  4068. translateAxis(box.y, scroll.offset.y);
  4069. }
  4070. }
  4071. return box;
  4072. }
  4073. removeElementScroll(box) {
  4074. const boxWithoutScroll = createBox();
  4075. copyBoxInto(boxWithoutScroll, box);
  4076. if (this.scroll?.wasRoot) {
  4077. return boxWithoutScroll;
  4078. }
  4079. /**
  4080. * Performance TODO: Keep a cumulative scroll offset down the tree
  4081. * rather than loop back up the path.
  4082. */
  4083. for (let i = 0; i < this.path.length; i++) {
  4084. const node = this.path[i];
  4085. const { scroll, options } = node;
  4086. if (node !== this.root && scroll && options.layoutScroll) {
  4087. /**
  4088. * If this is a new scroll root, we want to remove all previous scrolls
  4089. * from the viewport box.
  4090. */
  4091. if (scroll.wasRoot) {
  4092. copyBoxInto(boxWithoutScroll, box);
  4093. }
  4094. translateAxis(boxWithoutScroll.x, scroll.offset.x);
  4095. translateAxis(boxWithoutScroll.y, scroll.offset.y);
  4096. }
  4097. }
  4098. return boxWithoutScroll;
  4099. }
  4100. applyTransform(box, transformOnly = false) {
  4101. const withTransforms = createBox();
  4102. copyBoxInto(withTransforms, box);
  4103. for (let i = 0; i < this.path.length; i++) {
  4104. const node = this.path[i];
  4105. if (!transformOnly &&
  4106. node.options.layoutScroll &&
  4107. node.scroll &&
  4108. node !== node.root) {
  4109. transformBox(withTransforms, {
  4110. x: -node.scroll.offset.x,
  4111. y: -node.scroll.offset.y,
  4112. });
  4113. }
  4114. if (!hasTransform(node.latestValues))
  4115. continue;
  4116. transformBox(withTransforms, node.latestValues);
  4117. }
  4118. if (hasTransform(this.latestValues)) {
  4119. transformBox(withTransforms, this.latestValues);
  4120. }
  4121. return withTransforms;
  4122. }
  4123. removeTransform(box) {
  4124. const boxWithoutTransform = createBox();
  4125. copyBoxInto(boxWithoutTransform, box);
  4126. for (let i = 0; i < this.path.length; i++) {
  4127. const node = this.path[i];
  4128. if (!node.instance)
  4129. continue;
  4130. if (!hasTransform(node.latestValues))
  4131. continue;
  4132. hasScale(node.latestValues) && node.updateSnapshot();
  4133. const sourceBox = createBox();
  4134. const nodeBox = node.measurePageBox();
  4135. copyBoxInto(sourceBox, nodeBox);
  4136. removeBoxTransforms(boxWithoutTransform, node.latestValues, node.snapshot ? node.snapshot.layoutBox : undefined, sourceBox);
  4137. }
  4138. if (hasTransform(this.latestValues)) {
  4139. removeBoxTransforms(boxWithoutTransform, this.latestValues);
  4140. }
  4141. return boxWithoutTransform;
  4142. }
  4143. setTargetDelta(delta) {
  4144. this.targetDelta = delta;
  4145. this.root.scheduleUpdateProjection();
  4146. this.isProjectionDirty = true;
  4147. }
  4148. setOptions(options) {
  4149. this.options = {
  4150. ...this.options,
  4151. ...options,
  4152. crossfade: options.crossfade !== undefined ? options.crossfade : true,
  4153. };
  4154. }
  4155. clearMeasurements() {
  4156. this.scroll = undefined;
  4157. this.layout = undefined;
  4158. this.snapshot = undefined;
  4159. this.prevTransformTemplateValue = undefined;
  4160. this.targetDelta = undefined;
  4161. this.target = undefined;
  4162. this.isLayoutDirty = false;
  4163. }
  4164. forceRelativeParentToResolveTarget() {
  4165. if (!this.relativeParent)
  4166. return;
  4167. /**
  4168. * If the parent target isn't up-to-date, force it to update.
  4169. * This is an unfortunate de-optimisation as it means any updating relative
  4170. * projection will cause all the relative parents to recalculate back
  4171. * up the tree.
  4172. */
  4173. if (this.relativeParent.resolvedRelativeTargetAt !==
  4174. motionDom.frameData.timestamp) {
  4175. this.relativeParent.resolveTargetDelta(true);
  4176. }
  4177. }
  4178. resolveTargetDelta(forceRecalculation = false) {
  4179. /**
  4180. * Once the dirty status of nodes has been spread through the tree, we also
  4181. * need to check if we have a shared node of a different depth that has itself
  4182. * been dirtied.
  4183. */
  4184. const lead = this.getLead();
  4185. this.isProjectionDirty || (this.isProjectionDirty = lead.isProjectionDirty);
  4186. this.isTransformDirty || (this.isTransformDirty = lead.isTransformDirty);
  4187. this.isSharedProjectionDirty || (this.isSharedProjectionDirty = lead.isSharedProjectionDirty);
  4188. const isShared = Boolean(this.resumingFrom) || this !== lead;
  4189. /**
  4190. * We don't use transform for this step of processing so we don't
  4191. * need to check whether any nodes have changed transform.
  4192. */
  4193. const canSkip = !(forceRecalculation ||
  4194. (isShared && this.isSharedProjectionDirty) ||
  4195. this.isProjectionDirty ||
  4196. this.parent?.isProjectionDirty ||
  4197. this.attemptToResolveRelativeTarget ||
  4198. this.root.updateBlockedByResize);
  4199. if (canSkip)
  4200. return;
  4201. const { layout, layoutId } = this.options;
  4202. /**
  4203. * If we have no layout, we can't perform projection, so early return
  4204. */
  4205. if (!this.layout || !(layout || layoutId))
  4206. return;
  4207. this.resolvedRelativeTargetAt = motionDom.frameData.timestamp;
  4208. /**
  4209. * If we don't have a targetDelta but do have a layout, we can attempt to resolve
  4210. * a relativeParent. This will allow a component to perform scale correction
  4211. * even if no animation has started.
  4212. */
  4213. if (!this.targetDelta && !this.relativeTarget) {
  4214. const relativeParent = this.getClosestProjectingParent();
  4215. if (relativeParent &&
  4216. relativeParent.layout &&
  4217. this.animationProgress !== 1) {
  4218. this.relativeParent = relativeParent;
  4219. this.forceRelativeParentToResolveTarget();
  4220. this.relativeTarget = createBox();
  4221. this.relativeTargetOrigin = createBox();
  4222. calcRelativePosition(this.relativeTargetOrigin, this.layout.layoutBox, relativeParent.layout.layoutBox);
  4223. copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
  4224. }
  4225. else {
  4226. this.relativeParent = this.relativeTarget = undefined;
  4227. }
  4228. }
  4229. /**
  4230. * If we have no relative target or no target delta our target isn't valid
  4231. * for this frame.
  4232. */
  4233. if (!this.relativeTarget && !this.targetDelta)
  4234. return;
  4235. /**
  4236. * Lazy-init target data structure
  4237. */
  4238. if (!this.target) {
  4239. this.target = createBox();
  4240. this.targetWithTransforms = createBox();
  4241. }
  4242. /**
  4243. * If we've got a relative box for this component, resolve it into a target relative to the parent.
  4244. */
  4245. if (this.relativeTarget &&
  4246. this.relativeTargetOrigin &&
  4247. this.relativeParent &&
  4248. this.relativeParent.target) {
  4249. this.forceRelativeParentToResolveTarget();
  4250. calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target);
  4251. /**
  4252. * If we've only got a targetDelta, resolve it into a target
  4253. */
  4254. }
  4255. else if (this.targetDelta) {
  4256. if (Boolean(this.resumingFrom)) {
  4257. // TODO: This is creating a new object every frame
  4258. this.target = this.applyTransform(this.layout.layoutBox);
  4259. }
  4260. else {
  4261. copyBoxInto(this.target, this.layout.layoutBox);
  4262. }
  4263. applyBoxDelta(this.target, this.targetDelta);
  4264. }
  4265. else {
  4266. /**
  4267. * If no target, use own layout as target
  4268. */
  4269. copyBoxInto(this.target, this.layout.layoutBox);
  4270. }
  4271. /**
  4272. * If we've been told to attempt to resolve a relative target, do so.
  4273. */
  4274. if (this.attemptToResolveRelativeTarget) {
  4275. this.attemptToResolveRelativeTarget = false;
  4276. const relativeParent = this.getClosestProjectingParent();
  4277. if (relativeParent &&
  4278. Boolean(relativeParent.resumingFrom) ===
  4279. Boolean(this.resumingFrom) &&
  4280. !relativeParent.options.layoutScroll &&
  4281. relativeParent.target &&
  4282. this.animationProgress !== 1) {
  4283. this.relativeParent = relativeParent;
  4284. this.forceRelativeParentToResolveTarget();
  4285. this.relativeTarget = createBox();
  4286. this.relativeTargetOrigin = createBox();
  4287. calcRelativePosition(this.relativeTargetOrigin, this.target, relativeParent.target);
  4288. copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
  4289. }
  4290. else {
  4291. this.relativeParent = this.relativeTarget = undefined;
  4292. }
  4293. }
  4294. /**
  4295. * Increase debug counter for resolved target deltas
  4296. */
  4297. if (motionDom.statsBuffer.value) {
  4298. metrics.calculatedTargetDeltas++;
  4299. }
  4300. }
  4301. getClosestProjectingParent() {
  4302. if (!this.parent ||
  4303. hasScale(this.parent.latestValues) ||
  4304. has2DTranslate(this.parent.latestValues)) {
  4305. return undefined;
  4306. }
  4307. if (this.parent.isProjecting()) {
  4308. return this.parent;
  4309. }
  4310. else {
  4311. return this.parent.getClosestProjectingParent();
  4312. }
  4313. }
  4314. isProjecting() {
  4315. return Boolean((this.relativeTarget ||
  4316. this.targetDelta ||
  4317. this.options.layoutRoot) &&
  4318. this.layout);
  4319. }
  4320. calcProjection() {
  4321. const lead = this.getLead();
  4322. const isShared = Boolean(this.resumingFrom) || this !== lead;
  4323. let canSkip = true;
  4324. /**
  4325. * If this is a normal layout animation and neither this node nor its nearest projecting
  4326. * is dirty then we can't skip.
  4327. */
  4328. if (this.isProjectionDirty || this.parent?.isProjectionDirty) {
  4329. canSkip = false;
  4330. }
  4331. /**
  4332. * If this is a shared layout animation and this node's shared projection is dirty then
  4333. * we can't skip.
  4334. */
  4335. if (isShared &&
  4336. (this.isSharedProjectionDirty || this.isTransformDirty)) {
  4337. canSkip = false;
  4338. }
  4339. /**
  4340. * If we have resolved the target this frame we must recalculate the
  4341. * projection to ensure it visually represents the internal calculations.
  4342. */
  4343. if (this.resolvedRelativeTargetAt === motionDom.frameData.timestamp) {
  4344. canSkip = false;
  4345. }
  4346. if (canSkip)
  4347. return;
  4348. const { layout, layoutId } = this.options;
  4349. /**
  4350. * If this section of the tree isn't animating we can
  4351. * delete our target sources for the following frame.
  4352. */
  4353. this.isTreeAnimating = Boolean((this.parent && this.parent.isTreeAnimating) ||
  4354. this.currentAnimation ||
  4355. this.pendingAnimation);
  4356. if (!this.isTreeAnimating) {
  4357. this.targetDelta = this.relativeTarget = undefined;
  4358. }
  4359. if (!this.layout || !(layout || layoutId))
  4360. return;
  4361. /**
  4362. * Reset the corrected box with the latest values from box, as we're then going
  4363. * to perform mutative operations on it.
  4364. */
  4365. copyBoxInto(this.layoutCorrected, this.layout.layoutBox);
  4366. /**
  4367. * Record previous tree scales before updating.
  4368. */
  4369. const prevTreeScaleX = this.treeScale.x;
  4370. const prevTreeScaleY = this.treeScale.y;
  4371. /**
  4372. * Apply all the parent deltas to this box to produce the corrected box. This
  4373. * is the layout box, as it will appear on screen as a result of the transforms of its parents.
  4374. */
  4375. applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared);
  4376. /**
  4377. * If this layer needs to perform scale correction but doesn't have a target,
  4378. * use the layout as the target.
  4379. */
  4380. if (lead.layout &&
  4381. !lead.target &&
  4382. (this.treeScale.x !== 1 || this.treeScale.y !== 1)) {
  4383. lead.target = lead.layout.layoutBox;
  4384. lead.targetWithTransforms = createBox();
  4385. }
  4386. const { target } = lead;
  4387. if (!target) {
  4388. /**
  4389. * If we don't have a target to project into, but we were previously
  4390. * projecting, we want to remove the stored transform and schedule
  4391. * a render to ensure the elements reflect the removed transform.
  4392. */
  4393. if (this.prevProjectionDelta) {
  4394. this.createProjectionDeltas();
  4395. this.scheduleRender();
  4396. }
  4397. return;
  4398. }
  4399. if (!this.projectionDelta || !this.prevProjectionDelta) {
  4400. this.createProjectionDeltas();
  4401. }
  4402. else {
  4403. copyAxisDeltaInto(this.prevProjectionDelta.x, this.projectionDelta.x);
  4404. copyAxisDeltaInto(this.prevProjectionDelta.y, this.projectionDelta.y);
  4405. }
  4406. /**
  4407. * Update the delta between the corrected box and the target box before user-set transforms were applied.
  4408. * This will allow us to calculate the corrected borderRadius and boxShadow to compensate
  4409. * for our layout reprojection, but still allow them to be scaled correctly by the user.
  4410. * It might be that to simplify this we may want to accept that user-set scale is also corrected
  4411. * and we wouldn't have to keep and calc both deltas, OR we could support a user setting
  4412. * to allow people to choose whether these styles are corrected based on just the
  4413. * layout reprojection or the final bounding box.
  4414. */
  4415. calcBoxDelta(this.projectionDelta, this.layoutCorrected, target, this.latestValues);
  4416. if (this.treeScale.x !== prevTreeScaleX ||
  4417. this.treeScale.y !== prevTreeScaleY ||
  4418. !axisDeltaEquals(this.projectionDelta.x, this.prevProjectionDelta.x) ||
  4419. !axisDeltaEquals(this.projectionDelta.y, this.prevProjectionDelta.y)) {
  4420. this.hasProjected = true;
  4421. this.scheduleRender();
  4422. this.notifyListeners("projectionUpdate", target);
  4423. }
  4424. /**
  4425. * Increase debug counter for recalculated projections
  4426. */
  4427. if (motionDom.statsBuffer.value) {
  4428. metrics.calculatedProjections++;
  4429. }
  4430. }
  4431. hide() {
  4432. this.isVisible = false;
  4433. // TODO: Schedule render
  4434. }
  4435. show() {
  4436. this.isVisible = true;
  4437. // TODO: Schedule render
  4438. }
  4439. scheduleRender(notifyAll = true) {
  4440. this.options.visualElement?.scheduleRender();
  4441. if (notifyAll) {
  4442. const stack = this.getStack();
  4443. stack && stack.scheduleRender();
  4444. }
  4445. if (this.resumingFrom && !this.resumingFrom.instance) {
  4446. this.resumingFrom = undefined;
  4447. }
  4448. }
  4449. createProjectionDeltas() {
  4450. this.prevProjectionDelta = createDelta();
  4451. this.projectionDelta = createDelta();
  4452. this.projectionDeltaWithTransform = createDelta();
  4453. }
  4454. setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
  4455. const snapshot = this.snapshot;
  4456. const snapshotLatestValues = snapshot
  4457. ? snapshot.latestValues
  4458. : {};
  4459. const mixedValues = { ...this.latestValues };
  4460. const targetDelta = createDelta();
  4461. if (!this.relativeParent ||
  4462. !this.relativeParent.options.layoutRoot) {
  4463. this.relativeTarget = this.relativeTargetOrigin = undefined;
  4464. }
  4465. this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
  4466. const relativeLayout = createBox();
  4467. const snapshotSource = snapshot ? snapshot.source : undefined;
  4468. const layoutSource = this.layout ? this.layout.source : undefined;
  4469. const isSharedLayoutAnimation = snapshotSource !== layoutSource;
  4470. const stack = this.getStack();
  4471. const isOnlyMember = !stack || stack.members.length <= 1;
  4472. const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
  4473. !isOnlyMember &&
  4474. this.options.crossfade === true &&
  4475. !this.path.some(hasOpacityCrossfade));
  4476. this.animationProgress = 0;
  4477. let prevRelativeTarget;
  4478. this.mixTargetDelta = (latest) => {
  4479. const progress = latest / 1000;
  4480. mixAxisDelta(targetDelta.x, delta.x, progress);
  4481. mixAxisDelta(targetDelta.y, delta.y, progress);
  4482. this.setTargetDelta(targetDelta);
  4483. if (this.relativeTarget &&
  4484. this.relativeTargetOrigin &&
  4485. this.layout &&
  4486. this.relativeParent &&
  4487. this.relativeParent.layout) {
  4488. calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox);
  4489. mixBox(this.relativeTarget, this.relativeTargetOrigin, relativeLayout, progress);
  4490. /**
  4491. * If this is an unchanged relative target we can consider the
  4492. * projection not dirty.
  4493. */
  4494. if (prevRelativeTarget &&
  4495. boxEquals(this.relativeTarget, prevRelativeTarget)) {
  4496. this.isProjectionDirty = false;
  4497. }
  4498. if (!prevRelativeTarget)
  4499. prevRelativeTarget = createBox();
  4500. copyBoxInto(prevRelativeTarget, this.relativeTarget);
  4501. }
  4502. if (isSharedLayoutAnimation) {
  4503. this.animationValues = mixedValues;
  4504. mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
  4505. }
  4506. this.root.scheduleUpdateProjection();
  4507. this.scheduleRender();
  4508. this.animationProgress = progress;
  4509. };
  4510. this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
  4511. }
  4512. startAnimation(options) {
  4513. this.notifyListeners("animationStart");
  4514. this.currentAnimation && this.currentAnimation.stop();
  4515. if (this.resumingFrom && this.resumingFrom.currentAnimation) {
  4516. this.resumingFrom.currentAnimation.stop();
  4517. }
  4518. if (this.pendingAnimation) {
  4519. motionDom.cancelFrame(this.pendingAnimation);
  4520. this.pendingAnimation = undefined;
  4521. }
  4522. /**
  4523. * Start the animation in the next frame to have a frame with progress 0,
  4524. * where the target is the same as when the animation started, so we can
  4525. * calculate the relative positions correctly for instant transitions.
  4526. */
  4527. this.pendingAnimation = motionDom.frame.update(() => {
  4528. globalProjectionState.hasAnimatedSinceResize = true;
  4529. motionDom.activeAnimations.layout++;
  4530. this.currentAnimation = animateSingleValue(0, animationTarget, {
  4531. ...options,
  4532. onUpdate: (latest) => {
  4533. this.mixTargetDelta(latest);
  4534. options.onUpdate && options.onUpdate(latest);
  4535. },
  4536. onStop: () => {
  4537. motionDom.activeAnimations.layout--;
  4538. },
  4539. onComplete: () => {
  4540. motionDom.activeAnimations.layout--;
  4541. options.onComplete && options.onComplete();
  4542. this.completeAnimation();
  4543. },
  4544. });
  4545. if (this.resumingFrom) {
  4546. this.resumingFrom.currentAnimation = this.currentAnimation;
  4547. }
  4548. this.pendingAnimation = undefined;
  4549. });
  4550. }
  4551. completeAnimation() {
  4552. if (this.resumingFrom) {
  4553. this.resumingFrom.currentAnimation = undefined;
  4554. this.resumingFrom.preserveOpacity = undefined;
  4555. }
  4556. const stack = this.getStack();
  4557. stack && stack.exitAnimationComplete();
  4558. this.resumingFrom =
  4559. this.currentAnimation =
  4560. this.animationValues =
  4561. undefined;
  4562. this.notifyListeners("animationComplete");
  4563. }
  4564. finishAnimation() {
  4565. if (this.currentAnimation) {
  4566. this.mixTargetDelta && this.mixTargetDelta(animationTarget);
  4567. this.currentAnimation.stop();
  4568. }
  4569. this.completeAnimation();
  4570. }
  4571. applyTransformsToTarget() {
  4572. const lead = this.getLead();
  4573. let { targetWithTransforms, target, layout, latestValues } = lead;
  4574. if (!targetWithTransforms || !target || !layout)
  4575. return;
  4576. /**
  4577. * If we're only animating position, and this element isn't the lead element,
  4578. * then instead of projecting into the lead box we instead want to calculate
  4579. * a new target that aligns the two boxes but maintains the layout shape.
  4580. */
  4581. if (this !== lead &&
  4582. this.layout &&
  4583. layout &&
  4584. shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
  4585. target = this.target || createBox();
  4586. const xLength = calcLength(this.layout.layoutBox.x);
  4587. target.x.min = lead.target.x.min;
  4588. target.x.max = target.x.min + xLength;
  4589. const yLength = calcLength(this.layout.layoutBox.y);
  4590. target.y.min = lead.target.y.min;
  4591. target.y.max = target.y.min + yLength;
  4592. }
  4593. copyBoxInto(targetWithTransforms, target);
  4594. /**
  4595. * Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
  4596. * This is the final box that we will then project into by calculating a transform delta and
  4597. * applying it to the corrected box.
  4598. */
  4599. transformBox(targetWithTransforms, latestValues);
  4600. /**
  4601. * Update the delta between the corrected box and the final target box, after
  4602. * user-set transforms are applied to it. This will be used by the renderer to
  4603. * create a transform style that will reproject the element from its layout layout
  4604. * into the desired bounding box.
  4605. */
  4606. calcBoxDelta(this.projectionDeltaWithTransform, this.layoutCorrected, targetWithTransforms, latestValues);
  4607. }
  4608. registerSharedNode(layoutId, node) {
  4609. if (!this.sharedNodes.has(layoutId)) {
  4610. this.sharedNodes.set(layoutId, new NodeStack());
  4611. }
  4612. const stack = this.sharedNodes.get(layoutId);
  4613. stack.add(node);
  4614. const config = node.options.initialPromotionConfig;
  4615. node.promote({
  4616. transition: config ? config.transition : undefined,
  4617. preserveFollowOpacity: config && config.shouldPreserveFollowOpacity
  4618. ? config.shouldPreserveFollowOpacity(node)
  4619. : undefined,
  4620. });
  4621. }
  4622. isLead() {
  4623. const stack = this.getStack();
  4624. return stack ? stack.lead === this : true;
  4625. }
  4626. getLead() {
  4627. const { layoutId } = this.options;
  4628. return layoutId ? this.getStack()?.lead || this : this;
  4629. }
  4630. getPrevLead() {
  4631. const { layoutId } = this.options;
  4632. return layoutId ? this.getStack()?.prevLead : undefined;
  4633. }
  4634. getStack() {
  4635. const { layoutId } = this.options;
  4636. if (layoutId)
  4637. return this.root.sharedNodes.get(layoutId);
  4638. }
  4639. promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
  4640. const stack = this.getStack();
  4641. if (stack)
  4642. stack.promote(this, preserveFollowOpacity);
  4643. if (needsReset) {
  4644. this.projectionDelta = undefined;
  4645. this.needsReset = true;
  4646. }
  4647. if (transition)
  4648. this.setOptions({ transition });
  4649. }
  4650. relegate() {
  4651. const stack = this.getStack();
  4652. if (stack) {
  4653. return stack.relegate(this);
  4654. }
  4655. else {
  4656. return false;
  4657. }
  4658. }
  4659. resetSkewAndRotation() {
  4660. const { visualElement } = this.options;
  4661. if (!visualElement)
  4662. return;
  4663. // If there's no detected skew or rotation values, we can early return without a forced render.
  4664. let hasDistortingTransform = false;
  4665. /**
  4666. * An unrolled check for rotation values. Most elements don't have any rotation and
  4667. * skipping the nested loop and new object creation is 50% faster.
  4668. */
  4669. const { latestValues } = visualElement;
  4670. if (latestValues.z ||
  4671. latestValues.rotate ||
  4672. latestValues.rotateX ||
  4673. latestValues.rotateY ||
  4674. latestValues.rotateZ ||
  4675. latestValues.skewX ||
  4676. latestValues.skewY) {
  4677. hasDistortingTransform = true;
  4678. }
  4679. // If there's no distorting values, we don't need to do any more.
  4680. if (!hasDistortingTransform)
  4681. return;
  4682. const resetValues = {};
  4683. if (latestValues.z) {
  4684. resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
  4685. }
  4686. // Check the skew and rotate value of all axes and reset to 0
  4687. for (let i = 0; i < transformAxes.length; i++) {
  4688. resetDistortingTransform(`rotate${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
  4689. resetDistortingTransform(`skew${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
  4690. }
  4691. // Force a render of this element to apply the transform with all skews and rotations
  4692. // set to 0.
  4693. visualElement.render();
  4694. // Put back all the values we reset
  4695. for (const key in resetValues) {
  4696. visualElement.setStaticValue(key, resetValues[key]);
  4697. if (this.animationValues) {
  4698. this.animationValues[key] = resetValues[key];
  4699. }
  4700. }
  4701. // Schedule a render for the next frame. This ensures we won't visually
  4702. // see the element with the reset rotate value applied.
  4703. visualElement.scheduleRender();
  4704. }
  4705. getProjectionStyles(styleProp) {
  4706. if (!this.instance || this.isSVG)
  4707. return undefined;
  4708. if (!this.isVisible) {
  4709. return hiddenVisibility;
  4710. }
  4711. const styles = {
  4712. visibility: "",
  4713. };
  4714. const transformTemplate = this.getTransformTemplate();
  4715. if (this.needsReset) {
  4716. this.needsReset = false;
  4717. styles.opacity = "";
  4718. styles.pointerEvents =
  4719. resolveMotionValue(styleProp?.pointerEvents) || "";
  4720. styles.transform = transformTemplate
  4721. ? transformTemplate(this.latestValues, "")
  4722. : "none";
  4723. return styles;
  4724. }
  4725. const lead = this.getLead();
  4726. if (!this.projectionDelta || !this.layout || !lead.target) {
  4727. const emptyStyles = {};
  4728. if (this.options.layoutId) {
  4729. emptyStyles.opacity =
  4730. this.latestValues.opacity !== undefined
  4731. ? this.latestValues.opacity
  4732. : 1;
  4733. emptyStyles.pointerEvents =
  4734. resolveMotionValue(styleProp?.pointerEvents) || "";
  4735. }
  4736. if (this.hasProjected && !hasTransform(this.latestValues)) {
  4737. emptyStyles.transform = transformTemplate
  4738. ? transformTemplate({}, "")
  4739. : "none";
  4740. this.hasProjected = false;
  4741. }
  4742. return emptyStyles;
  4743. }
  4744. const valuesToRender = lead.animationValues || lead.latestValues;
  4745. this.applyTransformsToTarget();
  4746. styles.transform = buildProjectionTransform(this.projectionDeltaWithTransform, this.treeScale, valuesToRender);
  4747. if (transformTemplate) {
  4748. styles.transform = transformTemplate(valuesToRender, styles.transform);
  4749. }
  4750. const { x, y } = this.projectionDelta;
  4751. styles.transformOrigin = `${x.origin * 100}% ${y.origin * 100}% 0`;
  4752. if (lead.animationValues) {
  4753. /**
  4754. * If the lead component is animating, assign this either the entering/leaving
  4755. * opacity
  4756. */
  4757. styles.opacity =
  4758. lead === this
  4759. ? valuesToRender.opacity ??
  4760. this.latestValues.opacity ??
  4761. 1
  4762. : this.preserveOpacity
  4763. ? this.latestValues.opacity
  4764. : valuesToRender.opacityExit;
  4765. }
  4766. else {
  4767. /**
  4768. * Or we're not animating at all, set the lead component to its layout
  4769. * opacity and other components to hidden.
  4770. */
  4771. styles.opacity =
  4772. lead === this
  4773. ? valuesToRender.opacity !== undefined
  4774. ? valuesToRender.opacity
  4775. : ""
  4776. : valuesToRender.opacityExit !== undefined
  4777. ? valuesToRender.opacityExit
  4778. : 0;
  4779. }
  4780. /**
  4781. * Apply scale correction
  4782. */
  4783. for (const key in scaleCorrectors) {
  4784. if (valuesToRender[key] === undefined)
  4785. continue;
  4786. const { correct, applyTo, isCSSVariable } = scaleCorrectors[key];
  4787. /**
  4788. * Only apply scale correction to the value if we have an
  4789. * active projection transform. Otherwise these values become
  4790. * vulnerable to distortion if the element changes size without
  4791. * a corresponding layout animation.
  4792. */
  4793. const corrected = styles.transform === "none"
  4794. ? valuesToRender[key]
  4795. : correct(valuesToRender[key], lead);
  4796. if (applyTo) {
  4797. const num = applyTo.length;
  4798. for (let i = 0; i < num; i++) {
  4799. styles[applyTo[i]] = corrected;
  4800. }
  4801. }
  4802. else {
  4803. // If this is a CSS variable, set it directly on the instance.
  4804. // Replacing this function from creating styles to setting them
  4805. // would be a good place to remove per frame object creation
  4806. if (isCSSVariable) {
  4807. this.options.visualElement.renderState.vars[key] = corrected;
  4808. }
  4809. else {
  4810. styles[key] = corrected;
  4811. }
  4812. }
  4813. }
  4814. /**
  4815. * Disable pointer events on follow components. This is to ensure
  4816. * that if a follow component covers a lead component it doesn't block
  4817. * pointer events on the lead.
  4818. */
  4819. if (this.options.layoutId) {
  4820. styles.pointerEvents =
  4821. lead === this
  4822. ? resolveMotionValue(styleProp?.pointerEvents) || ""
  4823. : "none";
  4824. }
  4825. return styles;
  4826. }
  4827. clearSnapshot() {
  4828. this.resumeFrom = this.snapshot = undefined;
  4829. }
  4830. // Only run on root
  4831. resetTree() {
  4832. this.root.nodes.forEach((node) => node.currentAnimation?.stop());
  4833. this.root.nodes.forEach(clearMeasurements);
  4834. this.root.sharedNodes.clear();
  4835. }
  4836. };
  4837. }
  4838. function updateLayout(node) {
  4839. node.updateLayout();
  4840. }
  4841. function notifyLayoutUpdate(node) {
  4842. const snapshot = node.resumeFrom?.snapshot || node.snapshot;
  4843. if (node.isLead() &&
  4844. node.layout &&
  4845. snapshot &&
  4846. node.hasListeners("didUpdate")) {
  4847. const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
  4848. const { animationType } = node.options;
  4849. const isShared = snapshot.source !== node.layout.source;
  4850. // TODO Maybe we want to also resize the layout snapshot so we don't trigger
  4851. // animations for instance if layout="size" and an element has only changed position
  4852. if (animationType === "size") {
  4853. eachAxis((axis) => {
  4854. const axisSnapshot = isShared
  4855. ? snapshot.measuredBox[axis]
  4856. : snapshot.layoutBox[axis];
  4857. const length = calcLength(axisSnapshot);
  4858. axisSnapshot.min = layout[axis].min;
  4859. axisSnapshot.max = axisSnapshot.min + length;
  4860. });
  4861. }
  4862. else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
  4863. eachAxis((axis) => {
  4864. const axisSnapshot = isShared
  4865. ? snapshot.measuredBox[axis]
  4866. : snapshot.layoutBox[axis];
  4867. const length = calcLength(layout[axis]);
  4868. axisSnapshot.max = axisSnapshot.min + length;
  4869. /**
  4870. * Ensure relative target gets resized and rerendererd
  4871. */
  4872. if (node.relativeTarget && !node.currentAnimation) {
  4873. node.isProjectionDirty = true;
  4874. node.relativeTarget[axis].max =
  4875. node.relativeTarget[axis].min + length;
  4876. }
  4877. });
  4878. }
  4879. const layoutDelta = createDelta();
  4880. calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
  4881. const visualDelta = createDelta();
  4882. if (isShared) {
  4883. calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
  4884. }
  4885. else {
  4886. calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
  4887. }
  4888. const hasLayoutChanged = !isDeltaZero(layoutDelta);
  4889. let hasRelativeLayoutChanged = false;
  4890. if (!node.resumeFrom) {
  4891. const relativeParent = node.getClosestProjectingParent();
  4892. /**
  4893. * If the relativeParent is itself resuming from a different element then
  4894. * the relative snapshot is not relavent
  4895. */
  4896. if (relativeParent && !relativeParent.resumeFrom) {
  4897. const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
  4898. if (parentSnapshot && parentLayout) {
  4899. const relativeSnapshot = createBox();
  4900. calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox);
  4901. const relativeLayout = createBox();
  4902. calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox);
  4903. if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
  4904. hasRelativeLayoutChanged = true;
  4905. }
  4906. if (relativeParent.options.layoutRoot) {
  4907. node.relativeTarget = relativeLayout;
  4908. node.relativeTargetOrigin = relativeSnapshot;
  4909. node.relativeParent = relativeParent;
  4910. }
  4911. }
  4912. }
  4913. }
  4914. node.notifyListeners("didUpdate", {
  4915. layout,
  4916. snapshot,
  4917. delta: visualDelta,
  4918. layoutDelta,
  4919. hasLayoutChanged,
  4920. hasRelativeLayoutChanged,
  4921. });
  4922. }
  4923. else if (node.isLead()) {
  4924. const { onExitComplete } = node.options;
  4925. onExitComplete && onExitComplete();
  4926. }
  4927. /**
  4928. * Clearing transition
  4929. * TODO: Investigate why this transition is being passed in as {type: false } from Framer
  4930. * and why we need it at all
  4931. */
  4932. node.options.transition = undefined;
  4933. }
  4934. function propagateDirtyNodes(node) {
  4935. /**
  4936. * Increase debug counter for nodes encountered this frame
  4937. */
  4938. if (motionDom.statsBuffer.value) {
  4939. metrics.nodes++;
  4940. }
  4941. if (!node.parent)
  4942. return;
  4943. /**
  4944. * If this node isn't projecting, propagate isProjectionDirty. It will have
  4945. * no performance impact but it will allow the next child that *is* projecting
  4946. * but *isn't* dirty to just check its parent to see if *any* ancestor needs
  4947. * correcting.
  4948. */
  4949. if (!node.isProjecting()) {
  4950. node.isProjectionDirty = node.parent.isProjectionDirty;
  4951. }
  4952. /**
  4953. * Propagate isSharedProjectionDirty and isTransformDirty
  4954. * throughout the whole tree. A future revision can take another look at
  4955. * this but for safety we still recalcualte shared nodes.
  4956. */
  4957. node.isSharedProjectionDirty || (node.isSharedProjectionDirty = Boolean(node.isProjectionDirty ||
  4958. node.parent.isProjectionDirty ||
  4959. node.parent.isSharedProjectionDirty));
  4960. node.isTransformDirty || (node.isTransformDirty = node.parent.isTransformDirty);
  4961. }
  4962. function cleanDirtyNodes(node) {
  4963. node.isProjectionDirty =
  4964. node.isSharedProjectionDirty =
  4965. node.isTransformDirty =
  4966. false;
  4967. }
  4968. function clearSnapshot(node) {
  4969. node.clearSnapshot();
  4970. }
  4971. function clearMeasurements(node) {
  4972. node.clearMeasurements();
  4973. }
  4974. function clearIsLayoutDirty(node) {
  4975. node.isLayoutDirty = false;
  4976. }
  4977. function resetTransformStyle(node) {
  4978. const { visualElement } = node.options;
  4979. if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
  4980. visualElement.notify("BeforeLayoutMeasure");
  4981. }
  4982. node.resetTransform();
  4983. }
  4984. function finishAnimation(node) {
  4985. node.finishAnimation();
  4986. node.targetDelta = node.relativeTarget = node.target = undefined;
  4987. node.isProjectionDirty = true;
  4988. }
  4989. function resolveTargetDelta(node) {
  4990. node.resolveTargetDelta();
  4991. }
  4992. function calcProjection(node) {
  4993. node.calcProjection();
  4994. }
  4995. function resetSkewAndRotation(node) {
  4996. node.resetSkewAndRotation();
  4997. }
  4998. function removeLeadSnapshots(stack) {
  4999. stack.removeLeadSnapshot();
  5000. }
  5001. function mixAxisDelta(output, delta, p) {
  5002. output.translate = mixNumber$1(delta.translate, 0, p);
  5003. output.scale = mixNumber$1(delta.scale, 1, p);
  5004. output.origin = delta.origin;
  5005. output.originPoint = delta.originPoint;
  5006. }
  5007. function mixAxis(output, from, to, p) {
  5008. output.min = mixNumber$1(from.min, to.min, p);
  5009. output.max = mixNumber$1(from.max, to.max, p);
  5010. }
  5011. function mixBox(output, from, to, p) {
  5012. mixAxis(output.x, from.x, to.x, p);
  5013. mixAxis(output.y, from.y, to.y, p);
  5014. }
  5015. function hasOpacityCrossfade(node) {
  5016. return (node.animationValues && node.animationValues.opacityExit !== undefined);
  5017. }
  5018. const defaultLayoutTransition = {
  5019. duration: 0.45,
  5020. ease: [0.4, 0, 0.1, 1],
  5021. };
  5022. const userAgentContains = (string) => typeof navigator !== "undefined" &&
  5023. navigator.userAgent &&
  5024. navigator.userAgent.toLowerCase().includes(string);
  5025. /**
  5026. * Measured bounding boxes must be rounded in Safari and
  5027. * left untouched in Chrome, otherwise non-integer layouts within scaled-up elements
  5028. * can appear to jump.
  5029. */
  5030. const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
  5031. ? Math.round
  5032. : motionUtils.noop;
  5033. function roundAxis(axis) {
  5034. // Round to the nearest .5 pixels to support subpixel layouts
  5035. axis.min = roundPoint(axis.min);
  5036. axis.max = roundPoint(axis.max);
  5037. }
  5038. function roundBox(box) {
  5039. roundAxis(box.x);
  5040. roundAxis(box.y);
  5041. }
  5042. function shouldAnimatePositionOnly(animationType, snapshot, layout) {
  5043. return (animationType === "position" ||
  5044. (animationType === "preserve-aspect" &&
  5045. !isNear(aspectRatio(snapshot), aspectRatio(layout), 0.2)));
  5046. }
  5047. function checkNodeWasScrollRoot(node) {
  5048. return node !== node.root && node.scroll?.wasRoot;
  5049. }
  5050. function addDomEvent(target, eventName, handler, options = { passive: true }) {
  5051. target.addEventListener(eventName, handler, options);
  5052. return () => target.removeEventListener(eventName, handler);
  5053. }
  5054. const DocumentProjectionNode = createProjectionNode$1({
  5055. attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
  5056. measureScroll: () => ({
  5057. x: document.documentElement.scrollLeft || document.body.scrollLeft,
  5058. y: document.documentElement.scrollTop || document.body.scrollTop,
  5059. }),
  5060. checkIsScrollRoot: () => true,
  5061. });
  5062. const rootProjectionNode = {
  5063. current: undefined,
  5064. };
  5065. const HTMLProjectionNode = createProjectionNode$1({
  5066. measureScroll: (instance) => ({
  5067. x: instance.scrollLeft,
  5068. y: instance.scrollTop,
  5069. }),
  5070. defaultParent: () => {
  5071. if (!rootProjectionNode.current) {
  5072. const documentNode = new DocumentProjectionNode({});
  5073. documentNode.mount(window);
  5074. documentNode.setOptions({ layoutScroll: true });
  5075. rootProjectionNode.current = documentNode;
  5076. }
  5077. return rootProjectionNode.current;
  5078. },
  5079. resetTransform: (instance, value) => {
  5080. instance.style.transform = value !== undefined ? value : "none";
  5081. },
  5082. checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
  5083. });
  5084. function pixelsToPercent(pixels, axis) {
  5085. if (axis.max === axis.min)
  5086. return 0;
  5087. return (pixels / (axis.max - axis.min)) * 100;
  5088. }
  5089. /**
  5090. * We always correct borderRadius as a percentage rather than pixels to reduce paints.
  5091. * For example, if you are projecting a box that is 100px wide with a 10px borderRadius
  5092. * into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
  5093. * borderRadius in both states. If we animate between the two in pixels that will trigger
  5094. * a paint each time. If we animate between the two in percentage we'll avoid a paint.
  5095. */
  5096. const correctBorderRadius = {
  5097. correct: (latest, node) => {
  5098. if (!node.target)
  5099. return latest;
  5100. /**
  5101. * If latest is a string, if it's a percentage we can return immediately as it's
  5102. * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
  5103. */
  5104. if (typeof latest === "string") {
  5105. if (px.test(latest)) {
  5106. latest = parseFloat(latest);
  5107. }
  5108. else {
  5109. return latest;
  5110. }
  5111. }
  5112. /**
  5113. * If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
  5114. * pixel value as a percentage of each axis
  5115. */
  5116. const x = pixelsToPercent(latest, node.target.x);
  5117. const y = pixelsToPercent(latest, node.target.y);
  5118. return `${x}% ${y}%`;
  5119. },
  5120. };
  5121. const correctBoxShadow = {
  5122. correct: (latest, { treeScale, projectionDelta }) => {
  5123. const original = latest;
  5124. const shadow = complex.parse(latest);
  5125. // TODO: Doesn't support multiple shadows
  5126. if (shadow.length > 5)
  5127. return original;
  5128. const template = complex.createTransformer(latest);
  5129. const offset = typeof shadow[0] !== "number" ? 1 : 0;
  5130. // Calculate the overall context scale
  5131. const xScale = projectionDelta.x.scale * treeScale.x;
  5132. const yScale = projectionDelta.y.scale * treeScale.y;
  5133. shadow[0 + offset] /= xScale;
  5134. shadow[1 + offset] /= yScale;
  5135. /**
  5136. * Ideally we'd correct x and y scales individually, but because blur and
  5137. * spread apply to both we have to take a scale average and apply that instead.
  5138. * We could potentially improve the outcome of this by incorporating the ratio between
  5139. * the two scales.
  5140. */
  5141. const averageScale = mixNumber$1(xScale, yScale, 0.5);
  5142. // Blur
  5143. if (typeof shadow[2 + offset] === "number")
  5144. shadow[2 + offset] /= averageScale;
  5145. // Spread
  5146. if (typeof shadow[3 + offset] === "number")
  5147. shadow[3 + offset] /= averageScale;
  5148. return template(shadow);
  5149. },
  5150. };
  5151. /**
  5152. * Bounding boxes tend to be defined as top, left, right, bottom. For various operations
  5153. * it's easier to consider each axis individually. This function returns a bounding box
  5154. * as a map of single-axis min/max values.
  5155. */
  5156. function convertBoundingBoxToBox({ top, left, right, bottom, }) {
  5157. return {
  5158. x: { min: left, max: right },
  5159. y: { min: top, max: bottom },
  5160. };
  5161. }
  5162. function convertBoxToBoundingBox({ x, y }) {
  5163. return { top: y.min, right: x.max, bottom: y.max, left: x.min };
  5164. }
  5165. /**
  5166. * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
  5167. * provided by Framer to allow measured points to be corrected for device scaling. This is used
  5168. * when measuring DOM elements and DOM event points.
  5169. */
  5170. function transformBoxPoints(point, transformPoint) {
  5171. if (!transformPoint)
  5172. return point;
  5173. const topLeft = transformPoint({ x: point.left, y: point.top });
  5174. const bottomRight = transformPoint({ x: point.right, y: point.bottom });
  5175. return {
  5176. top: topLeft.y,
  5177. left: topLeft.x,
  5178. bottom: bottomRight.y,
  5179. right: bottomRight.x,
  5180. };
  5181. }
  5182. function measureViewportBox(instance, transformPoint) {
  5183. return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
  5184. }
  5185. function measurePageBox(element, rootProjectionNode, transformPagePoint) {
  5186. const viewportBox = measureViewportBox(element, transformPagePoint);
  5187. const { scroll } = rootProjectionNode;
  5188. if (scroll) {
  5189. translateAxis(viewportBox.x, scroll.offset.x);
  5190. translateAxis(viewportBox.y, scroll.offset.y);
  5191. }
  5192. return viewportBox;
  5193. }
  5194. const featureProps = {
  5195. animation: [
  5196. "animate",
  5197. "variants",
  5198. "whileHover",
  5199. "whileTap",
  5200. "exit",
  5201. "whileInView",
  5202. "whileFocus",
  5203. "whileDrag",
  5204. ],
  5205. exit: ["exit"],
  5206. drag: ["drag", "dragControls"],
  5207. focus: ["whileFocus"],
  5208. hover: ["whileHover", "onHoverStart", "onHoverEnd"],
  5209. tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
  5210. pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
  5211. inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
  5212. layout: ["layout", "layoutId"],
  5213. };
  5214. const featureDefinitions = {};
  5215. for (const key in featureProps) {
  5216. featureDefinitions[key] = {
  5217. isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
  5218. };
  5219. }
  5220. // Does this device prefer reduced motion? Returns `null` server-side.
  5221. const prefersReducedMotion = { current: null };
  5222. const hasReducedMotionListener = { current: false };
  5223. function initPrefersReducedMotion() {
  5224. hasReducedMotionListener.current = true;
  5225. if (!isBrowser)
  5226. return;
  5227. if (window.matchMedia) {
  5228. const motionMediaQuery = window.matchMedia("(prefers-reduced-motion)");
  5229. const setReducedMotionPreferences = () => (prefersReducedMotion.current = motionMediaQuery.matches);
  5230. motionMediaQuery.addListener(setReducedMotionPreferences);
  5231. setReducedMotionPreferences();
  5232. }
  5233. else {
  5234. prefersReducedMotion.current = false;
  5235. }
  5236. }
  5237. /**
  5238. * A list of all ValueTypes
  5239. */
  5240. const valueTypes = [...dimensionValueTypes, color, complex];
  5241. /**
  5242. * Tests a value against the list of ValueTypes
  5243. */
  5244. const findValueType = (v) => valueTypes.find(testValueType(v));
  5245. const visualElementStore = new WeakMap();
  5246. function isAnimationControls(v) {
  5247. return (v !== null &&
  5248. typeof v === "object" &&
  5249. typeof v.start === "function");
  5250. }
  5251. /**
  5252. * Decides if the supplied variable is variant label
  5253. */
  5254. function isVariantLabel(v) {
  5255. return typeof v === "string" || Array.isArray(v);
  5256. }
  5257. const variantPriorityOrder = [
  5258. "animate",
  5259. "whileInView",
  5260. "whileFocus",
  5261. "whileHover",
  5262. "whileTap",
  5263. "whileDrag",
  5264. "exit",
  5265. ];
  5266. const variantProps = ["initial", ...variantPriorityOrder];
  5267. function isControllingVariants(props) {
  5268. return (isAnimationControls(props.animate) ||
  5269. variantProps.some((name) => isVariantLabel(props[name])));
  5270. }
  5271. function isVariantNode(props) {
  5272. return Boolean(isControllingVariants(props) || props.variants);
  5273. }
  5274. function updateMotionValuesFromProps(element, next, prev) {
  5275. for (const key in next) {
  5276. const nextValue = next[key];
  5277. const prevValue = prev[key];
  5278. if (isMotionValue(nextValue)) {
  5279. /**
  5280. * If this is a motion value found in props or style, we want to add it
  5281. * to our visual element's motion value map.
  5282. */
  5283. element.addValue(key, nextValue);
  5284. /**
  5285. * Check the version of the incoming motion value with this version
  5286. * and warn against mismatches.
  5287. */
  5288. if (process.env.NODE_ENV === "development") {
  5289. motionUtils.warnOnce(nextValue.version === "12.7.3", `Attempting to mix Motion versions ${nextValue.version} with 12.7.3 may not work as expected.`);
  5290. }
  5291. }
  5292. else if (isMotionValue(prevValue)) {
  5293. /**
  5294. * If we're swapping from a motion value to a static value,
  5295. * create a new motion value from that
  5296. */
  5297. element.addValue(key, motionDom.motionValue(nextValue, { owner: element }));
  5298. }
  5299. else if (prevValue !== nextValue) {
  5300. /**
  5301. * If this is a flat value that has changed, update the motion value
  5302. * or create one if it doesn't exist. We only want to do this if we're
  5303. * not handling the value with our animation state.
  5304. */
  5305. if (element.hasValue(key)) {
  5306. const existingValue = element.getValue(key);
  5307. if (existingValue.liveStyle === true) {
  5308. existingValue.jump(nextValue);
  5309. }
  5310. else if (!existingValue.hasAnimated) {
  5311. existingValue.set(nextValue);
  5312. }
  5313. }
  5314. else {
  5315. const latestValue = element.getStaticValue(key);
  5316. element.addValue(key, motionDom.motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
  5317. }
  5318. }
  5319. }
  5320. // Handle removed values
  5321. for (const key in prev) {
  5322. if (next[key] === undefined)
  5323. element.removeValue(key);
  5324. }
  5325. return next;
  5326. }
  5327. function getValueState(visualElement) {
  5328. const state = [{}, {}];
  5329. visualElement?.values.forEach((value, key) => {
  5330. state[0][key] = value.get();
  5331. state[1][key] = value.getVelocity();
  5332. });
  5333. return state;
  5334. }
  5335. function resolveVariantFromProps(props, definition, custom, visualElement) {
  5336. /**
  5337. * If the variant definition is a function, resolve.
  5338. */
  5339. if (typeof definition === "function") {
  5340. const [current, velocity] = getValueState(visualElement);
  5341. definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
  5342. }
  5343. /**
  5344. * If the variant definition is a variant label, or
  5345. * the function returned a variant label, resolve.
  5346. */
  5347. if (typeof definition === "string") {
  5348. definition = props.variants && props.variants[definition];
  5349. }
  5350. /**
  5351. * At this point we've resolved both functions and variant labels,
  5352. * but the resolved variant label might itself have been a function.
  5353. * If so, resolve. This can only have returned a valid target object.
  5354. */
  5355. if (typeof definition === "function") {
  5356. const [current, velocity] = getValueState(visualElement);
  5357. definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
  5358. }
  5359. return definition;
  5360. }
  5361. const propEventHandlers = [
  5362. "AnimationStart",
  5363. "AnimationComplete",
  5364. "Update",
  5365. "BeforeLayoutMeasure",
  5366. "LayoutMeasure",
  5367. "LayoutAnimationStart",
  5368. "LayoutAnimationComplete",
  5369. ];
  5370. /**
  5371. * A VisualElement is an imperative abstraction around UI elements such as
  5372. * HTMLElement, SVGElement, Three.Object3D etc.
  5373. */
  5374. class VisualElement {
  5375. /**
  5376. * This method takes React props and returns found MotionValues. For example, HTML
  5377. * MotionValues will be found within the style prop, whereas for Three.js within attribute arrays.
  5378. *
  5379. * This isn't an abstract method as it needs calling in the constructor, but it is
  5380. * intended to be one.
  5381. */
  5382. scrapeMotionValuesFromProps(_props, _prevProps, _visualElement) {
  5383. return {};
  5384. }
  5385. constructor({ parent, props, presenceContext, reducedMotionConfig, blockInitialAnimation, visualState, }, options = {}) {
  5386. /**
  5387. * A reference to the current underlying Instance, e.g. a HTMLElement
  5388. * or Three.Mesh etc.
  5389. */
  5390. this.current = null;
  5391. /**
  5392. * A set containing references to this VisualElement's children.
  5393. */
  5394. this.children = new Set();
  5395. /**
  5396. * Determine what role this visual element should take in the variant tree.
  5397. */
  5398. this.isVariantNode = false;
  5399. this.isControllingVariants = false;
  5400. /**
  5401. * Decides whether this VisualElement should animate in reduced motion
  5402. * mode.
  5403. *
  5404. * TODO: This is currently set on every individual VisualElement but feels
  5405. * like it could be set globally.
  5406. */
  5407. this.shouldReduceMotion = null;
  5408. /**
  5409. * A map of all motion values attached to this visual element. Motion
  5410. * values are source of truth for any given animated value. A motion
  5411. * value might be provided externally by the component via props.
  5412. */
  5413. this.values = new Map();
  5414. this.KeyframeResolver = KeyframeResolver;
  5415. /**
  5416. * Cleanup functions for active features (hover/tap/exit etc)
  5417. */
  5418. this.features = {};
  5419. /**
  5420. * A map of every subscription that binds the provided or generated
  5421. * motion values onChange listeners to this visual element.
  5422. */
  5423. this.valueSubscriptions = new Map();
  5424. /**
  5425. * A reference to the previously-provided motion values as returned
  5426. * from scrapeMotionValuesFromProps. We use the keys in here to determine
  5427. * if any motion values need to be removed after props are updated.
  5428. */
  5429. this.prevMotionValues = {};
  5430. /**
  5431. * An object containing a SubscriptionManager for each active event.
  5432. */
  5433. this.events = {};
  5434. /**
  5435. * An object containing an unsubscribe function for each prop event subscription.
  5436. * For example, every "Update" event can have multiple subscribers via
  5437. * VisualElement.on(), but only one of those can be defined via the onUpdate prop.
  5438. */
  5439. this.propEventSubscriptions = {};
  5440. this.notifyUpdate = () => this.notify("Update", this.latestValues);
  5441. this.render = () => {
  5442. if (!this.current)
  5443. return;
  5444. this.triggerBuild();
  5445. this.renderInstance(this.current, this.renderState, this.props.style, this.projection);
  5446. };
  5447. this.renderScheduledAt = 0.0;
  5448. this.scheduleRender = () => {
  5449. const now = motionDom.time.now();
  5450. if (this.renderScheduledAt < now) {
  5451. this.renderScheduledAt = now;
  5452. motionDom.frame.render(this.render, false, true);
  5453. }
  5454. };
  5455. const { latestValues, renderState, onUpdate } = visualState;
  5456. this.onUpdate = onUpdate;
  5457. this.latestValues = latestValues;
  5458. this.baseTarget = { ...latestValues };
  5459. this.initialValues = props.initial ? { ...latestValues } : {};
  5460. this.renderState = renderState;
  5461. this.parent = parent;
  5462. this.props = props;
  5463. this.presenceContext = presenceContext;
  5464. this.depth = parent ? parent.depth + 1 : 0;
  5465. this.reducedMotionConfig = reducedMotionConfig;
  5466. this.options = options;
  5467. this.blockInitialAnimation = Boolean(blockInitialAnimation);
  5468. this.isControllingVariants = isControllingVariants(props);
  5469. this.isVariantNode = isVariantNode(props);
  5470. if (this.isVariantNode) {
  5471. this.variantChildren = new Set();
  5472. }
  5473. this.manuallyAnimateOnMount = Boolean(parent && parent.current);
  5474. /**
  5475. * Any motion values that are provided to the element when created
  5476. * aren't yet bound to the element, as this would technically be impure.
  5477. * However, we iterate through the motion values and set them to the
  5478. * initial values for this component.
  5479. *
  5480. * TODO: This is impure and we should look at changing this to run on mount.
  5481. * Doing so will break some tests but this isn't necessarily a breaking change,
  5482. * more a reflection of the test.
  5483. */
  5484. const { willChange, ...initialMotionValues } = this.scrapeMotionValuesFromProps(props, {}, this);
  5485. for (const key in initialMotionValues) {
  5486. const value = initialMotionValues[key];
  5487. if (latestValues[key] !== undefined && isMotionValue(value)) {
  5488. value.set(latestValues[key], false);
  5489. }
  5490. }
  5491. }
  5492. mount(instance) {
  5493. this.current = instance;
  5494. visualElementStore.set(instance, this);
  5495. if (this.projection && !this.projection.instance) {
  5496. this.projection.mount(instance);
  5497. }
  5498. if (this.parent && this.isVariantNode && !this.isControllingVariants) {
  5499. this.removeFromVariantTree = this.parent.addVariantChild(this);
  5500. }
  5501. this.values.forEach((value, key) => this.bindToMotionValue(key, value));
  5502. if (!hasReducedMotionListener.current) {
  5503. initPrefersReducedMotion();
  5504. }
  5505. this.shouldReduceMotion =
  5506. this.reducedMotionConfig === "never"
  5507. ? false
  5508. : this.reducedMotionConfig === "always"
  5509. ? true
  5510. : prefersReducedMotion.current;
  5511. if (process.env.NODE_ENV !== "production") {
  5512. motionUtils.warnOnce(this.shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.");
  5513. }
  5514. if (this.parent)
  5515. this.parent.children.add(this);
  5516. this.update(this.props, this.presenceContext);
  5517. }
  5518. unmount() {
  5519. this.projection && this.projection.unmount();
  5520. motionDom.cancelFrame(this.notifyUpdate);
  5521. motionDom.cancelFrame(this.render);
  5522. this.valueSubscriptions.forEach((remove) => remove());
  5523. this.valueSubscriptions.clear();
  5524. this.removeFromVariantTree && this.removeFromVariantTree();
  5525. this.parent && this.parent.children.delete(this);
  5526. for (const key in this.events) {
  5527. this.events[key].clear();
  5528. }
  5529. for (const key in this.features) {
  5530. const feature = this.features[key];
  5531. if (feature) {
  5532. feature.unmount();
  5533. feature.isMounted = false;
  5534. }
  5535. }
  5536. this.current = null;
  5537. }
  5538. bindToMotionValue(key, value) {
  5539. if (this.valueSubscriptions.has(key)) {
  5540. this.valueSubscriptions.get(key)();
  5541. }
  5542. const valueIsTransform = transformProps.has(key);
  5543. if (valueIsTransform && this.onBindTransform) {
  5544. this.onBindTransform();
  5545. }
  5546. const removeOnChange = value.on("change", (latestValue) => {
  5547. this.latestValues[key] = latestValue;
  5548. this.props.onUpdate && motionDom.frame.preRender(this.notifyUpdate);
  5549. if (valueIsTransform && this.projection) {
  5550. this.projection.isTransformDirty = true;
  5551. }
  5552. });
  5553. const removeOnRenderRequest = value.on("renderRequest", this.scheduleRender);
  5554. let removeSyncCheck;
  5555. if (window.MotionCheckAppearSync) {
  5556. removeSyncCheck = window.MotionCheckAppearSync(this, key, value);
  5557. }
  5558. this.valueSubscriptions.set(key, () => {
  5559. removeOnChange();
  5560. removeOnRenderRequest();
  5561. if (removeSyncCheck)
  5562. removeSyncCheck();
  5563. if (value.owner)
  5564. value.stop();
  5565. });
  5566. }
  5567. sortNodePosition(other) {
  5568. /**
  5569. * If these nodes aren't even of the same type we can't compare their depth.
  5570. */
  5571. if (!this.current ||
  5572. !this.sortInstanceNodePosition ||
  5573. this.type !== other.type) {
  5574. return 0;
  5575. }
  5576. return this.sortInstanceNodePosition(this.current, other.current);
  5577. }
  5578. updateFeatures() {
  5579. let key = "animation";
  5580. for (key in featureDefinitions) {
  5581. const featureDefinition = featureDefinitions[key];
  5582. if (!featureDefinition)
  5583. continue;
  5584. const { isEnabled, Feature: FeatureConstructor } = featureDefinition;
  5585. /**
  5586. * If this feature is enabled but not active, make a new instance.
  5587. */
  5588. if (!this.features[key] &&
  5589. FeatureConstructor &&
  5590. isEnabled(this.props)) {
  5591. this.features[key] = new FeatureConstructor(this);
  5592. }
  5593. /**
  5594. * If we have a feature, mount or update it.
  5595. */
  5596. if (this.features[key]) {
  5597. const feature = this.features[key];
  5598. if (feature.isMounted) {
  5599. feature.update();
  5600. }
  5601. else {
  5602. feature.mount();
  5603. feature.isMounted = true;
  5604. }
  5605. }
  5606. }
  5607. }
  5608. triggerBuild() {
  5609. this.build(this.renderState, this.latestValues, this.props);
  5610. }
  5611. /**
  5612. * Measure the current viewport box with or without transforms.
  5613. * Only measures axis-aligned boxes, rotate and skew must be manually
  5614. * removed with a re-render to work.
  5615. */
  5616. measureViewportBox() {
  5617. return this.current
  5618. ? this.measureInstanceViewportBox(this.current, this.props)
  5619. : createBox();
  5620. }
  5621. getStaticValue(key) {
  5622. return this.latestValues[key];
  5623. }
  5624. setStaticValue(key, value) {
  5625. this.latestValues[key] = value;
  5626. }
  5627. /**
  5628. * Update the provided props. Ensure any newly-added motion values are
  5629. * added to our map, old ones removed, and listeners updated.
  5630. */
  5631. update(props, presenceContext) {
  5632. if (props.transformTemplate || this.props.transformTemplate) {
  5633. this.scheduleRender();
  5634. }
  5635. this.prevProps = this.props;
  5636. this.props = props;
  5637. this.prevPresenceContext = this.presenceContext;
  5638. this.presenceContext = presenceContext;
  5639. /**
  5640. * Update prop event handlers ie onAnimationStart, onAnimationComplete
  5641. */
  5642. for (let i = 0; i < propEventHandlers.length; i++) {
  5643. const key = propEventHandlers[i];
  5644. if (this.propEventSubscriptions[key]) {
  5645. this.propEventSubscriptions[key]();
  5646. delete this.propEventSubscriptions[key];
  5647. }
  5648. const listenerName = ("on" + key);
  5649. const listener = props[listenerName];
  5650. if (listener) {
  5651. this.propEventSubscriptions[key] = this.on(key, listener);
  5652. }
  5653. }
  5654. this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps, this), this.prevMotionValues);
  5655. if (this.handleChildMotionValue) {
  5656. this.handleChildMotionValue();
  5657. }
  5658. this.onUpdate && this.onUpdate(this);
  5659. }
  5660. getProps() {
  5661. return this.props;
  5662. }
  5663. /**
  5664. * Returns the variant definition with a given name.
  5665. */
  5666. getVariant(name) {
  5667. return this.props.variants ? this.props.variants[name] : undefined;
  5668. }
  5669. /**
  5670. * Returns the defined default transition on this component.
  5671. */
  5672. getDefaultTransition() {
  5673. return this.props.transition;
  5674. }
  5675. getTransformPagePoint() {
  5676. return this.props.transformPagePoint;
  5677. }
  5678. getClosestVariantNode() {
  5679. return this.isVariantNode
  5680. ? this
  5681. : this.parent
  5682. ? this.parent.getClosestVariantNode()
  5683. : undefined;
  5684. }
  5685. /**
  5686. * Add a child visual element to our set of children.
  5687. */
  5688. addVariantChild(child) {
  5689. const closestVariantNode = this.getClosestVariantNode();
  5690. if (closestVariantNode) {
  5691. closestVariantNode.variantChildren &&
  5692. closestVariantNode.variantChildren.add(child);
  5693. return () => closestVariantNode.variantChildren.delete(child);
  5694. }
  5695. }
  5696. /**
  5697. * Add a motion value and bind it to this visual element.
  5698. */
  5699. addValue(key, value) {
  5700. // Remove existing value if it exists
  5701. const existingValue = this.values.get(key);
  5702. if (value !== existingValue) {
  5703. if (existingValue)
  5704. this.removeValue(key);
  5705. this.bindToMotionValue(key, value);
  5706. this.values.set(key, value);
  5707. this.latestValues[key] = value.get();
  5708. }
  5709. }
  5710. /**
  5711. * Remove a motion value and unbind any active subscriptions.
  5712. */
  5713. removeValue(key) {
  5714. this.values.delete(key);
  5715. const unsubscribe = this.valueSubscriptions.get(key);
  5716. if (unsubscribe) {
  5717. unsubscribe();
  5718. this.valueSubscriptions.delete(key);
  5719. }
  5720. delete this.latestValues[key];
  5721. this.removeValueFromRenderState(key, this.renderState);
  5722. }
  5723. /**
  5724. * Check whether we have a motion value for this key
  5725. */
  5726. hasValue(key) {
  5727. return this.values.has(key);
  5728. }
  5729. getValue(key, defaultValue) {
  5730. if (this.props.values && this.props.values[key]) {
  5731. return this.props.values[key];
  5732. }
  5733. let value = this.values.get(key);
  5734. if (value === undefined && defaultValue !== undefined) {
  5735. value = motionDom.motionValue(defaultValue === null ? undefined : defaultValue, { owner: this });
  5736. this.addValue(key, value);
  5737. }
  5738. return value;
  5739. }
  5740. /**
  5741. * If we're trying to animate to a previously unencountered value,
  5742. * we need to check for it in our state and as a last resort read it
  5743. * directly from the instance (which might have performance implications).
  5744. */
  5745. readValue(key, target) {
  5746. let value = this.latestValues[key] !== undefined || !this.current
  5747. ? this.latestValues[key]
  5748. : this.getBaseTargetFromProps(this.props, key) ??
  5749. this.readValueFromInstance(this.current, key, this.options);
  5750. if (value !== undefined && value !== null) {
  5751. if (typeof value === "string" &&
  5752. (isNumericalString(value) || isZeroValueString(value))) {
  5753. // If this is a number read as a string, ie "0" or "200", convert it to a number
  5754. value = parseFloat(value);
  5755. }
  5756. else if (!findValueType(value) && complex.test(target)) {
  5757. value = getAnimatableNone(key, target);
  5758. }
  5759. this.setBaseTarget(key, isMotionValue(value) ? value.get() : value);
  5760. }
  5761. return isMotionValue(value) ? value.get() : value;
  5762. }
  5763. /**
  5764. * Set the base target to later animate back to. This is currently
  5765. * only hydrated on creation and when we first read a value.
  5766. */
  5767. setBaseTarget(key, value) {
  5768. this.baseTarget[key] = value;
  5769. }
  5770. /**
  5771. * Find the base target for a value thats been removed from all animation
  5772. * props.
  5773. */
  5774. getBaseTarget(key) {
  5775. const { initial } = this.props;
  5776. let valueFromInitial;
  5777. if (typeof initial === "string" || typeof initial === "object") {
  5778. const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom);
  5779. if (variant) {
  5780. valueFromInitial = variant[key];
  5781. }
  5782. }
  5783. /**
  5784. * If this value still exists in the current initial variant, read that.
  5785. */
  5786. if (initial && valueFromInitial !== undefined) {
  5787. return valueFromInitial;
  5788. }
  5789. /**
  5790. * Alternatively, if this VisualElement config has defined a getBaseTarget
  5791. * so we can read the value from an alternative source, try that.
  5792. */
  5793. const target = this.getBaseTargetFromProps(this.props, key);
  5794. if (target !== undefined && !isMotionValue(target))
  5795. return target;
  5796. /**
  5797. * If the value was initially defined on initial, but it doesn't any more,
  5798. * return undefined. Otherwise return the value as initially read from the DOM.
  5799. */
  5800. return this.initialValues[key] !== undefined &&
  5801. valueFromInitial === undefined
  5802. ? undefined
  5803. : this.baseTarget[key];
  5804. }
  5805. on(eventName, callback) {
  5806. if (!this.events[eventName]) {
  5807. this.events[eventName] = new motionUtils.SubscriptionManager();
  5808. }
  5809. return this.events[eventName].add(callback);
  5810. }
  5811. notify(eventName, ...args) {
  5812. if (this.events[eventName]) {
  5813. this.events[eventName].notify(...args);
  5814. }
  5815. }
  5816. }
  5817. class DOMVisualElement extends VisualElement {
  5818. constructor() {
  5819. super(...arguments);
  5820. this.KeyframeResolver = DOMKeyframesResolver;
  5821. }
  5822. sortInstanceNodePosition(a, b) {
  5823. /**
  5824. * compareDocumentPosition returns a bitmask, by using the bitwise &
  5825. * we're returning true if 2 in that bitmask is set to true. 2 is set
  5826. * to true if b preceeds a.
  5827. */
  5828. return a.compareDocumentPosition(b) & 2 ? 1 : -1;
  5829. }
  5830. getBaseTargetFromProps(props, key) {
  5831. return props.style
  5832. ? props.style[key]
  5833. : undefined;
  5834. }
  5835. removeValueFromRenderState(key, { vars, style }) {
  5836. delete vars[key];
  5837. delete style[key];
  5838. }
  5839. handleChildMotionValue() {
  5840. if (this.childSubscription) {
  5841. this.childSubscription();
  5842. delete this.childSubscription;
  5843. }
  5844. const { children } = this.props;
  5845. if (isMotionValue(children)) {
  5846. this.childSubscription = children.on("change", (latest) => {
  5847. if (this.current) {
  5848. this.current.textContent = `${latest}`;
  5849. }
  5850. });
  5851. }
  5852. }
  5853. }
  5854. /**
  5855. * Provided a value and a ValueType, returns the value as that value type.
  5856. */
  5857. const getValueAsType = (value, type) => {
  5858. return type && typeof value === "number"
  5859. ? type.transform(value)
  5860. : value;
  5861. };
  5862. const translateAlias = {
  5863. x: "translateX",
  5864. y: "translateY",
  5865. z: "translateZ",
  5866. transformPerspective: "perspective",
  5867. };
  5868. const numTransforms = transformPropOrder.length;
  5869. /**
  5870. * Build a CSS transform style from individual x/y/scale etc properties.
  5871. *
  5872. * This outputs with a default order of transforms/scales/rotations, this can be customised by
  5873. * providing a transformTemplate function.
  5874. */
  5875. function buildTransform(latestValues, transform, transformTemplate) {
  5876. // The transform string we're going to build into.
  5877. let transformString = "";
  5878. let transformIsDefault = true;
  5879. /**
  5880. * Loop over all possible transforms in order, adding the ones that
  5881. * are present to the transform string.
  5882. */
  5883. for (let i = 0; i < numTransforms; i++) {
  5884. const key = transformPropOrder[i];
  5885. const value = latestValues[key];
  5886. if (value === undefined)
  5887. continue;
  5888. let valueIsDefault = true;
  5889. if (typeof value === "number") {
  5890. valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
  5891. }
  5892. else {
  5893. valueIsDefault = parseFloat(value) === 0;
  5894. }
  5895. if (!valueIsDefault || transformTemplate) {
  5896. const valueAsType = getValueAsType(value, numberValueTypes[key]);
  5897. if (!valueIsDefault) {
  5898. transformIsDefault = false;
  5899. const transformName = translateAlias[key] || key;
  5900. transformString += `${transformName}(${valueAsType}) `;
  5901. }
  5902. if (transformTemplate) {
  5903. transform[key] = valueAsType;
  5904. }
  5905. }
  5906. }
  5907. transformString = transformString.trim();
  5908. // If we have a custom `transform` template, pass our transform values and
  5909. // generated transformString to that before returning
  5910. if (transformTemplate) {
  5911. transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
  5912. }
  5913. else if (transformIsDefault) {
  5914. transformString = "none";
  5915. }
  5916. return transformString;
  5917. }
  5918. function buildHTMLStyles(state, latestValues, transformTemplate) {
  5919. const { style, vars, transformOrigin } = state;
  5920. // Track whether we encounter any transform or transformOrigin values.
  5921. let hasTransform = false;
  5922. let hasTransformOrigin = false;
  5923. /**
  5924. * Loop over all our latest animated values and decide whether to handle them
  5925. * as a style or CSS variable.
  5926. *
  5927. * Transforms and transform origins are kept separately for further processing.
  5928. */
  5929. for (const key in latestValues) {
  5930. const value = latestValues[key];
  5931. if (transformProps.has(key)) {
  5932. // If this is a transform, flag to enable further transform processing
  5933. hasTransform = true;
  5934. continue;
  5935. }
  5936. else if (isCSSVariableName(key)) {
  5937. vars[key] = value;
  5938. continue;
  5939. }
  5940. else {
  5941. // Convert the value to its default value type, ie 0 -> "0px"
  5942. const valueAsType = getValueAsType(value, numberValueTypes[key]);
  5943. if (key.startsWith("origin")) {
  5944. // If this is a transform origin, flag and enable further transform-origin processing
  5945. hasTransformOrigin = true;
  5946. transformOrigin[key] =
  5947. valueAsType;
  5948. }
  5949. else {
  5950. style[key] = valueAsType;
  5951. }
  5952. }
  5953. }
  5954. if (!latestValues.transform) {
  5955. if (hasTransform || transformTemplate) {
  5956. style.transform = buildTransform(latestValues, state.transform, transformTemplate);
  5957. }
  5958. else if (style.transform) {
  5959. /**
  5960. * If we have previously created a transform but currently don't have any,
  5961. * reset transform style to none.
  5962. */
  5963. style.transform = "none";
  5964. }
  5965. }
  5966. /**
  5967. * Build a transformOrigin style. Uses the same defaults as the browser for
  5968. * undefined origins.
  5969. */
  5970. if (hasTransformOrigin) {
  5971. const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
  5972. style.transformOrigin = `${originX} ${originY} ${originZ}`;
  5973. }
  5974. }
  5975. function renderHTML(element, { style, vars }, styleProp, projection) {
  5976. Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp));
  5977. // Loop over any CSS variables and assign those.
  5978. for (const key in vars) {
  5979. element.style.setProperty(key, vars[key]);
  5980. }
  5981. }
  5982. function isForcedMotionValue(key, { layout, layoutId }) {
  5983. return (transformProps.has(key) ||
  5984. key.startsWith("origin") ||
  5985. ((layout || layoutId !== undefined) &&
  5986. (!!scaleCorrectors[key] || key === "opacity")));
  5987. }
  5988. function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
  5989. const { style } = props;
  5990. const newValues = {};
  5991. for (const key in style) {
  5992. if (isMotionValue(style[key]) ||
  5993. (prevProps.style &&
  5994. isMotionValue(prevProps.style[key])) ||
  5995. isForcedMotionValue(key, props) ||
  5996. visualElement?.getValue(key)?.liveStyle !== undefined) {
  5997. newValues[key] = style[key];
  5998. }
  5999. }
  6000. return newValues;
  6001. }
  6002. function getComputedStyle$1(element) {
  6003. return window.getComputedStyle(element);
  6004. }
  6005. class HTMLVisualElement extends DOMVisualElement {
  6006. constructor() {
  6007. super(...arguments);
  6008. this.type = "html";
  6009. this.renderInstance = renderHTML;
  6010. }
  6011. readValueFromInstance(instance, key) {
  6012. if (transformProps.has(key)) {
  6013. return readTransformValue(instance, key);
  6014. }
  6015. else {
  6016. const computedStyle = getComputedStyle$1(instance);
  6017. const value = (isCSSVariableName(key)
  6018. ? computedStyle.getPropertyValue(key)
  6019. : computedStyle[key]) || 0;
  6020. return typeof value === "string" ? value.trim() : value;
  6021. }
  6022. }
  6023. measureInstanceViewportBox(instance, { transformPagePoint }) {
  6024. return measureViewportBox(instance, transformPagePoint);
  6025. }
  6026. build(renderState, latestValues, props) {
  6027. buildHTMLStyles(renderState, latestValues, props.transformTemplate);
  6028. }
  6029. scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  6030. return scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
  6031. }
  6032. }
  6033. const LazyContext = React.createContext({ strict: false });
  6034. function loadFeatures(features) {
  6035. for (const key in features) {
  6036. featureDefinitions[key] = {
  6037. ...featureDefinitions[key],
  6038. ...features[key],
  6039. };
  6040. }
  6041. }
  6042. /**
  6043. * A list of all valid MotionProps.
  6044. *
  6045. * @privateRemarks
  6046. * This doesn't throw if a `MotionProp` name is missing - it should.
  6047. */
  6048. const validMotionProps = new Set([
  6049. "animate",
  6050. "exit",
  6051. "variants",
  6052. "initial",
  6053. "style",
  6054. "values",
  6055. "variants",
  6056. "transition",
  6057. "transformTemplate",
  6058. "custom",
  6059. "inherit",
  6060. "onBeforeLayoutMeasure",
  6061. "onAnimationStart",
  6062. "onAnimationComplete",
  6063. "onUpdate",
  6064. "onDragStart",
  6065. "onDrag",
  6066. "onDragEnd",
  6067. "onMeasureDragConstraints",
  6068. "onDirectionLock",
  6069. "onDragTransitionEnd",
  6070. "_dragX",
  6071. "_dragY",
  6072. "onHoverStart",
  6073. "onHoverEnd",
  6074. "onViewportEnter",
  6075. "onViewportLeave",
  6076. "globalTapTarget",
  6077. "ignoreStrict",
  6078. "viewport",
  6079. ]);
  6080. /**
  6081. * Check whether a prop name is a valid `MotionProp` key.
  6082. *
  6083. * @param key - Name of the property to check
  6084. * @returns `true` is key is a valid `MotionProp`.
  6085. *
  6086. * @public
  6087. */
  6088. function isValidMotionProp(key) {
  6089. return (key.startsWith("while") ||
  6090. (key.startsWith("drag") && key !== "draggable") ||
  6091. key.startsWith("layout") ||
  6092. key.startsWith("onTap") ||
  6093. key.startsWith("onPan") ||
  6094. key.startsWith("onLayout") ||
  6095. validMotionProps.has(key));
  6096. }
  6097. let shouldForward = (key) => !isValidMotionProp(key);
  6098. function loadExternalIsValidProp(isValidProp) {
  6099. if (!isValidProp)
  6100. return;
  6101. // Explicitly filter our events
  6102. shouldForward = (key) => key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key);
  6103. }
  6104. /**
  6105. * Emotion and Styled Components both allow users to pass through arbitrary props to their components
  6106. * to dynamically generate CSS. They both use the `@emotion/is-prop-valid` package to determine which
  6107. * of these should be passed to the underlying DOM node.
  6108. *
  6109. * However, when styling a Motion component `styled(motion.div)`, both packages pass through *all* props
  6110. * as it's seen as an arbitrary component rather than a DOM node. Motion only allows arbitrary props
  6111. * passed through the `custom` prop so it doesn't *need* the payload or computational overhead of
  6112. * `@emotion/is-prop-valid`, however to fix this problem we need to use it.
  6113. *
  6114. * By making it an optionalDependency we can offer this functionality only in the situations where it's
  6115. * actually required.
  6116. */
  6117. try {
  6118. /**
  6119. * We attempt to import this package but require won't be defined in esm environments, in that case
  6120. * isPropValid will have to be provided via `MotionContext`. In a 6.0.0 this should probably be removed
  6121. * in favour of explicit injection.
  6122. */
  6123. loadExternalIsValidProp(require("@emotion/is-prop-valid").default);
  6124. }
  6125. catch {
  6126. // We don't need to actually do anything here - the fallback is the existing `isPropValid`.
  6127. }
  6128. function filterProps(props, isDom, forwardMotionProps) {
  6129. const filteredProps = {};
  6130. for (const key in props) {
  6131. /**
  6132. * values is considered a valid prop by Emotion, so if it's present
  6133. * this will be rendered out to the DOM unless explicitly filtered.
  6134. *
  6135. * We check the type as it could be used with the `feColorMatrix`
  6136. * element, which we support.
  6137. */
  6138. if (key === "values" && typeof props.values === "object")
  6139. continue;
  6140. if (shouldForward(key) ||
  6141. (forwardMotionProps === true && isValidMotionProp(key)) ||
  6142. (!isDom && !isValidMotionProp(key)) ||
  6143. // If trying to use native HTML drag events, forward drag listeners
  6144. (props["draggable"] &&
  6145. key.startsWith("onDrag"))) {
  6146. filteredProps[key] =
  6147. props[key];
  6148. }
  6149. }
  6150. return filteredProps;
  6151. }
  6152. function resolveVariant(visualElement, definition, custom) {
  6153. const props = visualElement.getProps();
  6154. return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
  6155. }
  6156. /**
  6157. * Set VisualElement's MotionValue, creating a new MotionValue for it if
  6158. * it doesn't exist.
  6159. */
  6160. function setMotionValue(visualElement, key, value) {
  6161. if (visualElement.hasValue(key)) {
  6162. visualElement.getValue(key).set(value);
  6163. }
  6164. else {
  6165. visualElement.addValue(key, motionDom.motionValue(value));
  6166. }
  6167. }
  6168. function setTarget(visualElement, definition) {
  6169. const resolved = resolveVariant(visualElement, definition);
  6170. let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
  6171. target = { ...target, ...transitionEnd };
  6172. for (const key in target) {
  6173. const value = resolveFinalValueInKeyframes(target[key]);
  6174. setMotionValue(visualElement, key, value);
  6175. }
  6176. }
  6177. function isWillChangeMotionValue(value) {
  6178. return Boolean(isMotionValue(value) && value.add);
  6179. }
  6180. function addValueToWillChange(visualElement, key) {
  6181. const willChange = visualElement.getValue("willChange");
  6182. /**
  6183. * It could be that a user has set willChange to a regular MotionValue,
  6184. * in which case we can't add the value to it.
  6185. */
  6186. if (isWillChangeMotionValue(willChange)) {
  6187. return willChange.add(key);
  6188. }
  6189. else if (!willChange && motionUtils.MotionGlobalConfig.WillChange) {
  6190. const newWillChange = new motionUtils.MotionGlobalConfig.WillChange("auto");
  6191. visualElement.addValue("willChange", newWillChange);
  6192. newWillChange.add(key);
  6193. }
  6194. }
  6195. /**
  6196. * Decide whether we should block this animation. Previously, we achieved this
  6197. * just by checking whether the key was listed in protectedKeys, but this
  6198. * posed problems if an animation was triggered by afterChildren and protectedKeys
  6199. * had been set to true in the meantime.
  6200. */
  6201. function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
  6202. const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
  6203. needsAnimating[key] = false;
  6204. return shouldBlock;
  6205. }
  6206. function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) {
  6207. let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = targetAndTransition;
  6208. if (transitionOverride)
  6209. transition = transitionOverride;
  6210. const animations = [];
  6211. const animationTypeState = type &&
  6212. visualElement.animationState &&
  6213. visualElement.animationState.getState()[type];
  6214. for (const key in target) {
  6215. const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
  6216. const valueTarget = target[key];
  6217. if (valueTarget === undefined ||
  6218. (animationTypeState &&
  6219. shouldBlockAnimation(animationTypeState, key))) {
  6220. continue;
  6221. }
  6222. const valueTransition = {
  6223. delay,
  6224. ...motionDom.getValueTransition(transition || {}, key),
  6225. };
  6226. /**
  6227. * If this is the first time a value is being animated, check
  6228. * to see if we're handling off from an existing animation.
  6229. */
  6230. let isHandoff = false;
  6231. if (window.MotionHandoffAnimation) {
  6232. const appearId = getOptimisedAppearId(visualElement);
  6233. if (appearId) {
  6234. const startTime = window.MotionHandoffAnimation(appearId, key, motionDom.frame);
  6235. if (startTime !== null) {
  6236. valueTransition.startTime = startTime;
  6237. isHandoff = true;
  6238. }
  6239. }
  6240. }
  6241. addValueToWillChange(visualElement, key);
  6242. value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
  6243. ? { type: false }
  6244. : valueTransition, visualElement, isHandoff));
  6245. const animation = value.animation;
  6246. if (animation) {
  6247. animations.push(animation);
  6248. }
  6249. }
  6250. if (transitionEnd) {
  6251. Promise.all(animations).then(() => {
  6252. motionDom.frame.update(() => {
  6253. transitionEnd && setTarget(visualElement, transitionEnd);
  6254. });
  6255. });
  6256. }
  6257. return animations;
  6258. }
  6259. function animateVariant(visualElement, variant, options = {}) {
  6260. const resolved = resolveVariant(visualElement, variant, options.type === "exit"
  6261. ? visualElement.presenceContext?.custom
  6262. : undefined);
  6263. let { transition = visualElement.getDefaultTransition() || {} } = resolved || {};
  6264. if (options.transitionOverride) {
  6265. transition = options.transitionOverride;
  6266. }
  6267. /**
  6268. * If we have a variant, create a callback that runs it as an animation.
  6269. * Otherwise, we resolve a Promise immediately for a composable no-op.
  6270. */
  6271. const getAnimation = resolved
  6272. ? () => Promise.all(animateTarget(visualElement, resolved, options))
  6273. : () => Promise.resolve();
  6274. /**
  6275. * If we have children, create a callback that runs all their animations.
  6276. * Otherwise, we resolve a Promise immediately for a composable no-op.
  6277. */
  6278. const getChildAnimations = visualElement.variantChildren && visualElement.variantChildren.size
  6279. ? (forwardDelay = 0) => {
  6280. const { delayChildren = 0, staggerChildren, staggerDirection, } = transition;
  6281. return animateChildren(visualElement, variant, delayChildren + forwardDelay, staggerChildren, staggerDirection, options);
  6282. }
  6283. : () => Promise.resolve();
  6284. /**
  6285. * If the transition explicitly defines a "when" option, we need to resolve either
  6286. * this animation or all children animations before playing the other.
  6287. */
  6288. const { when } = transition;
  6289. if (when) {
  6290. const [first, last] = when === "beforeChildren"
  6291. ? [getAnimation, getChildAnimations]
  6292. : [getChildAnimations, getAnimation];
  6293. return first().then(() => last());
  6294. }
  6295. else {
  6296. return Promise.all([getAnimation(), getChildAnimations(options.delay)]);
  6297. }
  6298. }
  6299. function animateChildren(visualElement, variant, delayChildren = 0, staggerChildren = 0, staggerDirection = 1, options) {
  6300. const animations = [];
  6301. const maxStaggerDuration = (visualElement.variantChildren.size - 1) * staggerChildren;
  6302. const generateStaggerDuration = staggerDirection === 1
  6303. ? (i = 0) => i * staggerChildren
  6304. : (i = 0) => maxStaggerDuration - i * staggerChildren;
  6305. Array.from(visualElement.variantChildren)
  6306. .sort(sortByTreeOrder)
  6307. .forEach((child, i) => {
  6308. child.notify("AnimationStart", variant);
  6309. animations.push(animateVariant(child, variant, {
  6310. ...options,
  6311. delay: delayChildren + generateStaggerDuration(i),
  6312. }).then(() => child.notify("AnimationComplete", variant)));
  6313. });
  6314. return Promise.all(animations);
  6315. }
  6316. function sortByTreeOrder(a, b) {
  6317. return a.sortNodePosition(b);
  6318. }
  6319. function animateVisualElement(visualElement, definition, options = {}) {
  6320. visualElement.notify("AnimationStart", definition);
  6321. let animation;
  6322. if (Array.isArray(definition)) {
  6323. const animations = definition.map((variant) => animateVariant(visualElement, variant, options));
  6324. animation = Promise.all(animations);
  6325. }
  6326. else if (typeof definition === "string") {
  6327. animation = animateVariant(visualElement, definition, options);
  6328. }
  6329. else {
  6330. const resolvedDefinition = typeof definition === "function"
  6331. ? resolveVariant(visualElement, definition, options.custom)
  6332. : definition;
  6333. animation = Promise.all(animateTarget(visualElement, resolvedDefinition, options));
  6334. }
  6335. return animation.then(() => {
  6336. visualElement.notify("AnimationComplete", definition);
  6337. });
  6338. }
  6339. function shallowCompare(next, prev) {
  6340. if (!Array.isArray(prev))
  6341. return false;
  6342. const prevLength = prev.length;
  6343. if (prevLength !== next.length)
  6344. return false;
  6345. for (let i = 0; i < prevLength; i++) {
  6346. if (prev[i] !== next[i])
  6347. return false;
  6348. }
  6349. return true;
  6350. }
  6351. const numVariantProps = variantProps.length;
  6352. function getVariantContext(visualElement) {
  6353. if (!visualElement)
  6354. return undefined;
  6355. if (!visualElement.isControllingVariants) {
  6356. const context = visualElement.parent
  6357. ? getVariantContext(visualElement.parent) || {}
  6358. : {};
  6359. if (visualElement.props.initial !== undefined) {
  6360. context.initial = visualElement.props.initial;
  6361. }
  6362. return context;
  6363. }
  6364. const context = {};
  6365. for (let i = 0; i < numVariantProps; i++) {
  6366. const name = variantProps[i];
  6367. const prop = visualElement.props[name];
  6368. if (isVariantLabel(prop) || prop === false) {
  6369. context[name] = prop;
  6370. }
  6371. }
  6372. return context;
  6373. }
  6374. const reversePriorityOrder = [...variantPriorityOrder].reverse();
  6375. const numAnimationTypes = variantPriorityOrder.length;
  6376. function animateList(visualElement) {
  6377. return (animations) => Promise.all(animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options)));
  6378. }
  6379. function createAnimationState(visualElement) {
  6380. let animate = animateList(visualElement);
  6381. let state = createState();
  6382. let isInitialRender = true;
  6383. /**
  6384. * This function will be used to reduce the animation definitions for
  6385. * each active animation type into an object of resolved values for it.
  6386. */
  6387. const buildResolvedTypeValues = (type) => (acc, definition) => {
  6388. const resolved = resolveVariant(visualElement, definition, type === "exit"
  6389. ? visualElement.presenceContext?.custom
  6390. : undefined);
  6391. if (resolved) {
  6392. const { transition, transitionEnd, ...target } = resolved;
  6393. acc = { ...acc, ...target, ...transitionEnd };
  6394. }
  6395. return acc;
  6396. };
  6397. /**
  6398. * This just allows us to inject mocked animation functions
  6399. * @internal
  6400. */
  6401. function setAnimateFunction(makeAnimator) {
  6402. animate = makeAnimator(visualElement);
  6403. }
  6404. /**
  6405. * When we receive new props, we need to:
  6406. * 1. Create a list of protected keys for each type. This is a directory of
  6407. * value keys that are currently being "handled" by types of a higher priority
  6408. * so that whenever an animation is played of a given type, these values are
  6409. * protected from being animated.
  6410. * 2. Determine if an animation type needs animating.
  6411. * 3. Determine if any values have been removed from a type and figure out
  6412. * what to animate those to.
  6413. */
  6414. function animateChanges(changedActiveType) {
  6415. const { props } = visualElement;
  6416. const context = getVariantContext(visualElement.parent) || {};
  6417. /**
  6418. * A list of animations that we'll build into as we iterate through the animation
  6419. * types. This will get executed at the end of the function.
  6420. */
  6421. const animations = [];
  6422. /**
  6423. * Keep track of which values have been removed. Then, as we hit lower priority
  6424. * animation types, we can check if they contain removed values and animate to that.
  6425. */
  6426. const removedKeys = new Set();
  6427. /**
  6428. * A dictionary of all encountered keys. This is an object to let us build into and
  6429. * copy it without iteration. Each time we hit an animation type we set its protected
  6430. * keys - the keys its not allowed to animate - to the latest version of this object.
  6431. */
  6432. let encounteredKeys = {};
  6433. /**
  6434. * If a variant has been removed at a given index, and this component is controlling
  6435. * variant animations, we want to ensure lower-priority variants are forced to animate.
  6436. */
  6437. let removedVariantIndex = Infinity;
  6438. /**
  6439. * Iterate through all animation types in reverse priority order. For each, we want to
  6440. * detect which values it's handling and whether or not they've changed (and therefore
  6441. * need to be animated). If any values have been removed, we want to detect those in
  6442. * lower priority props and flag for animation.
  6443. */
  6444. for (let i = 0; i < numAnimationTypes; i++) {
  6445. const type = reversePriorityOrder[i];
  6446. const typeState = state[type];
  6447. const prop = props[type] !== undefined
  6448. ? props[type]
  6449. : context[type];
  6450. const propIsVariant = isVariantLabel(prop);
  6451. /**
  6452. * If this type has *just* changed isActive status, set activeDelta
  6453. * to that status. Otherwise set to null.
  6454. */
  6455. const activeDelta = type === changedActiveType ? typeState.isActive : null;
  6456. if (activeDelta === false)
  6457. removedVariantIndex = i;
  6458. /**
  6459. * If this prop is an inherited variant, rather than been set directly on the
  6460. * component itself, we want to make sure we allow the parent to trigger animations.
  6461. *
  6462. * TODO: Can probably change this to a !isControllingVariants check
  6463. */
  6464. let isInherited = prop === context[type] &&
  6465. prop !== props[type] &&
  6466. propIsVariant;
  6467. /**
  6468. *
  6469. */
  6470. if (isInherited &&
  6471. isInitialRender &&
  6472. visualElement.manuallyAnimateOnMount) {
  6473. isInherited = false;
  6474. }
  6475. /**
  6476. * Set all encountered keys so far as the protected keys for this type. This will
  6477. * be any key that has been animated or otherwise handled by active, higher-priortiy types.
  6478. */
  6479. typeState.protectedKeys = { ...encounteredKeys };
  6480. // Check if we can skip analysing this prop early
  6481. if (
  6482. // If it isn't active and hasn't *just* been set as inactive
  6483. (!typeState.isActive && activeDelta === null) ||
  6484. // If we didn't and don't have any defined prop for this animation type
  6485. (!prop && !typeState.prevProp) ||
  6486. // Or if the prop doesn't define an animation
  6487. isAnimationControls(prop) ||
  6488. typeof prop === "boolean") {
  6489. continue;
  6490. }
  6491. /**
  6492. * As we go look through the values defined on this type, if we detect
  6493. * a changed value or a value that was removed in a higher priority, we set
  6494. * this to true and add this prop to the animation list.
  6495. */
  6496. const variantDidChange = checkVariantsDidChange(typeState.prevProp, prop);
  6497. let shouldAnimateType = variantDidChange ||
  6498. // If we're making this variant active, we want to always make it active
  6499. (type === changedActiveType &&
  6500. typeState.isActive &&
  6501. !isInherited &&
  6502. propIsVariant) ||
  6503. // If we removed a higher-priority variant (i is in reverse order)
  6504. (i > removedVariantIndex && propIsVariant);
  6505. let handledRemovedValues = false;
  6506. /**
  6507. * As animations can be set as variant lists, variants or target objects, we
  6508. * coerce everything to an array if it isn't one already
  6509. */
  6510. const definitionList = Array.isArray(prop) ? prop : [prop];
  6511. /**
  6512. * Build an object of all the resolved values. We'll use this in the subsequent
  6513. * animateChanges calls to determine whether a value has changed.
  6514. */
  6515. let resolvedValues = definitionList.reduce(buildResolvedTypeValues(type), {});
  6516. if (activeDelta === false)
  6517. resolvedValues = {};
  6518. /**
  6519. * Now we need to loop through all the keys in the prev prop and this prop,
  6520. * and decide:
  6521. * 1. If the value has changed, and needs animating
  6522. * 2. If it has been removed, and needs adding to the removedKeys set
  6523. * 3. If it has been removed in a higher priority type and needs animating
  6524. * 4. If it hasn't been removed in a higher priority but hasn't changed, and
  6525. * needs adding to the type's protectedKeys list.
  6526. */
  6527. const { prevResolvedValues = {} } = typeState;
  6528. const allKeys = {
  6529. ...prevResolvedValues,
  6530. ...resolvedValues,
  6531. };
  6532. const markToAnimate = (key) => {
  6533. shouldAnimateType = true;
  6534. if (removedKeys.has(key)) {
  6535. handledRemovedValues = true;
  6536. removedKeys.delete(key);
  6537. }
  6538. typeState.needsAnimating[key] = true;
  6539. const motionValue = visualElement.getValue(key);
  6540. if (motionValue)
  6541. motionValue.liveStyle = false;
  6542. };
  6543. for (const key in allKeys) {
  6544. const next = resolvedValues[key];
  6545. const prev = prevResolvedValues[key];
  6546. // If we've already handled this we can just skip ahead
  6547. if (encounteredKeys.hasOwnProperty(key))
  6548. continue;
  6549. /**
  6550. * If the value has changed, we probably want to animate it.
  6551. */
  6552. let valueHasChanged = false;
  6553. if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
  6554. valueHasChanged = !shallowCompare(next, prev);
  6555. }
  6556. else {
  6557. valueHasChanged = next !== prev;
  6558. }
  6559. if (valueHasChanged) {
  6560. if (next !== undefined && next !== null) {
  6561. // If next is defined and doesn't equal prev, it needs animating
  6562. markToAnimate(key);
  6563. }
  6564. else {
  6565. // If it's undefined, it's been removed.
  6566. removedKeys.add(key);
  6567. }
  6568. }
  6569. else if (next !== undefined && removedKeys.has(key)) {
  6570. /**
  6571. * If next hasn't changed and it isn't undefined, we want to check if it's
  6572. * been removed by a higher priority
  6573. */
  6574. markToAnimate(key);
  6575. }
  6576. else {
  6577. /**
  6578. * If it hasn't changed, we add it to the list of protected values
  6579. * to ensure it doesn't get animated.
  6580. */
  6581. typeState.protectedKeys[key] = true;
  6582. }
  6583. }
  6584. /**
  6585. * Update the typeState so next time animateChanges is called we can compare the
  6586. * latest prop and resolvedValues to these.
  6587. */
  6588. typeState.prevProp = prop;
  6589. typeState.prevResolvedValues = resolvedValues;
  6590. /**
  6591. *
  6592. */
  6593. if (typeState.isActive) {
  6594. encounteredKeys = { ...encounteredKeys, ...resolvedValues };
  6595. }
  6596. if (isInitialRender && visualElement.blockInitialAnimation) {
  6597. shouldAnimateType = false;
  6598. }
  6599. /**
  6600. * If this is an inherited prop we want to skip this animation
  6601. * unless the inherited variants haven't changed on this render.
  6602. */
  6603. const willAnimateViaParent = isInherited && variantDidChange;
  6604. const needsAnimating = !willAnimateViaParent || handledRemovedValues;
  6605. if (shouldAnimateType && needsAnimating) {
  6606. animations.push(...definitionList.map((animation) => ({
  6607. animation: animation,
  6608. options: { type },
  6609. })));
  6610. }
  6611. }
  6612. /**
  6613. * If there are some removed value that haven't been dealt with,
  6614. * we need to create a new animation that falls back either to the value
  6615. * defined in the style prop, or the last read value.
  6616. */
  6617. if (removedKeys.size) {
  6618. const fallbackAnimation = {};
  6619. /**
  6620. * If the initial prop contains a transition we can use that, otherwise
  6621. * allow the animation function to use the visual element's default.
  6622. */
  6623. if (typeof props.initial !== "boolean") {
  6624. const initialTransition = resolveVariant(visualElement, Array.isArray(props.initial)
  6625. ? props.initial[0]
  6626. : props.initial);
  6627. if (initialTransition && initialTransition.transition) {
  6628. fallbackAnimation.transition = initialTransition.transition;
  6629. }
  6630. }
  6631. removedKeys.forEach((key) => {
  6632. const fallbackTarget = visualElement.getBaseTarget(key);
  6633. const motionValue = visualElement.getValue(key);
  6634. if (motionValue)
  6635. motionValue.liveStyle = true;
  6636. // @ts-expect-error - @mattgperry to figure if we should do something here
  6637. fallbackAnimation[key] = fallbackTarget ?? null;
  6638. });
  6639. animations.push({ animation: fallbackAnimation });
  6640. }
  6641. let shouldAnimate = Boolean(animations.length);
  6642. if (isInitialRender &&
  6643. (props.initial === false || props.initial === props.animate) &&
  6644. !visualElement.manuallyAnimateOnMount) {
  6645. shouldAnimate = false;
  6646. }
  6647. isInitialRender = false;
  6648. return shouldAnimate ? animate(animations) : Promise.resolve();
  6649. }
  6650. /**
  6651. * Change whether a certain animation type is active.
  6652. */
  6653. function setActive(type, isActive) {
  6654. // If the active state hasn't changed, we can safely do nothing here
  6655. if (state[type].isActive === isActive)
  6656. return Promise.resolve();
  6657. // Propagate active change to children
  6658. visualElement.variantChildren?.forEach((child) => child.animationState?.setActive(type, isActive));
  6659. state[type].isActive = isActive;
  6660. const animations = animateChanges(type);
  6661. for (const key in state) {
  6662. state[key].protectedKeys = {};
  6663. }
  6664. return animations;
  6665. }
  6666. return {
  6667. animateChanges,
  6668. setActive,
  6669. setAnimateFunction,
  6670. getState: () => state,
  6671. reset: () => {
  6672. state = createState();
  6673. isInitialRender = true;
  6674. },
  6675. };
  6676. }
  6677. function checkVariantsDidChange(prev, next) {
  6678. if (typeof next === "string") {
  6679. return next !== prev;
  6680. }
  6681. else if (Array.isArray(next)) {
  6682. return !shallowCompare(next, prev);
  6683. }
  6684. return false;
  6685. }
  6686. function createTypeState(isActive = false) {
  6687. return {
  6688. isActive,
  6689. protectedKeys: {},
  6690. needsAnimating: {},
  6691. prevResolvedValues: {},
  6692. };
  6693. }
  6694. function createState() {
  6695. return {
  6696. animate: createTypeState(true),
  6697. whileInView: createTypeState(),
  6698. whileHover: createTypeState(),
  6699. whileTap: createTypeState(),
  6700. whileDrag: createTypeState(),
  6701. whileFocus: createTypeState(),
  6702. exit: createTypeState(),
  6703. };
  6704. }
  6705. class Feature {
  6706. constructor(node) {
  6707. this.isMounted = false;
  6708. this.node = node;
  6709. }
  6710. update() { }
  6711. }
  6712. class AnimationFeature extends Feature {
  6713. /**
  6714. * We dynamically generate the AnimationState manager as it contains a reference
  6715. * to the underlying animation library. We only want to load that if we load this,
  6716. * so people can optionally code split it out using the `m` component.
  6717. */
  6718. constructor(node) {
  6719. super(node);
  6720. node.animationState || (node.animationState = createAnimationState(node));
  6721. }
  6722. updateAnimationControlsSubscription() {
  6723. const { animate } = this.node.getProps();
  6724. if (isAnimationControls(animate)) {
  6725. this.unmountControls = animate.subscribe(this.node);
  6726. }
  6727. }
  6728. /**
  6729. * Subscribe any provided AnimationControls to the component's VisualElement
  6730. */
  6731. mount() {
  6732. this.updateAnimationControlsSubscription();
  6733. }
  6734. update() {
  6735. const { animate } = this.node.getProps();
  6736. const { animate: prevAnimate } = this.node.prevProps || {};
  6737. if (animate !== prevAnimate) {
  6738. this.updateAnimationControlsSubscription();
  6739. }
  6740. }
  6741. unmount() {
  6742. this.node.animationState.reset();
  6743. this.unmountControls?.();
  6744. }
  6745. }
  6746. let id = 0;
  6747. class ExitAnimationFeature extends Feature {
  6748. constructor() {
  6749. super(...arguments);
  6750. this.id = id++;
  6751. }
  6752. update() {
  6753. if (!this.node.presenceContext)
  6754. return;
  6755. const { isPresent, onExitComplete } = this.node.presenceContext;
  6756. const { isPresent: prevIsPresent } = this.node.prevPresenceContext || {};
  6757. if (!this.node.animationState || isPresent === prevIsPresent) {
  6758. return;
  6759. }
  6760. const exitAnimation = this.node.animationState.setActive("exit", !isPresent);
  6761. if (onExitComplete && !isPresent) {
  6762. exitAnimation.then(() => {
  6763. onExitComplete(this.id);
  6764. });
  6765. }
  6766. }
  6767. mount() {
  6768. const { register, onExitComplete } = this.node.presenceContext || {};
  6769. if (onExitComplete) {
  6770. onExitComplete(this.id);
  6771. }
  6772. if (register) {
  6773. this.unmount = register(this.id);
  6774. }
  6775. }
  6776. unmount() { }
  6777. }
  6778. const animations = {
  6779. animation: {
  6780. Feature: AnimationFeature,
  6781. },
  6782. exit: {
  6783. Feature: ExitAnimationFeature,
  6784. },
  6785. };
  6786. function extractEventInfo(event) {
  6787. return {
  6788. point: {
  6789. x: event.pageX,
  6790. y: event.pageY,
  6791. },
  6792. };
  6793. }
  6794. const addPointerInfo = (handler) => {
  6795. return (event) => motionDom.isPrimaryPointer(event) && handler(event, extractEventInfo(event));
  6796. };
  6797. function addPointerEvent(target, eventName, handler, options) {
  6798. return addDomEvent(target, eventName, addPointerInfo(handler), options);
  6799. }
  6800. // Fixes https://github.com/motiondivision/motion/issues/2270
  6801. const getContextWindow = ({ current }) => {
  6802. return current ? current.ownerDocument.defaultView : null;
  6803. };
  6804. function isRefObject(ref) {
  6805. return (ref &&
  6806. typeof ref === "object" &&
  6807. Object.prototype.hasOwnProperty.call(ref, "current"));
  6808. }
  6809. const distance = (a, b) => Math.abs(a - b);
  6810. function distance2D(a, b) {
  6811. // Multi-dimensional
  6812. const xDelta = distance(a.x, b.x);
  6813. const yDelta = distance(a.y, b.y);
  6814. return Math.sqrt(xDelta ** 2 + yDelta ** 2);
  6815. }
  6816. /**
  6817. * @internal
  6818. */
  6819. class PanSession {
  6820. constructor(event, handlers, { transformPagePoint, contextWindow, dragSnapToOrigin = false, } = {}) {
  6821. /**
  6822. * @internal
  6823. */
  6824. this.startEvent = null;
  6825. /**
  6826. * @internal
  6827. */
  6828. this.lastMoveEvent = null;
  6829. /**
  6830. * @internal
  6831. */
  6832. this.lastMoveEventInfo = null;
  6833. /**
  6834. * @internal
  6835. */
  6836. this.handlers = {};
  6837. /**
  6838. * @internal
  6839. */
  6840. this.contextWindow = window;
  6841. this.updatePoint = () => {
  6842. if (!(this.lastMoveEvent && this.lastMoveEventInfo))
  6843. return;
  6844. const info = getPanInfo(this.lastMoveEventInfo, this.history);
  6845. const isPanStarted = this.startEvent !== null;
  6846. // Only start panning if the offset is larger than 3 pixels. If we make it
  6847. // any larger than this we'll want to reset the pointer history
  6848. // on the first update to avoid visual snapping to the cursoe.
  6849. const isDistancePastThreshold = distance2D(info.offset, { x: 0, y: 0 }) >= 3;
  6850. if (!isPanStarted && !isDistancePastThreshold)
  6851. return;
  6852. const { point } = info;
  6853. const { timestamp } = motionDom.frameData;
  6854. this.history.push({ ...point, timestamp });
  6855. const { onStart, onMove } = this.handlers;
  6856. if (!isPanStarted) {
  6857. onStart && onStart(this.lastMoveEvent, info);
  6858. this.startEvent = this.lastMoveEvent;
  6859. }
  6860. onMove && onMove(this.lastMoveEvent, info);
  6861. };
  6862. this.handlePointerMove = (event, info) => {
  6863. this.lastMoveEvent = event;
  6864. this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
  6865. // Throttle mouse move event to once per frame
  6866. motionDom.frame.update(this.updatePoint, true);
  6867. };
  6868. this.handlePointerUp = (event, info) => {
  6869. this.end();
  6870. const { onEnd, onSessionEnd, resumeAnimation } = this.handlers;
  6871. if (this.dragSnapToOrigin)
  6872. resumeAnimation && resumeAnimation();
  6873. if (!(this.lastMoveEvent && this.lastMoveEventInfo))
  6874. return;
  6875. const panInfo = getPanInfo(event.type === "pointercancel"
  6876. ? this.lastMoveEventInfo
  6877. : transformPoint(info, this.transformPagePoint), this.history);
  6878. if (this.startEvent && onEnd) {
  6879. onEnd(event, panInfo);
  6880. }
  6881. onSessionEnd && onSessionEnd(event, panInfo);
  6882. };
  6883. // If we have more than one touch, don't start detecting this gesture
  6884. if (!motionDom.isPrimaryPointer(event))
  6885. return;
  6886. this.dragSnapToOrigin = dragSnapToOrigin;
  6887. this.handlers = handlers;
  6888. this.transformPagePoint = transformPagePoint;
  6889. this.contextWindow = contextWindow || window;
  6890. const info = extractEventInfo(event);
  6891. const initialInfo = transformPoint(info, this.transformPagePoint);
  6892. const { point } = initialInfo;
  6893. const { timestamp } = motionDom.frameData;
  6894. this.history = [{ ...point, timestamp }];
  6895. const { onSessionStart } = handlers;
  6896. onSessionStart &&
  6897. onSessionStart(event, getPanInfo(initialInfo, this.history));
  6898. this.removeListeners = pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp));
  6899. }
  6900. updateHandlers(handlers) {
  6901. this.handlers = handlers;
  6902. }
  6903. end() {
  6904. this.removeListeners && this.removeListeners();
  6905. motionDom.cancelFrame(this.updatePoint);
  6906. }
  6907. }
  6908. function transformPoint(info, transformPagePoint) {
  6909. return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
  6910. }
  6911. function subtractPoint(a, b) {
  6912. return { x: a.x - b.x, y: a.y - b.y };
  6913. }
  6914. function getPanInfo({ point }, history) {
  6915. return {
  6916. point,
  6917. delta: subtractPoint(point, lastDevicePoint(history)),
  6918. offset: subtractPoint(point, startDevicePoint(history)),
  6919. velocity: getVelocity(history, 0.1),
  6920. };
  6921. }
  6922. function startDevicePoint(history) {
  6923. return history[0];
  6924. }
  6925. function lastDevicePoint(history) {
  6926. return history[history.length - 1];
  6927. }
  6928. function getVelocity(history, timeDelta) {
  6929. if (history.length < 2) {
  6930. return { x: 0, y: 0 };
  6931. }
  6932. let i = history.length - 1;
  6933. let timestampedPoint = null;
  6934. const lastPoint = lastDevicePoint(history);
  6935. while (i >= 0) {
  6936. timestampedPoint = history[i];
  6937. if (lastPoint.timestamp - timestampedPoint.timestamp >
  6938. motionUtils.secondsToMilliseconds(timeDelta)) {
  6939. break;
  6940. }
  6941. i--;
  6942. }
  6943. if (!timestampedPoint) {
  6944. return { x: 0, y: 0 };
  6945. }
  6946. const time = motionUtils.millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
  6947. if (time === 0) {
  6948. return { x: 0, y: 0 };
  6949. }
  6950. const currentVelocity = {
  6951. x: (lastPoint.x - timestampedPoint.x) / time,
  6952. y: (lastPoint.y - timestampedPoint.y) / time,
  6953. };
  6954. if (currentVelocity.x === Infinity) {
  6955. currentVelocity.x = 0;
  6956. }
  6957. if (currentVelocity.y === Infinity) {
  6958. currentVelocity.y = 0;
  6959. }
  6960. return currentVelocity;
  6961. }
  6962. /**
  6963. * Apply constraints to a point. These constraints are both physical along an
  6964. * axis, and an elastic factor that determines how much to constrain the point
  6965. * by if it does lie outside the defined parameters.
  6966. */
  6967. function applyConstraints(point, { min, max }, elastic) {
  6968. if (min !== undefined && point < min) {
  6969. // If we have a min point defined, and this is outside of that, constrain
  6970. point = elastic
  6971. ? mixNumber$1(min, point, elastic.min)
  6972. : Math.max(point, min);
  6973. }
  6974. else if (max !== undefined && point > max) {
  6975. // If we have a max point defined, and this is outside of that, constrain
  6976. point = elastic
  6977. ? mixNumber$1(max, point, elastic.max)
  6978. : Math.min(point, max);
  6979. }
  6980. return point;
  6981. }
  6982. /**
  6983. * Calculate constraints in terms of the viewport when defined relatively to the
  6984. * measured axis. This is measured from the nearest edge, so a max constraint of 200
  6985. * on an axis with a max value of 300 would return a constraint of 500 - axis length
  6986. */
  6987. function calcRelativeAxisConstraints(axis, min, max) {
  6988. return {
  6989. min: min !== undefined ? axis.min + min : undefined,
  6990. max: max !== undefined
  6991. ? axis.max + max - (axis.max - axis.min)
  6992. : undefined,
  6993. };
  6994. }
  6995. /**
  6996. * Calculate constraints in terms of the viewport when
  6997. * defined relatively to the measured bounding box.
  6998. */
  6999. function calcRelativeConstraints(layoutBox, { top, left, bottom, right }) {
  7000. return {
  7001. x: calcRelativeAxisConstraints(layoutBox.x, left, right),
  7002. y: calcRelativeAxisConstraints(layoutBox.y, top, bottom),
  7003. };
  7004. }
  7005. /**
  7006. * Calculate viewport constraints when defined as another viewport-relative axis
  7007. */
  7008. function calcViewportAxisConstraints(layoutAxis, constraintsAxis) {
  7009. let min = constraintsAxis.min - layoutAxis.min;
  7010. let max = constraintsAxis.max - layoutAxis.max;
  7011. // If the constraints axis is actually smaller than the layout axis then we can
  7012. // flip the constraints
  7013. if (constraintsAxis.max - constraintsAxis.min <
  7014. layoutAxis.max - layoutAxis.min) {
  7015. [min, max] = [max, min];
  7016. }
  7017. return { min, max };
  7018. }
  7019. /**
  7020. * Calculate viewport constraints when defined as another viewport-relative box
  7021. */
  7022. function calcViewportConstraints(layoutBox, constraintsBox) {
  7023. return {
  7024. x: calcViewportAxisConstraints(layoutBox.x, constraintsBox.x),
  7025. y: calcViewportAxisConstraints(layoutBox.y, constraintsBox.y),
  7026. };
  7027. }
  7028. /**
  7029. * Calculate a transform origin relative to the source axis, between 0-1, that results
  7030. * in an asthetically pleasing scale/transform needed to project from source to target.
  7031. */
  7032. function calcOrigin$1(source, target) {
  7033. let origin = 0.5;
  7034. const sourceLength = calcLength(source);
  7035. const targetLength = calcLength(target);
  7036. if (targetLength > sourceLength) {
  7037. origin = motionUtils.progress(target.min, target.max - sourceLength, source.min);
  7038. }
  7039. else if (sourceLength > targetLength) {
  7040. origin = motionUtils.progress(source.min, source.max - targetLength, target.min);
  7041. }
  7042. return clamp(0, 1, origin);
  7043. }
  7044. /**
  7045. * Rebase the calculated viewport constraints relative to the layout.min point.
  7046. */
  7047. function rebaseAxisConstraints(layout, constraints) {
  7048. const relativeConstraints = {};
  7049. if (constraints.min !== undefined) {
  7050. relativeConstraints.min = constraints.min - layout.min;
  7051. }
  7052. if (constraints.max !== undefined) {
  7053. relativeConstraints.max = constraints.max - layout.min;
  7054. }
  7055. return relativeConstraints;
  7056. }
  7057. const defaultElastic = 0.35;
  7058. /**
  7059. * Accepts a dragElastic prop and returns resolved elastic values for each axis.
  7060. */
  7061. function resolveDragElastic(dragElastic = defaultElastic) {
  7062. if (dragElastic === false) {
  7063. dragElastic = 0;
  7064. }
  7065. else if (dragElastic === true) {
  7066. dragElastic = defaultElastic;
  7067. }
  7068. return {
  7069. x: resolveAxisElastic(dragElastic, "left", "right"),
  7070. y: resolveAxisElastic(dragElastic, "top", "bottom"),
  7071. };
  7072. }
  7073. function resolveAxisElastic(dragElastic, minLabel, maxLabel) {
  7074. return {
  7075. min: resolvePointElastic(dragElastic, minLabel),
  7076. max: resolvePointElastic(dragElastic, maxLabel),
  7077. };
  7078. }
  7079. function resolvePointElastic(dragElastic, label) {
  7080. return typeof dragElastic === "number"
  7081. ? dragElastic
  7082. : dragElastic[label] || 0;
  7083. }
  7084. const elementDragControls = new WeakMap();
  7085. /**
  7086. *
  7087. */
  7088. // let latestPointerEvent: PointerEvent
  7089. class VisualElementDragControls {
  7090. constructor(visualElement) {
  7091. this.openDragLock = null;
  7092. this.isDragging = false;
  7093. this.currentDirection = null;
  7094. this.originPoint = { x: 0, y: 0 };
  7095. /**
  7096. * The permitted boundaries of travel, in pixels.
  7097. */
  7098. this.constraints = false;
  7099. this.hasMutatedConstraints = false;
  7100. /**
  7101. * The per-axis resolved elastic values.
  7102. */
  7103. this.elastic = createBox();
  7104. this.visualElement = visualElement;
  7105. }
  7106. start(originEvent, { snapToCursor = false } = {}) {
  7107. /**
  7108. * Don't start dragging if this component is exiting
  7109. */
  7110. const { presenceContext } = this.visualElement;
  7111. if (presenceContext && presenceContext.isPresent === false)
  7112. return;
  7113. const onSessionStart = (event) => {
  7114. const { dragSnapToOrigin } = this.getProps();
  7115. // Stop or pause any animations on both axis values immediately. This allows the user to throw and catch
  7116. // the component.
  7117. dragSnapToOrigin ? this.pauseAnimation() : this.stopAnimation();
  7118. if (snapToCursor) {
  7119. this.snapToCursor(extractEventInfo(event).point);
  7120. }
  7121. };
  7122. const onStart = (event, info) => {
  7123. // Attempt to grab the global drag gesture lock - maybe make this part of PanSession
  7124. const { drag, dragPropagation, onDragStart } = this.getProps();
  7125. if (drag && !dragPropagation) {
  7126. if (this.openDragLock)
  7127. this.openDragLock();
  7128. this.openDragLock = motionDom.setDragLock(drag);
  7129. // If we don 't have the lock, don't start dragging
  7130. if (!this.openDragLock)
  7131. return;
  7132. }
  7133. this.isDragging = true;
  7134. this.currentDirection = null;
  7135. this.resolveConstraints();
  7136. if (this.visualElement.projection) {
  7137. this.visualElement.projection.isAnimationBlocked = true;
  7138. this.visualElement.projection.target = undefined;
  7139. }
  7140. /**
  7141. * Record gesture origin
  7142. */
  7143. eachAxis((axis) => {
  7144. let current = this.getAxisMotionValue(axis).get() || 0;
  7145. /**
  7146. * If the MotionValue is a percentage value convert to px
  7147. */
  7148. if (percent.test(current)) {
  7149. const { projection } = this.visualElement;
  7150. if (projection && projection.layout) {
  7151. const measuredAxis = projection.layout.layoutBox[axis];
  7152. if (measuredAxis) {
  7153. const length = calcLength(measuredAxis);
  7154. current = length * (parseFloat(current) / 100);
  7155. }
  7156. }
  7157. }
  7158. this.originPoint[axis] = current;
  7159. });
  7160. // Fire onDragStart event
  7161. if (onDragStart) {
  7162. motionDom.frame.postRender(() => onDragStart(event, info));
  7163. }
  7164. addValueToWillChange(this.visualElement, "transform");
  7165. const { animationState } = this.visualElement;
  7166. animationState && animationState.setActive("whileDrag", true);
  7167. };
  7168. const onMove = (event, info) => {
  7169. // latestPointerEvent = event
  7170. const { dragPropagation, dragDirectionLock, onDirectionLock, onDrag, } = this.getProps();
  7171. // If we didn't successfully receive the gesture lock, early return.
  7172. if (!dragPropagation && !this.openDragLock)
  7173. return;
  7174. const { offset } = info;
  7175. // Attempt to detect drag direction if directionLock is true
  7176. if (dragDirectionLock && this.currentDirection === null) {
  7177. this.currentDirection = getCurrentDirection(offset);
  7178. // If we've successfully set a direction, notify listener
  7179. if (this.currentDirection !== null) {
  7180. onDirectionLock && onDirectionLock(this.currentDirection);
  7181. }
  7182. return;
  7183. }
  7184. // Update each point with the latest position
  7185. this.updateAxis("x", info.point, offset);
  7186. this.updateAxis("y", info.point, offset);
  7187. /**
  7188. * Ideally we would leave the renderer to fire naturally at the end of
  7189. * this frame but if the element is about to change layout as the result
  7190. * of a re-render we want to ensure the browser can read the latest
  7191. * bounding box to ensure the pointer and element don't fall out of sync.
  7192. */
  7193. this.visualElement.render();
  7194. /**
  7195. * This must fire after the render call as it might trigger a state
  7196. * change which itself might trigger a layout update.
  7197. */
  7198. onDrag && onDrag(event, info);
  7199. };
  7200. const onSessionEnd = (event, info) => this.stop(event, info);
  7201. const resumeAnimation = () => eachAxis((axis) => this.getAnimationState(axis) === "paused" &&
  7202. this.getAxisMotionValue(axis).animation?.play());
  7203. const { dragSnapToOrigin } = this.getProps();
  7204. this.panSession = new PanSession(originEvent, {
  7205. onSessionStart,
  7206. onStart,
  7207. onMove,
  7208. onSessionEnd,
  7209. resumeAnimation,
  7210. }, {
  7211. transformPagePoint: this.visualElement.getTransformPagePoint(),
  7212. dragSnapToOrigin,
  7213. contextWindow: getContextWindow(this.visualElement),
  7214. });
  7215. }
  7216. stop(event, info) {
  7217. const isDragging = this.isDragging;
  7218. this.cancel();
  7219. if (!isDragging)
  7220. return;
  7221. const { velocity } = info;
  7222. this.startAnimation(velocity);
  7223. const { onDragEnd } = this.getProps();
  7224. if (onDragEnd) {
  7225. motionDom.frame.postRender(() => onDragEnd(event, info));
  7226. }
  7227. }
  7228. cancel() {
  7229. this.isDragging = false;
  7230. const { projection, animationState } = this.visualElement;
  7231. if (projection) {
  7232. projection.isAnimationBlocked = false;
  7233. }
  7234. this.panSession && this.panSession.end();
  7235. this.panSession = undefined;
  7236. const { dragPropagation } = this.getProps();
  7237. if (!dragPropagation && this.openDragLock) {
  7238. this.openDragLock();
  7239. this.openDragLock = null;
  7240. }
  7241. animationState && animationState.setActive("whileDrag", false);
  7242. }
  7243. updateAxis(axis, _point, offset) {
  7244. const { drag } = this.getProps();
  7245. // If we're not dragging this axis, do an early return.
  7246. if (!offset || !shouldDrag(axis, drag, this.currentDirection))
  7247. return;
  7248. const axisValue = this.getAxisMotionValue(axis);
  7249. let next = this.originPoint[axis] + offset[axis];
  7250. // Apply constraints
  7251. if (this.constraints && this.constraints[axis]) {
  7252. next = applyConstraints(next, this.constraints[axis], this.elastic[axis]);
  7253. }
  7254. axisValue.set(next);
  7255. }
  7256. resolveConstraints() {
  7257. const { dragConstraints, dragElastic } = this.getProps();
  7258. const layout = this.visualElement.projection &&
  7259. !this.visualElement.projection.layout
  7260. ? this.visualElement.projection.measure(false)
  7261. : this.visualElement.projection?.layout;
  7262. const prevConstraints = this.constraints;
  7263. if (dragConstraints && isRefObject(dragConstraints)) {
  7264. if (!this.constraints) {
  7265. this.constraints = this.resolveRefConstraints();
  7266. }
  7267. }
  7268. else {
  7269. if (dragConstraints && layout) {
  7270. this.constraints = calcRelativeConstraints(layout.layoutBox, dragConstraints);
  7271. }
  7272. else {
  7273. this.constraints = false;
  7274. }
  7275. }
  7276. this.elastic = resolveDragElastic(dragElastic);
  7277. /**
  7278. * If we're outputting to external MotionValues, we want to rebase the measured constraints
  7279. * from viewport-relative to component-relative.
  7280. */
  7281. if (prevConstraints !== this.constraints &&
  7282. layout &&
  7283. this.constraints &&
  7284. !this.hasMutatedConstraints) {
  7285. eachAxis((axis) => {
  7286. if (this.constraints !== false &&
  7287. this.getAxisMotionValue(axis)) {
  7288. this.constraints[axis] = rebaseAxisConstraints(layout.layoutBox[axis], this.constraints[axis]);
  7289. }
  7290. });
  7291. }
  7292. }
  7293. resolveRefConstraints() {
  7294. const { dragConstraints: constraints, onMeasureDragConstraints } = this.getProps();
  7295. if (!constraints || !isRefObject(constraints))
  7296. return false;
  7297. const constraintsElement = constraints.current;
  7298. motionUtils.invariant(constraintsElement !== null, "If `dragConstraints` is set as a React ref, that ref must be passed to another component's `ref` prop.");
  7299. const { projection } = this.visualElement;
  7300. // TODO
  7301. if (!projection || !projection.layout)
  7302. return false;
  7303. const constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
  7304. let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, constraintsBox);
  7305. /**
  7306. * If there's an onMeasureDragConstraints listener we call it and
  7307. * if different constraints are returned, set constraints to that
  7308. */
  7309. if (onMeasureDragConstraints) {
  7310. const userConstraints = onMeasureDragConstraints(convertBoxToBoundingBox(measuredConstraints));
  7311. this.hasMutatedConstraints = !!userConstraints;
  7312. if (userConstraints) {
  7313. measuredConstraints = convertBoundingBoxToBox(userConstraints);
  7314. }
  7315. }
  7316. return measuredConstraints;
  7317. }
  7318. startAnimation(velocity) {
  7319. const { drag, dragMomentum, dragElastic, dragTransition, dragSnapToOrigin, onDragTransitionEnd, } = this.getProps();
  7320. const constraints = this.constraints || {};
  7321. const momentumAnimations = eachAxis((axis) => {
  7322. if (!shouldDrag(axis, drag, this.currentDirection)) {
  7323. return;
  7324. }
  7325. let transition = (constraints && constraints[axis]) || {};
  7326. if (dragSnapToOrigin)
  7327. transition = { min: 0, max: 0 };
  7328. /**
  7329. * Overdamp the boundary spring if `dragElastic` is disabled. There's still a frame
  7330. * of spring animations so we should look into adding a disable spring option to `inertia`.
  7331. * We could do something here where we affect the `bounceStiffness` and `bounceDamping`
  7332. * using the value of `dragElastic`.
  7333. */
  7334. const bounceStiffness = dragElastic ? 200 : 1000000;
  7335. const bounceDamping = dragElastic ? 40 : 10000000;
  7336. const inertia = {
  7337. type: "inertia",
  7338. velocity: dragMomentum ? velocity[axis] : 0,
  7339. bounceStiffness,
  7340. bounceDamping,
  7341. timeConstant: 750,
  7342. restDelta: 1,
  7343. restSpeed: 10,
  7344. ...dragTransition,
  7345. ...transition,
  7346. };
  7347. // If we're not animating on an externally-provided `MotionValue` we can use the
  7348. // component's animation controls which will handle interactions with whileHover (etc),
  7349. // otherwise we just have to animate the `MotionValue` itself.
  7350. return this.startAxisValueAnimation(axis, inertia);
  7351. });
  7352. // Run all animations and then resolve the new drag constraints.
  7353. return Promise.all(momentumAnimations).then(onDragTransitionEnd);
  7354. }
  7355. startAxisValueAnimation(axis, transition) {
  7356. const axisValue = this.getAxisMotionValue(axis);
  7357. addValueToWillChange(this.visualElement, axis);
  7358. return axisValue.start(animateMotionValue(axis, axisValue, 0, transition, this.visualElement, false));
  7359. }
  7360. stopAnimation() {
  7361. eachAxis((axis) => this.getAxisMotionValue(axis).stop());
  7362. }
  7363. pauseAnimation() {
  7364. eachAxis((axis) => this.getAxisMotionValue(axis).animation?.pause());
  7365. }
  7366. getAnimationState(axis) {
  7367. return this.getAxisMotionValue(axis).animation?.state;
  7368. }
  7369. /**
  7370. * Drag works differently depending on which props are provided.
  7371. *
  7372. * - If _dragX and _dragY are provided, we output the gesture delta directly to those motion values.
  7373. * - Otherwise, we apply the delta to the x/y motion values.
  7374. */
  7375. getAxisMotionValue(axis) {
  7376. const dragKey = `_drag${axis.toUpperCase()}`;
  7377. const props = this.visualElement.getProps();
  7378. const externalMotionValue = props[dragKey];
  7379. return externalMotionValue
  7380. ? externalMotionValue
  7381. : this.visualElement.getValue(axis, (props.initial
  7382. ? props.initial[axis]
  7383. : undefined) || 0);
  7384. }
  7385. snapToCursor(point) {
  7386. eachAxis((axis) => {
  7387. const { drag } = this.getProps();
  7388. // If we're not dragging this axis, do an early return.
  7389. if (!shouldDrag(axis, drag, this.currentDirection))
  7390. return;
  7391. const { projection } = this.visualElement;
  7392. const axisValue = this.getAxisMotionValue(axis);
  7393. if (projection && projection.layout) {
  7394. const { min, max } = projection.layout.layoutBox[axis];
  7395. axisValue.set(point[axis] - mixNumber$1(min, max, 0.5));
  7396. }
  7397. });
  7398. }
  7399. /**
  7400. * When the viewport resizes we want to check if the measured constraints
  7401. * have changed and, if so, reposition the element within those new constraints
  7402. * relative to where it was before the resize.
  7403. */
  7404. scalePositionWithinConstraints() {
  7405. if (!this.visualElement.current)
  7406. return;
  7407. const { drag, dragConstraints } = this.getProps();
  7408. const { projection } = this.visualElement;
  7409. if (!isRefObject(dragConstraints) || !projection || !this.constraints)
  7410. return;
  7411. /**
  7412. * Stop current animations as there can be visual glitching if we try to do
  7413. * this mid-animation
  7414. */
  7415. this.stopAnimation();
  7416. /**
  7417. * Record the relative position of the dragged element relative to the
  7418. * constraints box and save as a progress value.
  7419. */
  7420. const boxProgress = { x: 0, y: 0 };
  7421. eachAxis((axis) => {
  7422. const axisValue = this.getAxisMotionValue(axis);
  7423. if (axisValue && this.constraints !== false) {
  7424. const latest = axisValue.get();
  7425. boxProgress[axis] = calcOrigin$1({ min: latest, max: latest }, this.constraints[axis]);
  7426. }
  7427. });
  7428. /**
  7429. * Update the layout of this element and resolve the latest drag constraints
  7430. */
  7431. const { transformTemplate } = this.visualElement.getProps();
  7432. this.visualElement.current.style.transform = transformTemplate
  7433. ? transformTemplate({}, "")
  7434. : "none";
  7435. projection.root && projection.root.updateScroll();
  7436. projection.updateLayout();
  7437. this.resolveConstraints();
  7438. /**
  7439. * For each axis, calculate the current progress of the layout axis
  7440. * within the new constraints.
  7441. */
  7442. eachAxis((axis) => {
  7443. if (!shouldDrag(axis, drag, null))
  7444. return;
  7445. /**
  7446. * Calculate a new transform based on the previous box progress
  7447. */
  7448. const axisValue = this.getAxisMotionValue(axis);
  7449. const { min, max } = this.constraints[axis];
  7450. axisValue.set(mixNumber$1(min, max, boxProgress[axis]));
  7451. });
  7452. }
  7453. addListeners() {
  7454. if (!this.visualElement.current)
  7455. return;
  7456. elementDragControls.set(this.visualElement, this);
  7457. const element = this.visualElement.current;
  7458. /**
  7459. * Attach a pointerdown event listener on this DOM element to initiate drag tracking.
  7460. */
  7461. const stopPointerListener = addPointerEvent(element, "pointerdown", (event) => {
  7462. const { drag, dragListener = true } = this.getProps();
  7463. drag && dragListener && this.start(event);
  7464. });
  7465. const measureDragConstraints = () => {
  7466. const { dragConstraints } = this.getProps();
  7467. if (isRefObject(dragConstraints) && dragConstraints.current) {
  7468. this.constraints = this.resolveRefConstraints();
  7469. }
  7470. };
  7471. const { projection } = this.visualElement;
  7472. const stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints);
  7473. if (projection && !projection.layout) {
  7474. projection.root && projection.root.updateScroll();
  7475. projection.updateLayout();
  7476. }
  7477. motionDom.frame.read(measureDragConstraints);
  7478. /**
  7479. * Attach a window resize listener to scale the draggable target within its defined
  7480. * constraints as the window resizes.
  7481. */
  7482. const stopResizeListener = addDomEvent(window, "resize", () => this.scalePositionWithinConstraints());
  7483. /**
  7484. * If the element's layout changes, calculate the delta and apply that to
  7485. * the drag gesture's origin point.
  7486. */
  7487. const stopLayoutUpdateListener = projection.addEventListener("didUpdate", (({ delta, hasLayoutChanged }) => {
  7488. if (this.isDragging && hasLayoutChanged) {
  7489. eachAxis((axis) => {
  7490. const motionValue = this.getAxisMotionValue(axis);
  7491. if (!motionValue)
  7492. return;
  7493. this.originPoint[axis] += delta[axis].translate;
  7494. motionValue.set(motionValue.get() + delta[axis].translate);
  7495. });
  7496. this.visualElement.render();
  7497. }
  7498. }));
  7499. return () => {
  7500. stopResizeListener();
  7501. stopPointerListener();
  7502. stopMeasureLayoutListener();
  7503. stopLayoutUpdateListener && stopLayoutUpdateListener();
  7504. };
  7505. }
  7506. getProps() {
  7507. const props = this.visualElement.getProps();
  7508. const { drag = false, dragDirectionLock = false, dragPropagation = false, dragConstraints = false, dragElastic = defaultElastic, dragMomentum = true, } = props;
  7509. return {
  7510. ...props,
  7511. drag,
  7512. dragDirectionLock,
  7513. dragPropagation,
  7514. dragConstraints,
  7515. dragElastic,
  7516. dragMomentum,
  7517. };
  7518. }
  7519. }
  7520. function shouldDrag(direction, drag, currentDirection) {
  7521. return ((drag === true || drag === direction) &&
  7522. (currentDirection === null || currentDirection === direction));
  7523. }
  7524. /**
  7525. * Based on an x/y offset determine the current drag direction. If both axis' offsets are lower
  7526. * than the provided threshold, return `null`.
  7527. *
  7528. * @param offset - The x/y offset from origin.
  7529. * @param lockThreshold - (Optional) - the minimum absolute offset before we can determine a drag direction.
  7530. */
  7531. function getCurrentDirection(offset, lockThreshold = 10) {
  7532. let direction = null;
  7533. if (Math.abs(offset.y) > lockThreshold) {
  7534. direction = "y";
  7535. }
  7536. else if (Math.abs(offset.x) > lockThreshold) {
  7537. direction = "x";
  7538. }
  7539. return direction;
  7540. }
  7541. class DragGesture extends Feature {
  7542. constructor(node) {
  7543. super(node);
  7544. this.removeGroupControls = motionUtils.noop;
  7545. this.removeListeners = motionUtils.noop;
  7546. this.controls = new VisualElementDragControls(node);
  7547. }
  7548. mount() {
  7549. // If we've been provided a DragControls for manual control over the drag gesture,
  7550. // subscribe this component to it on mount.
  7551. const { dragControls } = this.node.getProps();
  7552. if (dragControls) {
  7553. this.removeGroupControls = dragControls.subscribe(this.controls);
  7554. }
  7555. this.removeListeners = this.controls.addListeners() || motionUtils.noop;
  7556. }
  7557. unmount() {
  7558. this.removeGroupControls();
  7559. this.removeListeners();
  7560. }
  7561. }
  7562. const asyncHandler = (handler) => (event, info) => {
  7563. if (handler) {
  7564. motionDom.frame.postRender(() => handler(event, info));
  7565. }
  7566. };
  7567. class PanGesture extends Feature {
  7568. constructor() {
  7569. super(...arguments);
  7570. this.removePointerDownListener = motionUtils.noop;
  7571. }
  7572. onPointerDown(pointerDownEvent) {
  7573. this.session = new PanSession(pointerDownEvent, this.createPanHandlers(), {
  7574. transformPagePoint: this.node.getTransformPagePoint(),
  7575. contextWindow: getContextWindow(this.node),
  7576. });
  7577. }
  7578. createPanHandlers() {
  7579. const { onPanSessionStart, onPanStart, onPan, onPanEnd } = this.node.getProps();
  7580. return {
  7581. onSessionStart: asyncHandler(onPanSessionStart),
  7582. onStart: asyncHandler(onPanStart),
  7583. onMove: onPan,
  7584. onEnd: (event, info) => {
  7585. delete this.session;
  7586. if (onPanEnd) {
  7587. motionDom.frame.postRender(() => onPanEnd(event, info));
  7588. }
  7589. },
  7590. };
  7591. }
  7592. mount() {
  7593. this.removePointerDownListener = addPointerEvent(this.node.current, "pointerdown", (event) => this.onPointerDown(event));
  7594. }
  7595. update() {
  7596. this.session && this.session.updateHandlers(this.createPanHandlers());
  7597. }
  7598. unmount() {
  7599. this.removePointerDownListener();
  7600. this.session && this.session.end();
  7601. }
  7602. }
  7603. /**
  7604. * Internal, exported only for usage in Framer
  7605. */
  7606. const SwitchLayoutGroupContext = React.createContext({});
  7607. class MeasureLayoutWithContext extends React.Component {
  7608. /**
  7609. * This only mounts projection nodes for components that
  7610. * need measuring, we might want to do it for all components
  7611. * in order to incorporate transforms
  7612. */
  7613. componentDidMount() {
  7614. const { visualElement, layoutGroup, switchLayoutGroup, layoutId } = this.props;
  7615. const { projection } = visualElement;
  7616. addScaleCorrector(defaultScaleCorrectors);
  7617. if (projection) {
  7618. if (layoutGroup.group)
  7619. layoutGroup.group.add(projection);
  7620. if (switchLayoutGroup && switchLayoutGroup.register && layoutId) {
  7621. switchLayoutGroup.register(projection);
  7622. }
  7623. projection.root.didUpdate();
  7624. projection.addEventListener("animationComplete", () => {
  7625. this.safeToRemove();
  7626. });
  7627. projection.setOptions({
  7628. ...projection.options,
  7629. onExitComplete: () => this.safeToRemove(),
  7630. });
  7631. }
  7632. globalProjectionState.hasEverUpdated = true;
  7633. }
  7634. getSnapshotBeforeUpdate(prevProps) {
  7635. const { layoutDependency, visualElement, drag, isPresent } = this.props;
  7636. const projection = visualElement.projection;
  7637. if (!projection)
  7638. return null;
  7639. /**
  7640. * TODO: We use this data in relegate to determine whether to
  7641. * promote a previous element. There's no guarantee its presence data
  7642. * will have updated by this point - if a bug like this arises it will
  7643. * have to be that we markForRelegation and then find a new lead some other way,
  7644. * perhaps in didUpdate
  7645. */
  7646. projection.isPresent = isPresent;
  7647. if (drag ||
  7648. prevProps.layoutDependency !== layoutDependency ||
  7649. layoutDependency === undefined ||
  7650. prevProps.isPresent !== isPresent) {
  7651. projection.willUpdate();
  7652. }
  7653. else {
  7654. this.safeToRemove();
  7655. }
  7656. if (prevProps.isPresent !== isPresent) {
  7657. if (isPresent) {
  7658. projection.promote();
  7659. }
  7660. else if (!projection.relegate()) {
  7661. /**
  7662. * If there's another stack member taking over from this one,
  7663. * it's in charge of the exit animation and therefore should
  7664. * be in charge of the safe to remove. Otherwise we call it here.
  7665. */
  7666. motionDom.frame.postRender(() => {
  7667. const stack = projection.getStack();
  7668. if (!stack || !stack.members.length) {
  7669. this.safeToRemove();
  7670. }
  7671. });
  7672. }
  7673. }
  7674. return null;
  7675. }
  7676. componentDidUpdate() {
  7677. const { projection } = this.props.visualElement;
  7678. if (projection) {
  7679. projection.root.didUpdate();
  7680. motionDom.microtask.postRender(() => {
  7681. if (!projection.currentAnimation && projection.isLead()) {
  7682. this.safeToRemove();
  7683. }
  7684. });
  7685. }
  7686. }
  7687. componentWillUnmount() {
  7688. const { visualElement, layoutGroup, switchLayoutGroup: promoteContext, } = this.props;
  7689. const { projection } = visualElement;
  7690. if (projection) {
  7691. projection.scheduleCheckAfterUnmount();
  7692. if (layoutGroup && layoutGroup.group)
  7693. layoutGroup.group.remove(projection);
  7694. if (promoteContext && promoteContext.deregister)
  7695. promoteContext.deregister(projection);
  7696. }
  7697. }
  7698. safeToRemove() {
  7699. const { safeToRemove } = this.props;
  7700. safeToRemove && safeToRemove();
  7701. }
  7702. render() {
  7703. return null;
  7704. }
  7705. }
  7706. function MeasureLayout(props) {
  7707. const [isPresent, safeToRemove] = usePresence();
  7708. const layoutGroup = React.useContext(LayoutGroupContext);
  7709. return (jsxRuntime.jsx(MeasureLayoutWithContext, { ...props, layoutGroup: layoutGroup, switchLayoutGroup: React.useContext(SwitchLayoutGroupContext), isPresent: isPresent, safeToRemove: safeToRemove }));
  7710. }
  7711. const defaultScaleCorrectors = {
  7712. borderRadius: {
  7713. ...correctBorderRadius,
  7714. applyTo: [
  7715. "borderTopLeftRadius",
  7716. "borderTopRightRadius",
  7717. "borderBottomLeftRadius",
  7718. "borderBottomRightRadius",
  7719. ],
  7720. },
  7721. borderTopLeftRadius: correctBorderRadius,
  7722. borderTopRightRadius: correctBorderRadius,
  7723. borderBottomLeftRadius: correctBorderRadius,
  7724. borderBottomRightRadius: correctBorderRadius,
  7725. boxShadow: correctBoxShadow,
  7726. };
  7727. const drag = {
  7728. pan: {
  7729. Feature: PanGesture,
  7730. },
  7731. drag: {
  7732. Feature: DragGesture,
  7733. ProjectionNode: HTMLProjectionNode,
  7734. MeasureLayout,
  7735. },
  7736. };
  7737. function handleHoverEvent(node, event, lifecycle) {
  7738. const { props } = node;
  7739. if (node.animationState && props.whileHover) {
  7740. node.animationState.setActive("whileHover", lifecycle === "Start");
  7741. }
  7742. const eventName = ("onHover" + lifecycle);
  7743. const callback = props[eventName];
  7744. if (callback) {
  7745. motionDom.frame.postRender(() => callback(event, extractEventInfo(event)));
  7746. }
  7747. }
  7748. class HoverGesture extends Feature {
  7749. mount() {
  7750. const { current } = this.node;
  7751. if (!current)
  7752. return;
  7753. this.unmount = motionDom.hover(current, (_element, startEvent) => {
  7754. handleHoverEvent(this.node, startEvent, "Start");
  7755. return (endEvent) => handleHoverEvent(this.node, endEvent, "End");
  7756. });
  7757. }
  7758. unmount() { }
  7759. }
  7760. class FocusGesture extends Feature {
  7761. constructor() {
  7762. super(...arguments);
  7763. this.isActive = false;
  7764. }
  7765. onFocus() {
  7766. let isFocusVisible = false;
  7767. /**
  7768. * If this element doesn't match focus-visible then don't
  7769. * apply whileHover. But, if matches throws that focus-visible
  7770. * is not a valid selector then in that browser outline styles will be applied
  7771. * to the element by default and we want to match that behaviour with whileFocus.
  7772. */
  7773. try {
  7774. isFocusVisible = this.node.current.matches(":focus-visible");
  7775. }
  7776. catch (e) {
  7777. isFocusVisible = true;
  7778. }
  7779. if (!isFocusVisible || !this.node.animationState)
  7780. return;
  7781. this.node.animationState.setActive("whileFocus", true);
  7782. this.isActive = true;
  7783. }
  7784. onBlur() {
  7785. if (!this.isActive || !this.node.animationState)
  7786. return;
  7787. this.node.animationState.setActive("whileFocus", false);
  7788. this.isActive = false;
  7789. }
  7790. mount() {
  7791. this.unmount = pipe(addDomEvent(this.node.current, "focus", () => this.onFocus()), addDomEvent(this.node.current, "blur", () => this.onBlur()));
  7792. }
  7793. unmount() { }
  7794. }
  7795. function handlePressEvent(node, event, lifecycle) {
  7796. const { props } = node;
  7797. if (node.current instanceof HTMLButtonElement && node.current.disabled) {
  7798. return;
  7799. }
  7800. if (node.animationState && props.whileTap) {
  7801. node.animationState.setActive("whileTap", lifecycle === "Start");
  7802. }
  7803. const eventName = ("onTap" + (lifecycle === "End" ? "" : lifecycle));
  7804. const callback = props[eventName];
  7805. if (callback) {
  7806. motionDom.frame.postRender(() => callback(event, extractEventInfo(event)));
  7807. }
  7808. }
  7809. class PressGesture extends Feature {
  7810. mount() {
  7811. const { current } = this.node;
  7812. if (!current)
  7813. return;
  7814. this.unmount = motionDom.press(current, (_element, startEvent) => {
  7815. handlePressEvent(this.node, startEvent, "Start");
  7816. return (endEvent, { success }) => handlePressEvent(this.node, endEvent, success ? "End" : "Cancel");
  7817. }, { useGlobalTarget: this.node.props.globalTapTarget });
  7818. }
  7819. unmount() { }
  7820. }
  7821. /**
  7822. * Map an IntersectionHandler callback to an element. We only ever make one handler for one
  7823. * element, so even though these handlers might all be triggered by different
  7824. * observers, we can keep them in the same map.
  7825. */
  7826. const observerCallbacks = new WeakMap();
  7827. /**
  7828. * Multiple observers can be created for multiple element/document roots. Each with
  7829. * different settings. So here we store dictionaries of observers to each root,
  7830. * using serialised settings (threshold/margin) as lookup keys.
  7831. */
  7832. const observers = new WeakMap();
  7833. const fireObserverCallback = (entry) => {
  7834. const callback = observerCallbacks.get(entry.target);
  7835. callback && callback(entry);
  7836. };
  7837. const fireAllObserverCallbacks = (entries) => {
  7838. entries.forEach(fireObserverCallback);
  7839. };
  7840. function initIntersectionObserver({ root, ...options }) {
  7841. const lookupRoot = root || document;
  7842. /**
  7843. * If we don't have an observer lookup map for this root, create one.
  7844. */
  7845. if (!observers.has(lookupRoot)) {
  7846. observers.set(lookupRoot, {});
  7847. }
  7848. const rootObservers = observers.get(lookupRoot);
  7849. const key = JSON.stringify(options);
  7850. /**
  7851. * If we don't have an observer for this combination of root and settings,
  7852. * create one.
  7853. */
  7854. if (!rootObservers[key]) {
  7855. rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, { root, ...options });
  7856. }
  7857. return rootObservers[key];
  7858. }
  7859. function observeIntersection(element, options, callback) {
  7860. const rootInteresectionObserver = initIntersectionObserver(options);
  7861. observerCallbacks.set(element, callback);
  7862. rootInteresectionObserver.observe(element);
  7863. return () => {
  7864. observerCallbacks.delete(element);
  7865. rootInteresectionObserver.unobserve(element);
  7866. };
  7867. }
  7868. const thresholdNames = {
  7869. some: 0,
  7870. all: 1,
  7871. };
  7872. class InViewFeature extends Feature {
  7873. constructor() {
  7874. super(...arguments);
  7875. this.hasEnteredView = false;
  7876. this.isInView = false;
  7877. }
  7878. startObserver() {
  7879. this.unmount();
  7880. const { viewport = {} } = this.node.getProps();
  7881. const { root, margin: rootMargin, amount = "some", once } = viewport;
  7882. const options = {
  7883. root: root ? root.current : undefined,
  7884. rootMargin,
  7885. threshold: typeof amount === "number" ? amount : thresholdNames[amount],
  7886. };
  7887. const onIntersectionUpdate = (entry) => {
  7888. const { isIntersecting } = entry;
  7889. /**
  7890. * If there's been no change in the viewport state, early return.
  7891. */
  7892. if (this.isInView === isIntersecting)
  7893. return;
  7894. this.isInView = isIntersecting;
  7895. /**
  7896. * Handle hasEnteredView. If this is only meant to run once, and
  7897. * element isn't visible, early return. Otherwise set hasEnteredView to true.
  7898. */
  7899. if (once && !isIntersecting && this.hasEnteredView) {
  7900. return;
  7901. }
  7902. else if (isIntersecting) {
  7903. this.hasEnteredView = true;
  7904. }
  7905. if (this.node.animationState) {
  7906. this.node.animationState.setActive("whileInView", isIntersecting);
  7907. }
  7908. /**
  7909. * Use the latest committed props rather than the ones in scope
  7910. * when this observer is created
  7911. */
  7912. const { onViewportEnter, onViewportLeave } = this.node.getProps();
  7913. const callback = isIntersecting ? onViewportEnter : onViewportLeave;
  7914. callback && callback(entry);
  7915. };
  7916. return observeIntersection(this.node.current, options, onIntersectionUpdate);
  7917. }
  7918. mount() {
  7919. this.startObserver();
  7920. }
  7921. update() {
  7922. if (typeof IntersectionObserver === "undefined")
  7923. return;
  7924. const { props, prevProps } = this.node;
  7925. const hasOptionsChanged = ["amount", "margin", "root"].some(hasViewportOptionChanged(props, prevProps));
  7926. if (hasOptionsChanged) {
  7927. this.startObserver();
  7928. }
  7929. }
  7930. unmount() { }
  7931. }
  7932. function hasViewportOptionChanged({ viewport = {} }, { viewport: prevViewport = {} } = {}) {
  7933. return (name) => viewport[name] !== prevViewport[name];
  7934. }
  7935. const gestureAnimations = {
  7936. inView: {
  7937. Feature: InViewFeature,
  7938. },
  7939. tap: {
  7940. Feature: PressGesture,
  7941. },
  7942. focus: {
  7943. Feature: FocusGesture,
  7944. },
  7945. hover: {
  7946. Feature: HoverGesture,
  7947. },
  7948. };
  7949. const layout = {
  7950. layout: {
  7951. ProjectionNode: HTMLProjectionNode,
  7952. MeasureLayout,
  7953. },
  7954. };
  7955. const MotionContext = /* @__PURE__ */ React.createContext({});
  7956. function getCurrentTreeVariants(props, context) {
  7957. if (isControllingVariants(props)) {
  7958. const { initial, animate } = props;
  7959. return {
  7960. initial: initial === false || isVariantLabel(initial)
  7961. ? initial
  7962. : undefined,
  7963. animate: isVariantLabel(animate) ? animate : undefined,
  7964. };
  7965. }
  7966. return props.inherit !== false ? context : {};
  7967. }
  7968. function useCreateMotionContext(props) {
  7969. const { initial, animate } = getCurrentTreeVariants(props, React.useContext(MotionContext));
  7970. return React.useMemo(() => ({ initial, animate }), [variantLabelsAsDependency(initial), variantLabelsAsDependency(animate)]);
  7971. }
  7972. function variantLabelsAsDependency(prop) {
  7973. return Array.isArray(prop) ? prop.join(" ") : prop;
  7974. }
  7975. const motionComponentSymbol = Symbol.for("motionComponentSymbol");
  7976. /**
  7977. * Creates a ref function that, when called, hydrates the provided
  7978. * external ref and VisualElement.
  7979. */
  7980. function useMotionRef(visualState, visualElement, externalRef) {
  7981. return React.useCallback((instance) => {
  7982. if (instance) {
  7983. visualState.onMount && visualState.onMount(instance);
  7984. }
  7985. if (visualElement) {
  7986. if (instance) {
  7987. visualElement.mount(instance);
  7988. }
  7989. else {
  7990. visualElement.unmount();
  7991. }
  7992. }
  7993. if (externalRef) {
  7994. if (typeof externalRef === "function") {
  7995. externalRef(instance);
  7996. }
  7997. else if (isRefObject(externalRef)) {
  7998. externalRef.current = instance;
  7999. }
  8000. }
  8001. },
  8002. /**
  8003. * Only pass a new ref callback to React if we've received a visual element
  8004. * factory. Otherwise we'll be mounting/remounting every time externalRef
  8005. * or other dependencies change.
  8006. */
  8007. [visualElement]);
  8008. }
  8009. function useVisualElement(Component, visualState, props, createVisualElement, ProjectionNodeConstructor) {
  8010. const { visualElement: parent } = React.useContext(MotionContext);
  8011. const lazyContext = React.useContext(LazyContext);
  8012. const presenceContext = React.useContext(PresenceContext);
  8013. const reducedMotionConfig = React.useContext(MotionConfigContext).reducedMotion;
  8014. const visualElementRef = React.useRef(null);
  8015. /**
  8016. * If we haven't preloaded a renderer, check to see if we have one lazy-loaded
  8017. */
  8018. createVisualElement = createVisualElement || lazyContext.renderer;
  8019. if (!visualElementRef.current && createVisualElement) {
  8020. visualElementRef.current = createVisualElement(Component, {
  8021. visualState,
  8022. parent,
  8023. props,
  8024. presenceContext,
  8025. blockInitialAnimation: presenceContext
  8026. ? presenceContext.initial === false
  8027. : false,
  8028. reducedMotionConfig,
  8029. });
  8030. }
  8031. const visualElement = visualElementRef.current;
  8032. /**
  8033. * Load Motion gesture and animation features. These are rendered as renderless
  8034. * components so each feature can optionally make use of React lifecycle methods.
  8035. */
  8036. const initialLayoutGroupConfig = React.useContext(SwitchLayoutGroupContext);
  8037. if (visualElement &&
  8038. !visualElement.projection &&
  8039. ProjectionNodeConstructor &&
  8040. (visualElement.type === "html" || visualElement.type === "svg")) {
  8041. createProjectionNode(visualElementRef.current, props, ProjectionNodeConstructor, initialLayoutGroupConfig);
  8042. }
  8043. const isMounted = React.useRef(false);
  8044. React.useInsertionEffect(() => {
  8045. /**
  8046. * Check the component has already mounted before calling
  8047. * `update` unnecessarily. This ensures we skip the initial update.
  8048. */
  8049. if (visualElement && isMounted.current) {
  8050. visualElement.update(props, presenceContext);
  8051. }
  8052. });
  8053. /**
  8054. * Cache this value as we want to know whether HandoffAppearAnimations
  8055. * was present on initial render - it will be deleted after this.
  8056. */
  8057. const optimisedAppearId = props[optimizedAppearDataAttribute];
  8058. const wantsHandoff = React.useRef(Boolean(optimisedAppearId) &&
  8059. !window.MotionHandoffIsComplete?.(optimisedAppearId) &&
  8060. window.MotionHasOptimisedAnimation?.(optimisedAppearId));
  8061. useIsomorphicLayoutEffect(() => {
  8062. if (!visualElement)
  8063. return;
  8064. isMounted.current = true;
  8065. window.MotionIsMounted = true;
  8066. visualElement.updateFeatures();
  8067. motionDom.microtask.render(visualElement.render);
  8068. /**
  8069. * Ideally this function would always run in a useEffect.
  8070. *
  8071. * However, if we have optimised appear animations to handoff from,
  8072. * it needs to happen synchronously to ensure there's no flash of
  8073. * incorrect styles in the event of a hydration error.
  8074. *
  8075. * So if we detect a situtation where optimised appear animations
  8076. * are running, we use useLayoutEffect to trigger animations.
  8077. */
  8078. if (wantsHandoff.current && visualElement.animationState) {
  8079. visualElement.animationState.animateChanges();
  8080. }
  8081. });
  8082. React.useEffect(() => {
  8083. if (!visualElement)
  8084. return;
  8085. if (!wantsHandoff.current && visualElement.animationState) {
  8086. visualElement.animationState.animateChanges();
  8087. }
  8088. if (wantsHandoff.current) {
  8089. // This ensures all future calls to animateChanges() in this component will run in useEffect
  8090. queueMicrotask(() => {
  8091. window.MotionHandoffMarkAsComplete?.(optimisedAppearId);
  8092. });
  8093. wantsHandoff.current = false;
  8094. }
  8095. });
  8096. return visualElement;
  8097. }
  8098. function createProjectionNode(visualElement, props, ProjectionNodeConstructor, initialPromotionConfig) {
  8099. const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, layoutCrossfade, } = props;
  8100. visualElement.projection = new ProjectionNodeConstructor(visualElement.latestValues, props["data-framer-portal-id"]
  8101. ? undefined
  8102. : getClosestProjectingNode(visualElement.parent));
  8103. visualElement.projection.setOptions({
  8104. layoutId,
  8105. layout,
  8106. alwaysMeasureLayout: Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),
  8107. visualElement,
  8108. /**
  8109. * TODO: Update options in an effect. This could be tricky as it'll be too late
  8110. * to update by the time layout animations run.
  8111. * We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
  8112. * ensuring it gets called if there's no potential layout animations.
  8113. *
  8114. */
  8115. animationType: typeof layout === "string" ? layout : "both",
  8116. initialPromotionConfig,
  8117. crossfade: layoutCrossfade,
  8118. layoutScroll,
  8119. layoutRoot,
  8120. });
  8121. }
  8122. function getClosestProjectingNode(visualElement) {
  8123. if (!visualElement)
  8124. return undefined;
  8125. return visualElement.options.allowProjection !== false
  8126. ? visualElement.projection
  8127. : getClosestProjectingNode(visualElement.parent);
  8128. }
  8129. /**
  8130. * Create a `motion` component.
  8131. *
  8132. * This function accepts a Component argument, which can be either a string (ie "div"
  8133. * for `motion.div`), or an actual React component.
  8134. *
  8135. * Alongside this is a config option which provides a way of rendering the provided
  8136. * component "offline", or outside the React render cycle.
  8137. */
  8138. function createRendererMotionComponent({ preloadedFeatures, createVisualElement, useRender, useVisualState, Component, }) {
  8139. preloadedFeatures && loadFeatures(preloadedFeatures);
  8140. function MotionComponent(props, externalRef) {
  8141. /**
  8142. * If we need to measure the element we load this functionality in a
  8143. * separate class component in order to gain access to getSnapshotBeforeUpdate.
  8144. */
  8145. let MeasureLayout;
  8146. const configAndProps = {
  8147. ...React.useContext(MotionConfigContext),
  8148. ...props,
  8149. layoutId: useLayoutId(props),
  8150. };
  8151. const { isStatic } = configAndProps;
  8152. const context = useCreateMotionContext(props);
  8153. const visualState = useVisualState(props, isStatic);
  8154. if (!isStatic && isBrowser) {
  8155. useStrictMode(configAndProps, preloadedFeatures);
  8156. const layoutProjection = getProjectionFunctionality(configAndProps);
  8157. MeasureLayout = layoutProjection.MeasureLayout;
  8158. /**
  8159. * Create a VisualElement for this component. A VisualElement provides a common
  8160. * interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
  8161. * providing a way of rendering to these APIs outside of the React render loop
  8162. * for more performant animations and interactions
  8163. */
  8164. context.visualElement = useVisualElement(Component, visualState, configAndProps, createVisualElement, layoutProjection.ProjectionNode);
  8165. }
  8166. /**
  8167. * The mount order and hierarchy is specific to ensure our element ref
  8168. * is hydrated by the time features fire their effects.
  8169. */
  8170. return (jsxRuntime.jsxs(MotionContext.Provider, { value: context, children: [MeasureLayout && context.visualElement ? (jsxRuntime.jsx(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null, useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, context.visualElement)] }));
  8171. }
  8172. MotionComponent.displayName = `motion.${typeof Component === "string"
  8173. ? Component
  8174. : `create(${Component.displayName ?? Component.name ?? ""})`}`;
  8175. const ForwardRefMotionComponent = React.forwardRef(MotionComponent);
  8176. ForwardRefMotionComponent[motionComponentSymbol] = Component;
  8177. return ForwardRefMotionComponent;
  8178. }
  8179. function useLayoutId({ layoutId }) {
  8180. const layoutGroupId = React.useContext(LayoutGroupContext).id;
  8181. return layoutGroupId && layoutId !== undefined
  8182. ? layoutGroupId + "-" + layoutId
  8183. : layoutId;
  8184. }
  8185. function useStrictMode(configAndProps, preloadedFeatures) {
  8186. const isStrict = React.useContext(LazyContext).strict;
  8187. /**
  8188. * If we're in development mode, check to make sure we're not rendering a motion component
  8189. * as a child of LazyMotion, as this will break the file-size benefits of using it.
  8190. */
  8191. if (process.env.NODE_ENV !== "production" &&
  8192. preloadedFeatures &&
  8193. isStrict) {
  8194. const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.";
  8195. configAndProps.ignoreStrict
  8196. ? motionUtils.warning(false, strictMessage)
  8197. : motionUtils.invariant(false, strictMessage);
  8198. }
  8199. }
  8200. function getProjectionFunctionality(props) {
  8201. const { drag, layout } = featureDefinitions;
  8202. if (!drag && !layout)
  8203. return {};
  8204. const combined = { ...drag, ...layout };
  8205. return {
  8206. MeasureLayout: drag?.isEnabled(props) || layout?.isEnabled(props)
  8207. ? combined.MeasureLayout
  8208. : undefined,
  8209. ProjectionNode: combined.ProjectionNode,
  8210. };
  8211. }
  8212. const createHtmlRenderState = () => ({
  8213. style: {},
  8214. transform: {},
  8215. transformOrigin: {},
  8216. vars: {},
  8217. });
  8218. function copyRawValuesOnly(target, source, props) {
  8219. for (const key in source) {
  8220. if (!isMotionValue(source[key]) && !isForcedMotionValue(key, props)) {
  8221. target[key] = source[key];
  8222. }
  8223. }
  8224. }
  8225. function useInitialMotionValues({ transformTemplate }, visualState) {
  8226. return React.useMemo(() => {
  8227. const state = createHtmlRenderState();
  8228. buildHTMLStyles(state, visualState, transformTemplate);
  8229. return Object.assign({}, state.vars, state.style);
  8230. }, [visualState]);
  8231. }
  8232. function useStyle(props, visualState) {
  8233. const styleProp = props.style || {};
  8234. const style = {};
  8235. /**
  8236. * Copy non-Motion Values straight into style
  8237. */
  8238. copyRawValuesOnly(style, styleProp, props);
  8239. Object.assign(style, useInitialMotionValues(props, visualState));
  8240. return style;
  8241. }
  8242. function useHTMLProps(props, visualState) {
  8243. // The `any` isn't ideal but it is the type of createElement props argument
  8244. const htmlProps = {};
  8245. const style = useStyle(props, visualState);
  8246. if (props.drag && props.dragListener !== false) {
  8247. // Disable the ghost element when a user drags
  8248. htmlProps.draggable = false;
  8249. // Disable text selection
  8250. style.userSelect =
  8251. style.WebkitUserSelect =
  8252. style.WebkitTouchCallout =
  8253. "none";
  8254. // Disable scrolling on the draggable direction
  8255. style.touchAction =
  8256. props.drag === true
  8257. ? "none"
  8258. : `pan-${props.drag === "x" ? "y" : "x"}`;
  8259. }
  8260. if (props.tabIndex === undefined &&
  8261. (props.onTap || props.onTapStart || props.whileTap)) {
  8262. htmlProps.tabIndex = 0;
  8263. }
  8264. htmlProps.style = style;
  8265. return htmlProps;
  8266. }
  8267. /**
  8268. * We keep these listed separately as we use the lowercase tag names as part
  8269. * of the runtime bundle to detect SVG components
  8270. */
  8271. const lowercaseSVGElements = [
  8272. "animate",
  8273. "circle",
  8274. "defs",
  8275. "desc",
  8276. "ellipse",
  8277. "g",
  8278. "image",
  8279. "line",
  8280. "filter",
  8281. "marker",
  8282. "mask",
  8283. "metadata",
  8284. "path",
  8285. "pattern",
  8286. "polygon",
  8287. "polyline",
  8288. "rect",
  8289. "stop",
  8290. "switch",
  8291. "symbol",
  8292. "svg",
  8293. "text",
  8294. "tspan",
  8295. "use",
  8296. "view",
  8297. ];
  8298. function isSVGComponent(Component) {
  8299. if (
  8300. /**
  8301. * If it's not a string, it's a custom React component. Currently we only support
  8302. * HTML custom React components.
  8303. */
  8304. typeof Component !== "string" ||
  8305. /**
  8306. * If it contains a dash, the element is a custom HTML webcomponent.
  8307. */
  8308. Component.includes("-")) {
  8309. return false;
  8310. }
  8311. else if (
  8312. /**
  8313. * If it's in our list of lowercase SVG tags, it's an SVG component
  8314. */
  8315. lowercaseSVGElements.indexOf(Component) > -1 ||
  8316. /**
  8317. * If it contains a capital letter, it's an SVG component
  8318. */
  8319. /[A-Z]/u.test(Component)) {
  8320. return true;
  8321. }
  8322. return false;
  8323. }
  8324. const dashKeys = {
  8325. offset: "stroke-dashoffset",
  8326. array: "stroke-dasharray",
  8327. };
  8328. const camelKeys = {
  8329. offset: "strokeDashoffset",
  8330. array: "strokeDasharray",
  8331. };
  8332. /**
  8333. * Build SVG path properties. Uses the path's measured length to convert
  8334. * our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
  8335. * and stroke-dasharray attributes.
  8336. *
  8337. * This function is mutative to reduce per-frame GC.
  8338. */
  8339. function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
  8340. // Normalise path length by setting SVG attribute pathLength to 1
  8341. attrs.pathLength = 1;
  8342. // We use dash case when setting attributes directly to the DOM node and camel case
  8343. // when defining props on a React component.
  8344. const keys = useDashCase ? dashKeys : camelKeys;
  8345. // Build the dash offset
  8346. attrs[keys.offset] = px.transform(-offset);
  8347. // Build the dash array
  8348. const pathLength = px.transform(length);
  8349. const pathSpacing = px.transform(spacing);
  8350. attrs[keys.array] = `${pathLength} ${pathSpacing}`;
  8351. }
  8352. function calcOrigin(origin, offset, size) {
  8353. return typeof origin === "string"
  8354. ? origin
  8355. : px.transform(offset + size * origin);
  8356. }
  8357. /**
  8358. * The SVG transform origin defaults are different to CSS and is less intuitive,
  8359. * so we use the measured dimensions of the SVG to reconcile these.
  8360. */
  8361. function calcSVGTransformOrigin(dimensions, originX, originY) {
  8362. const pxOriginX = calcOrigin(originX, dimensions.x, dimensions.width);
  8363. const pxOriginY = calcOrigin(originY, dimensions.y, dimensions.height);
  8364. return `${pxOriginX} ${pxOriginY}`;
  8365. }
  8366. /**
  8367. * Build SVG visual attrbutes, like cx and style.transform
  8368. */
  8369. function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0,
  8370. // This is object creation, which we try to avoid per-frame.
  8371. ...latest }, isSVGTag, transformTemplate) {
  8372. buildHTMLStyles(state, latest, transformTemplate);
  8373. /**
  8374. * For svg tags we just want to make sure viewBox is animatable and treat all the styles
  8375. * as normal HTML tags.
  8376. */
  8377. if (isSVGTag) {
  8378. if (state.style.viewBox) {
  8379. state.attrs.viewBox = state.style.viewBox;
  8380. }
  8381. return;
  8382. }
  8383. state.attrs = state.style;
  8384. state.style = {};
  8385. const { attrs, style, dimensions } = state;
  8386. /**
  8387. * However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
  8388. * and copy it into style.
  8389. */
  8390. if (attrs.transform) {
  8391. if (dimensions)
  8392. style.transform = attrs.transform;
  8393. delete attrs.transform;
  8394. }
  8395. // Parse transformOrigin
  8396. if (dimensions &&
  8397. (originX !== undefined || originY !== undefined || style.transform)) {
  8398. style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
  8399. }
  8400. // Render attrX/attrY/attrScale as attributes
  8401. if (attrX !== undefined)
  8402. attrs.x = attrX;
  8403. if (attrY !== undefined)
  8404. attrs.y = attrY;
  8405. if (attrScale !== undefined)
  8406. attrs.scale = attrScale;
  8407. // Build SVG path if one has been defined
  8408. if (pathLength !== undefined) {
  8409. buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
  8410. }
  8411. }
  8412. const createSvgRenderState = () => ({
  8413. ...createHtmlRenderState(),
  8414. attrs: {},
  8415. });
  8416. const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
  8417. function useSVGProps(props, visualState, _isStatic, Component) {
  8418. const visualProps = React.useMemo(() => {
  8419. const state = createSvgRenderState();
  8420. buildSVGAttrs(state, visualState, isSVGTag(Component), props.transformTemplate);
  8421. return {
  8422. ...state.attrs,
  8423. style: { ...state.style },
  8424. };
  8425. }, [visualState]);
  8426. if (props.style) {
  8427. const rawStyles = {};
  8428. copyRawValuesOnly(rawStyles, props.style, props);
  8429. visualProps.style = { ...rawStyles, ...visualProps.style };
  8430. }
  8431. return visualProps;
  8432. }
  8433. function createUseRender(forwardMotionProps = false) {
  8434. const useRender = (Component, props, ref, { latestValues }, isStatic) => {
  8435. const useVisualProps = isSVGComponent(Component)
  8436. ? useSVGProps
  8437. : useHTMLProps;
  8438. const visualProps = useVisualProps(props, latestValues, isStatic, Component);
  8439. const filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps);
  8440. const elementProps = Component !== React.Fragment
  8441. ? { ...filteredProps, ...visualProps, ref }
  8442. : {};
  8443. /**
  8444. * If component has been handed a motion value as its child,
  8445. * memoise its initial value and render that. Subsequent updates
  8446. * will be handled by the onChange handler
  8447. */
  8448. const { children } = props;
  8449. const renderedChildren = React.useMemo(() => (isMotionValue(children) ? children.get() : children), [children]);
  8450. return React.createElement(Component, {
  8451. ...elementProps,
  8452. children: renderedChildren,
  8453. });
  8454. };
  8455. return useRender;
  8456. }
  8457. function makeState({ scrapeMotionValuesFromProps, createRenderState, onUpdate, }, props, context, presenceContext) {
  8458. const state = {
  8459. latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
  8460. renderState: createRenderState(),
  8461. };
  8462. if (onUpdate) {
  8463. /**
  8464. * onMount works without the VisualElement because it could be
  8465. * called before the VisualElement payload has been hydrated.
  8466. * (e.g. if someone is using m components <m.circle />)
  8467. */
  8468. state.onMount = (instance) => onUpdate({ props, current: instance, ...state });
  8469. state.onUpdate = (visualElement) => onUpdate(visualElement);
  8470. }
  8471. return state;
  8472. }
  8473. const makeUseVisualState = (config) => (props, isStatic) => {
  8474. const context = React.useContext(MotionContext);
  8475. const presenceContext = React.useContext(PresenceContext);
  8476. const make = () => makeState(config, props, context, presenceContext);
  8477. return isStatic ? make() : useConstant(make);
  8478. };
  8479. function makeLatestValues(props, context, presenceContext, scrapeMotionValues) {
  8480. const values = {};
  8481. const motionValues = scrapeMotionValues(props, {});
  8482. for (const key in motionValues) {
  8483. values[key] = resolveMotionValue(motionValues[key]);
  8484. }
  8485. let { initial, animate } = props;
  8486. const isControllingVariants$1 = isControllingVariants(props);
  8487. const isVariantNode$1 = isVariantNode(props);
  8488. if (context &&
  8489. isVariantNode$1 &&
  8490. !isControllingVariants$1 &&
  8491. props.inherit !== false) {
  8492. if (initial === undefined)
  8493. initial = context.initial;
  8494. if (animate === undefined)
  8495. animate = context.animate;
  8496. }
  8497. let isInitialAnimationBlocked = presenceContext
  8498. ? presenceContext.initial === false
  8499. : false;
  8500. isInitialAnimationBlocked = isInitialAnimationBlocked || initial === false;
  8501. const variantToSet = isInitialAnimationBlocked ? animate : initial;
  8502. if (variantToSet &&
  8503. typeof variantToSet !== "boolean" &&
  8504. !isAnimationControls(variantToSet)) {
  8505. const list = Array.isArray(variantToSet) ? variantToSet : [variantToSet];
  8506. for (let i = 0; i < list.length; i++) {
  8507. const resolved = resolveVariantFromProps(props, list[i]);
  8508. if (resolved) {
  8509. const { transitionEnd, transition, ...target } = resolved;
  8510. for (const key in target) {
  8511. let valueTarget = target[key];
  8512. if (Array.isArray(valueTarget)) {
  8513. /**
  8514. * Take final keyframe if the initial animation is blocked because
  8515. * we want to initialise at the end of that blocked animation.
  8516. */
  8517. const index = isInitialAnimationBlocked
  8518. ? valueTarget.length - 1
  8519. : 0;
  8520. valueTarget = valueTarget[index];
  8521. }
  8522. if (valueTarget !== null) {
  8523. values[key] = valueTarget;
  8524. }
  8525. }
  8526. for (const key in transitionEnd) {
  8527. values[key] = transitionEnd[key];
  8528. }
  8529. }
  8530. }
  8531. }
  8532. return values;
  8533. }
  8534. const htmlMotionConfig = {
  8535. useVisualState: makeUseVisualState({
  8536. scrapeMotionValuesFromProps: scrapeMotionValuesFromProps$1,
  8537. createRenderState: createHtmlRenderState,
  8538. }),
  8539. };
  8540. function updateSVGDimensions(instance, renderState) {
  8541. try {
  8542. renderState.dimensions =
  8543. typeof instance.getBBox === "function"
  8544. ? instance.getBBox()
  8545. : instance.getBoundingClientRect();
  8546. }
  8547. catch (e) {
  8548. // Most likely trying to measure an unrendered element under Firefox
  8549. renderState.dimensions = {
  8550. x: 0,
  8551. y: 0,
  8552. width: 0,
  8553. height: 0,
  8554. };
  8555. }
  8556. }
  8557. /**
  8558. * A set of attribute names that are always read/written as camel case.
  8559. */
  8560. const camelCaseAttributes = new Set([
  8561. "baseFrequency",
  8562. "diffuseConstant",
  8563. "kernelMatrix",
  8564. "kernelUnitLength",
  8565. "keySplines",
  8566. "keyTimes",
  8567. "limitingConeAngle",
  8568. "markerHeight",
  8569. "markerWidth",
  8570. "numOctaves",
  8571. "targetX",
  8572. "targetY",
  8573. "surfaceScale",
  8574. "specularConstant",
  8575. "specularExponent",
  8576. "stdDeviation",
  8577. "tableValues",
  8578. "viewBox",
  8579. "gradientTransform",
  8580. "pathLength",
  8581. "startOffset",
  8582. "textLength",
  8583. "lengthAdjust",
  8584. ]);
  8585. function renderSVG(element, renderState, _styleProp, projection) {
  8586. renderHTML(element, renderState, undefined, projection);
  8587. for (const key in renderState.attrs) {
  8588. element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
  8589. }
  8590. }
  8591. function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  8592. const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
  8593. for (const key in props) {
  8594. if (isMotionValue(props[key]) ||
  8595. isMotionValue(prevProps[key])) {
  8596. const targetKey = transformPropOrder.indexOf(key) !== -1
  8597. ? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
  8598. : key;
  8599. newValues[targetKey] = props[key];
  8600. }
  8601. }
  8602. return newValues;
  8603. }
  8604. const layoutProps = ["x", "y", "width", "height", "cx", "cy", "r"];
  8605. const svgMotionConfig = {
  8606. useVisualState: makeUseVisualState({
  8607. scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
  8608. createRenderState: createSvgRenderState,
  8609. onUpdate: ({ props, prevProps, current, renderState, latestValues, }) => {
  8610. if (!current)
  8611. return;
  8612. let hasTransform = !!props.drag;
  8613. if (!hasTransform) {
  8614. for (const key in latestValues) {
  8615. if (transformProps.has(key)) {
  8616. hasTransform = true;
  8617. break;
  8618. }
  8619. }
  8620. }
  8621. if (!hasTransform)
  8622. return;
  8623. let needsMeasure = !prevProps;
  8624. if (prevProps) {
  8625. /**
  8626. * Check the layout props for changes, if any are found we need to
  8627. * measure the element again.
  8628. */
  8629. for (let i = 0; i < layoutProps.length; i++) {
  8630. const key = layoutProps[i];
  8631. if (props[key] !==
  8632. prevProps[key]) {
  8633. needsMeasure = true;
  8634. }
  8635. }
  8636. }
  8637. if (!needsMeasure)
  8638. return;
  8639. motionDom.frame.read(() => {
  8640. updateSVGDimensions(current, renderState);
  8641. motionDom.frame.render(() => {
  8642. buildSVGAttrs(renderState, latestValues, isSVGTag(current.tagName), props.transformTemplate);
  8643. renderSVG(current, renderState);
  8644. });
  8645. });
  8646. },
  8647. }),
  8648. };
  8649. function createMotionComponentFactory(preloadedFeatures, createVisualElement) {
  8650. return function createMotionComponent(Component, { forwardMotionProps } = { forwardMotionProps: false }) {
  8651. const baseConfig = isSVGComponent(Component)
  8652. ? svgMotionConfig
  8653. : htmlMotionConfig;
  8654. const config = {
  8655. ...baseConfig,
  8656. preloadedFeatures,
  8657. useRender: createUseRender(forwardMotionProps),
  8658. createVisualElement,
  8659. Component,
  8660. };
  8661. return createRendererMotionComponent(config);
  8662. };
  8663. }
  8664. class SVGVisualElement extends DOMVisualElement {
  8665. constructor() {
  8666. super(...arguments);
  8667. this.type = "svg";
  8668. this.isSVGTag = false;
  8669. this.measureInstanceViewportBox = createBox;
  8670. this.updateDimensions = () => {
  8671. if (this.current && !this.renderState.dimensions) {
  8672. updateSVGDimensions(this.current, this.renderState);
  8673. }
  8674. };
  8675. }
  8676. getBaseTargetFromProps(props, key) {
  8677. return props[key];
  8678. }
  8679. readValueFromInstance(instance, key) {
  8680. if (transformProps.has(key)) {
  8681. const defaultType = getDefaultValueType(key);
  8682. return defaultType ? defaultType.default || 0 : 0;
  8683. }
  8684. key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
  8685. return instance.getAttribute(key);
  8686. }
  8687. scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  8688. return scrapeMotionValuesFromProps(props, prevProps, visualElement);
  8689. }
  8690. onBindTransform() {
  8691. if (this.current && !this.renderState.dimensions) {
  8692. motionDom.frame.postRender(this.updateDimensions);
  8693. }
  8694. }
  8695. build(renderState, latestValues, props) {
  8696. buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate);
  8697. }
  8698. renderInstance(instance, renderState, styleProp, projection) {
  8699. renderSVG(instance, renderState, styleProp, projection);
  8700. }
  8701. mount(instance) {
  8702. this.isSVGTag = isSVGTag(instance.tagName);
  8703. super.mount(instance);
  8704. }
  8705. }
  8706. const createDomVisualElement = (Component, options) => {
  8707. return isSVGComponent(Component)
  8708. ? new SVGVisualElement(options)
  8709. : new HTMLVisualElement(options, {
  8710. allowProjection: Component !== React.Fragment,
  8711. });
  8712. };
  8713. const createMotionComponent = /*@__PURE__*/ createMotionComponentFactory({
  8714. ...animations,
  8715. ...gestureAnimations,
  8716. ...drag,
  8717. ...layout,
  8718. }, createDomVisualElement);
  8719. exports.AcceleratedAnimation = AcceleratedAnimation;
  8720. exports.FlatTree = FlatTree;
  8721. exports.HTMLVisualElement = HTMLVisualElement;
  8722. exports.LayoutGroupContext = LayoutGroupContext;
  8723. exports.LazyContext = LazyContext;
  8724. exports.MotionConfigContext = MotionConfigContext;
  8725. exports.MotionContext = MotionContext;
  8726. exports.PresenceContext = PresenceContext;
  8727. exports.SVGVisualElement = SVGVisualElement;
  8728. exports.SwitchLayoutGroupContext = SwitchLayoutGroupContext;
  8729. exports.VisualElement = VisualElement;
  8730. exports.acceleratedValues = acceleratedValues;
  8731. exports.addDomEvent = addDomEvent;
  8732. exports.addPointerEvent = addPointerEvent;
  8733. exports.addPointerInfo = addPointerInfo;
  8734. exports.addScaleCorrector = addScaleCorrector;
  8735. exports.animateSingleValue = animateSingleValue;
  8736. exports.animateTarget = animateTarget;
  8737. exports.animateValue = animateValue;
  8738. exports.animateVisualElement = animateVisualElement;
  8739. exports.animations = animations;
  8740. exports.anticipate = anticipate;
  8741. exports.backIn = backIn;
  8742. exports.backInOut = backInOut;
  8743. exports.backOut = backOut;
  8744. exports.buildTransform = buildTransform;
  8745. exports.calcLength = calcLength;
  8746. exports.camelToDash = camelToDash;
  8747. exports.circIn = circIn;
  8748. exports.circInOut = circInOut;
  8749. exports.circOut = circOut;
  8750. exports.clamp = clamp;
  8751. exports.color = color;
  8752. exports.complex = complex;
  8753. exports.createBox = createBox;
  8754. exports.createDomVisualElement = createDomVisualElement;
  8755. exports.createMotionComponent = createMotionComponent;
  8756. exports.createMotionComponentFactory = createMotionComponentFactory;
  8757. exports.createRendererMotionComponent = createRendererMotionComponent;
  8758. exports.cubicBezier = cubicBezier;
  8759. exports.defaultOffset = defaultOffset;
  8760. exports.delay = delay;
  8761. exports.distance = distance;
  8762. exports.distance2D = distance2D;
  8763. exports.drag = drag;
  8764. exports.easeIn = easeIn;
  8765. exports.easeInOut = easeInOut;
  8766. exports.easeOut = easeOut;
  8767. exports.easingDefinitionToFunction = easingDefinitionToFunction;
  8768. exports.fillOffset = fillOffset;
  8769. exports.filterProps = filterProps;
  8770. exports.findSpring = findSpring;
  8771. exports.gestureAnimations = gestureAnimations;
  8772. exports.getOptimisedAppearId = getOptimisedAppearId;
  8773. exports.hasReducedMotionListener = hasReducedMotionListener;
  8774. exports.inertia = inertia;
  8775. exports.initPrefersReducedMotion = initPrefersReducedMotion;
  8776. exports.instantAnimationState = instantAnimationState;
  8777. exports.interpolate = interpolate;
  8778. exports.isBrowser = isBrowser;
  8779. exports.isEasingArray = isEasingArray;
  8780. exports.isMotionValue = isMotionValue;
  8781. exports.isSVGElement = isSVGElement;
  8782. exports.isValidMotionProp = isValidMotionProp;
  8783. exports.keyframes = keyframes;
  8784. exports.layout = layout;
  8785. exports.loadExternalIsValidProp = loadExternalIsValidProp;
  8786. exports.loadFeatures = loadFeatures;
  8787. exports.makeUseVisualState = makeUseVisualState;
  8788. exports.mirrorEasing = mirrorEasing;
  8789. exports.mix = mix;
  8790. exports.mixNumber = mixNumber$1;
  8791. exports.motionComponentSymbol = motionComponentSymbol;
  8792. exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
  8793. exports.optimizedAppearDataId = optimizedAppearDataId;
  8794. exports.pipe = pipe;
  8795. exports.prefersReducedMotion = prefersReducedMotion;
  8796. exports.px = px;
  8797. exports.resolveMotionValue = resolveMotionValue;
  8798. exports.reverseEasing = reverseEasing;
  8799. exports.rootProjectionNode = rootProjectionNode;
  8800. exports.setTarget = setTarget;
  8801. exports.spring = spring;
  8802. exports.transformProps = transformProps;
  8803. exports.useConstant = useConstant;
  8804. exports.useIsPresent = useIsPresent;
  8805. exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
  8806. exports.usePresence = usePresence;
  8807. exports.visualElementStore = visualElementStore;