dom.js 184 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var motionDom = require('motion-dom');
  4. var motionUtils = require('motion-utils');
  5. const clamp = (min, max, v) => {
  6. if (v > max)
  7. return max;
  8. if (v < min)
  9. return min;
  10. return v;
  11. };
  12. const velocitySampleDuration = 5; // ms
  13. function calcGeneratorVelocity(resolveValue, t, current) {
  14. const prevT = Math.max(t - velocitySampleDuration, 0);
  15. return motionUtils.velocityPerSecond(current - resolveValue(prevT), t - prevT);
  16. }
  17. const springDefaults = {
  18. // Default spring physics
  19. stiffness: 100,
  20. damping: 10,
  21. mass: 1.0,
  22. velocity: 0.0,
  23. // Default duration/bounce-based options
  24. duration: 800, // in ms
  25. bounce: 0.3,
  26. visualDuration: 0.3, // in seconds
  27. // Rest thresholds
  28. restSpeed: {
  29. granular: 0.01,
  30. default: 2,
  31. },
  32. restDelta: {
  33. granular: 0.005,
  34. default: 0.5,
  35. },
  36. // Limits
  37. minDuration: 0.01, // in seconds
  38. maxDuration: 10.0, // in seconds
  39. minDamping: 0.05,
  40. maxDamping: 1,
  41. };
  42. const safeMin = 0.001;
  43. function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
  44. let envelope;
  45. let derivative;
  46. motionUtils.warning(duration <= motionUtils.secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
  47. let dampingRatio = 1 - bounce;
  48. /**
  49. * Restrict dampingRatio and duration to within acceptable ranges.
  50. */
  51. dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
  52. duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, motionUtils.millisecondsToSeconds(duration));
  53. if (dampingRatio < 1) {
  54. /**
  55. * Underdamped spring
  56. */
  57. envelope = (undampedFreq) => {
  58. const exponentialDecay = undampedFreq * dampingRatio;
  59. const delta = exponentialDecay * duration;
  60. const a = exponentialDecay - velocity;
  61. const b = calcAngularFreq(undampedFreq, dampingRatio);
  62. const c = Math.exp(-delta);
  63. return safeMin - (a / b) * c;
  64. };
  65. derivative = (undampedFreq) => {
  66. const exponentialDecay = undampedFreq * dampingRatio;
  67. const delta = exponentialDecay * duration;
  68. const d = delta * velocity + velocity;
  69. const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
  70. const f = Math.exp(-delta);
  71. const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
  72. const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
  73. return (factor * ((d - e) * f)) / g;
  74. };
  75. }
  76. else {
  77. /**
  78. * Critically-damped spring
  79. */
  80. envelope = (undampedFreq) => {
  81. const a = Math.exp(-undampedFreq * duration);
  82. const b = (undampedFreq - velocity) * duration + 1;
  83. return -safeMin + a * b;
  84. };
  85. derivative = (undampedFreq) => {
  86. const a = Math.exp(-undampedFreq * duration);
  87. const b = (velocity - undampedFreq) * (duration * duration);
  88. return a * b;
  89. };
  90. }
  91. const initialGuess = 5 / duration;
  92. const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
  93. duration = motionUtils.secondsToMilliseconds(duration);
  94. if (isNaN(undampedFreq)) {
  95. return {
  96. stiffness: springDefaults.stiffness,
  97. damping: springDefaults.damping,
  98. duration,
  99. };
  100. }
  101. else {
  102. const stiffness = Math.pow(undampedFreq, 2) * mass;
  103. return {
  104. stiffness,
  105. damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
  106. duration,
  107. };
  108. }
  109. }
  110. const rootIterations = 12;
  111. function approximateRoot(envelope, derivative, initialGuess) {
  112. let result = initialGuess;
  113. for (let i = 1; i < rootIterations; i++) {
  114. result = result - envelope(result) / derivative(result);
  115. }
  116. return result;
  117. }
  118. function calcAngularFreq(undampedFreq, dampingRatio) {
  119. return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
  120. }
  121. const durationKeys = ["duration", "bounce"];
  122. const physicsKeys = ["stiffness", "damping", "mass"];
  123. function isSpringType(options, keys) {
  124. return keys.some((key) => options[key] !== undefined);
  125. }
  126. function getSpringOptions(options) {
  127. let springOptions = {
  128. velocity: springDefaults.velocity,
  129. stiffness: springDefaults.stiffness,
  130. damping: springDefaults.damping,
  131. mass: springDefaults.mass,
  132. isResolvedFromDuration: false,
  133. ...options,
  134. };
  135. // stiffness/damping/mass overrides duration/bounce
  136. if (!isSpringType(options, physicsKeys) &&
  137. isSpringType(options, durationKeys)) {
  138. if (options.visualDuration) {
  139. const visualDuration = options.visualDuration;
  140. const root = (2 * Math.PI) / (visualDuration * 1.2);
  141. const stiffness = root * root;
  142. const damping = 2 *
  143. clamp(0.05, 1, 1 - (options.bounce || 0)) *
  144. Math.sqrt(stiffness);
  145. springOptions = {
  146. ...springOptions,
  147. mass: springDefaults.mass,
  148. stiffness,
  149. damping,
  150. };
  151. }
  152. else {
  153. const derived = findSpring(options);
  154. springOptions = {
  155. ...springOptions,
  156. ...derived,
  157. mass: springDefaults.mass,
  158. };
  159. springOptions.isResolvedFromDuration = true;
  160. }
  161. }
  162. return springOptions;
  163. }
  164. function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
  165. const options = typeof optionsOrVisualDuration !== "object"
  166. ? {
  167. visualDuration: optionsOrVisualDuration,
  168. keyframes: [0, 1],
  169. bounce,
  170. }
  171. : optionsOrVisualDuration;
  172. let { restSpeed, restDelta } = options;
  173. const origin = options.keyframes[0];
  174. const target = options.keyframes[options.keyframes.length - 1];
  175. /**
  176. * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
  177. * to reduce GC during animation.
  178. */
  179. const state = { done: false, value: origin };
  180. const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
  181. ...options,
  182. velocity: -motionUtils.millisecondsToSeconds(options.velocity || 0),
  183. });
  184. const initialVelocity = velocity || 0.0;
  185. const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
  186. const initialDelta = target - origin;
  187. const undampedAngularFreq = motionUtils.millisecondsToSeconds(Math.sqrt(stiffness / mass));
  188. /**
  189. * If we're working on a granular scale, use smaller defaults for determining
  190. * when the spring is finished.
  191. *
  192. * These defaults have been selected emprically based on what strikes a good
  193. * ratio between feeling good and finishing as soon as changes are imperceptible.
  194. */
  195. const isGranularScale = Math.abs(initialDelta) < 5;
  196. restSpeed || (restSpeed = isGranularScale
  197. ? springDefaults.restSpeed.granular
  198. : springDefaults.restSpeed.default);
  199. restDelta || (restDelta = isGranularScale
  200. ? springDefaults.restDelta.granular
  201. : springDefaults.restDelta.default);
  202. let resolveSpring;
  203. if (dampingRatio < 1) {
  204. const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
  205. // Underdamped spring
  206. resolveSpring = (t) => {
  207. const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
  208. return (target -
  209. envelope *
  210. (((initialVelocity +
  211. dampingRatio * undampedAngularFreq * initialDelta) /
  212. angularFreq) *
  213. Math.sin(angularFreq * t) +
  214. initialDelta * Math.cos(angularFreq * t)));
  215. };
  216. }
  217. else if (dampingRatio === 1) {
  218. // Critically damped spring
  219. resolveSpring = (t) => target -
  220. Math.exp(-undampedAngularFreq * t) *
  221. (initialDelta +
  222. (initialVelocity + undampedAngularFreq * initialDelta) * t);
  223. }
  224. else {
  225. // Overdamped spring
  226. const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
  227. resolveSpring = (t) => {
  228. const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
  229. // When performing sinh or cosh values can hit Infinity so we cap them here
  230. const freqForT = Math.min(dampedAngularFreq * t, 300);
  231. return (target -
  232. (envelope *
  233. ((initialVelocity +
  234. dampingRatio * undampedAngularFreq * initialDelta) *
  235. Math.sinh(freqForT) +
  236. dampedAngularFreq *
  237. initialDelta *
  238. Math.cosh(freqForT))) /
  239. dampedAngularFreq);
  240. };
  241. }
  242. const generator = {
  243. calculatedDuration: isResolvedFromDuration ? duration || null : null,
  244. next: (t) => {
  245. const current = resolveSpring(t);
  246. if (!isResolvedFromDuration) {
  247. let currentVelocity = 0.0;
  248. /**
  249. * We only need to calculate velocity for under-damped springs
  250. * as over- and critically-damped springs can't overshoot, so
  251. * checking only for displacement is enough.
  252. */
  253. if (dampingRatio < 1) {
  254. currentVelocity =
  255. t === 0
  256. ? motionUtils.secondsToMilliseconds(initialVelocity)
  257. : calcGeneratorVelocity(resolveSpring, t, current);
  258. }
  259. const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
  260. const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
  261. state.done =
  262. isBelowVelocityThreshold && isBelowDisplacementThreshold;
  263. }
  264. else {
  265. state.done = t >= duration;
  266. }
  267. state.value = state.done ? target : current;
  268. return state;
  269. },
  270. toString: () => {
  271. const calculatedDuration = Math.min(motionDom.calcGeneratorDuration(generator), motionDom.maxGeneratorDuration);
  272. const easing = motionDom.generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
  273. return calculatedDuration + "ms " + easing;
  274. },
  275. toTransition: () => { },
  276. };
  277. return generator;
  278. }
  279. spring.applyToOptions = (options) => {
  280. const generatorOptions = motionDom.createGeneratorEasing(options, 100, spring);
  281. options.ease = motionDom.supportsLinearEasing() ? generatorOptions.ease : "easeOut";
  282. options.duration = motionUtils.secondsToMilliseconds(generatorOptions.duration);
  283. options.type = "keyframes";
  284. return options;
  285. };
  286. const wrap = (min, max, v) => {
  287. const rangeSize = max - min;
  288. return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
  289. };
  290. const isEasingArray = (ease) => {
  291. return Array.isArray(ease) && typeof ease[0] !== "number";
  292. };
  293. function getEasingForSegment(easing, i) {
  294. return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
  295. }
  296. /*
  297. Value in range from progress
  298. Given a lower limit and an upper limit, we return the value within
  299. that range as expressed by progress (usually a number from 0 to 1)
  300. So progress = 0.5 would change
  301. from -------- to
  302. to
  303. from ---- to
  304. E.g. from = 10, to = 20, progress = 0.5 => 15
  305. @param [number]: Lower limit of range
  306. @param [number]: Upper limit of range
  307. @param [number]: The progress between lower and upper limits expressed 0-1
  308. @return [number]: Value as calculated from progress within range (not limited within range)
  309. */
  310. const mixNumber$1 = (from, to, progress) => {
  311. return from + (to - from) * progress;
  312. };
  313. function fillOffset(offset, remaining) {
  314. const min = offset[offset.length - 1];
  315. for (let i = 1; i <= remaining; i++) {
  316. const offsetProgress = motionUtils.progress(0, remaining, i);
  317. offset.push(mixNumber$1(min, 1, offsetProgress));
  318. }
  319. }
  320. function defaultOffset$1(arr) {
  321. const offset = [0];
  322. fillOffset(offset, arr.length - 1);
  323. return offset;
  324. }
  325. const isMotionValue = (value) => Boolean(value && value.getVelocity);
  326. function isDOMKeyframes(keyframes) {
  327. return typeof keyframes === "object" && !Array.isArray(keyframes);
  328. }
  329. function resolveSubjects(subject, keyframes, scope, selectorCache) {
  330. if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
  331. return motionDom.resolveElements(subject, scope, selectorCache);
  332. }
  333. else if (subject instanceof NodeList) {
  334. return Array.from(subject);
  335. }
  336. else if (Array.isArray(subject)) {
  337. return subject;
  338. }
  339. else {
  340. return [subject];
  341. }
  342. }
  343. function calculateRepeatDuration(duration, repeat, _repeatDelay) {
  344. return duration * (repeat + 1);
  345. }
  346. /**
  347. * Given a absolute or relative time definition and current/prev time state of the sequence,
  348. * calculate an absolute time for the next keyframes.
  349. */
  350. function calcNextTime(current, next, prev, labels) {
  351. if (typeof next === "number") {
  352. return next;
  353. }
  354. else if (next.startsWith("-") || next.startsWith("+")) {
  355. return Math.max(0, current + parseFloat(next));
  356. }
  357. else if (next === "<") {
  358. return prev;
  359. }
  360. else {
  361. return labels.get(next) ?? current;
  362. }
  363. }
  364. function eraseKeyframes(sequence, startTime, endTime) {
  365. for (let i = 0; i < sequence.length; i++) {
  366. const keyframe = sequence[i];
  367. if (keyframe.at > startTime && keyframe.at < endTime) {
  368. motionUtils.removeItem(sequence, keyframe);
  369. // If we remove this item we have to push the pointer back one
  370. i--;
  371. }
  372. }
  373. }
  374. function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
  375. /**
  376. * Erase every existing value between currentTime and targetTime,
  377. * this will essentially splice this timeline into any currently
  378. * defined ones.
  379. */
  380. eraseKeyframes(sequence, startTime, endTime);
  381. for (let i = 0; i < keyframes.length; i++) {
  382. sequence.push({
  383. value: keyframes[i],
  384. at: mixNumber$1(startTime, endTime, offset[i]),
  385. easing: getEasingForSegment(easing, i),
  386. });
  387. }
  388. }
  389. /**
  390. * Take an array of times that represent repeated keyframes. For instance
  391. * if we have original times of [0, 0.5, 1] then our repeated times will
  392. * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
  393. * down to a 0-1 scale.
  394. */
  395. function normalizeTimes(times, repeat) {
  396. for (let i = 0; i < times.length; i++) {
  397. times[i] = times[i] / (repeat + 1);
  398. }
  399. }
  400. function compareByTime(a, b) {
  401. if (a.at === b.at) {
  402. if (a.value === null)
  403. return 1;
  404. if (b.value === null)
  405. return -1;
  406. return 0;
  407. }
  408. else {
  409. return a.at - b.at;
  410. }
  411. }
  412. const defaultSegmentEasing = "easeInOut";
  413. const MAX_REPEAT = 20;
  414. function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
  415. const defaultDuration = defaultTransition.duration || 0.3;
  416. const animationDefinitions = new Map();
  417. const sequences = new Map();
  418. const elementCache = {};
  419. const timeLabels = new Map();
  420. let prevTime = 0;
  421. let currentTime = 0;
  422. let totalDuration = 0;
  423. /**
  424. * Build the timeline by mapping over the sequence array and converting
  425. * the definitions into keyframes and offsets with absolute time values.
  426. * These will later get converted into relative offsets in a second pass.
  427. */
  428. for (let i = 0; i < sequence.length; i++) {
  429. const segment = sequence[i];
  430. /**
  431. * If this is a timeline label, mark it and skip the rest of this iteration.
  432. */
  433. if (typeof segment === "string") {
  434. timeLabels.set(segment, currentTime);
  435. continue;
  436. }
  437. else if (!Array.isArray(segment)) {
  438. timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
  439. continue;
  440. }
  441. let [subject, keyframes, transition = {}] = segment;
  442. /**
  443. * If a relative or absolute time value has been specified we need to resolve
  444. * it in relation to the currentTime.
  445. */
  446. if (transition.at !== undefined) {
  447. currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
  448. }
  449. /**
  450. * Keep track of the maximum duration in this definition. This will be
  451. * applied to currentTime once the definition has been parsed.
  452. */
  453. let maxDuration = 0;
  454. const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
  455. const valueKeyframesAsList = keyframesAsList(valueKeyframes);
  456. const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
  457. let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
  458. /**
  459. * Resolve stagger() if defined.
  460. */
  461. const calculatedDelay = typeof delay === "function"
  462. ? delay(elementIndex, numSubjects)
  463. : delay;
  464. /**
  465. * If this animation should and can use a spring, generate a spring easing function.
  466. */
  467. const numKeyframes = valueKeyframesAsList.length;
  468. const createGenerator = motionDom.isGenerator(type)
  469. ? type
  470. : generators?.[type];
  471. if (numKeyframes <= 2 && createGenerator) {
  472. /**
  473. * As we're creating an easing function from a spring,
  474. * ideally we want to generate it using the real distance
  475. * between the two keyframes. However this isn't always
  476. * possible - in these situations we use 0-100.
  477. */
  478. let absoluteDelta = 100;
  479. if (numKeyframes === 2 &&
  480. isNumberKeyframesArray(valueKeyframesAsList)) {
  481. const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
  482. absoluteDelta = Math.abs(delta);
  483. }
  484. const springTransition = { ...remainingTransition };
  485. if (duration !== undefined) {
  486. springTransition.duration = motionUtils.secondsToMilliseconds(duration);
  487. }
  488. const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
  489. ease = springEasing.ease;
  490. duration = springEasing.duration;
  491. }
  492. duration ?? (duration = defaultDuration);
  493. const startTime = currentTime + calculatedDelay;
  494. /**
  495. * If there's only one time offset of 0, fill in a second with length 1
  496. */
  497. if (times.length === 1 && times[0] === 0) {
  498. times[1] = 1;
  499. }
  500. /**
  501. * Fill out if offset if fewer offsets than keyframes
  502. */
  503. const remainder = times.length - valueKeyframesAsList.length;
  504. remainder > 0 && fillOffset(times, remainder);
  505. /**
  506. * If only one value has been set, ie [1], push a null to the start of
  507. * the keyframe array. This will let us mark a keyframe at this point
  508. * that will later be hydrated with the previous value.
  509. */
  510. valueKeyframesAsList.length === 1 &&
  511. valueKeyframesAsList.unshift(null);
  512. /**
  513. * Handle repeat options
  514. */
  515. if (repeat) {
  516. motionUtils.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20");
  517. duration = calculateRepeatDuration(duration, repeat);
  518. const originalKeyframes = [...valueKeyframesAsList];
  519. const originalTimes = [...times];
  520. ease = Array.isArray(ease) ? [...ease] : [ease];
  521. const originalEase = [...ease];
  522. for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
  523. valueKeyframesAsList.push(...originalKeyframes);
  524. for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
  525. times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
  526. ease.push(keyframeIndex === 0
  527. ? "linear"
  528. : getEasingForSegment(originalEase, keyframeIndex - 1));
  529. }
  530. }
  531. normalizeTimes(times, repeat);
  532. }
  533. const targetTime = startTime + duration;
  534. /**
  535. * Add keyframes, mapping offsets to absolute time.
  536. */
  537. addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
  538. maxDuration = Math.max(calculatedDelay + duration, maxDuration);
  539. totalDuration = Math.max(targetTime, totalDuration);
  540. };
  541. if (isMotionValue(subject)) {
  542. const subjectSequence = getSubjectSequence(subject, sequences);
  543. resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
  544. }
  545. else {
  546. const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
  547. const numSubjects = subjects.length;
  548. /**
  549. * For every element in this segment, process the defined values.
  550. */
  551. for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
  552. /**
  553. * Cast necessary, but we know these are of this type
  554. */
  555. keyframes = keyframes;
  556. transition = transition;
  557. const thisSubject = subjects[subjectIndex];
  558. const subjectSequence = getSubjectSequence(thisSubject, sequences);
  559. for (const key in keyframes) {
  560. resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
  561. }
  562. }
  563. }
  564. prevTime = currentTime;
  565. currentTime += maxDuration;
  566. }
  567. /**
  568. * For every element and value combination create a new animation.
  569. */
  570. sequences.forEach((valueSequences, element) => {
  571. for (const key in valueSequences) {
  572. const valueSequence = valueSequences[key];
  573. /**
  574. * Arrange all the keyframes in ascending time order.
  575. */
  576. valueSequence.sort(compareByTime);
  577. const keyframes = [];
  578. const valueOffset = [];
  579. const valueEasing = [];
  580. /**
  581. * For each keyframe, translate absolute times into
  582. * relative offsets based on the total duration of the timeline.
  583. */
  584. for (let i = 0; i < valueSequence.length; i++) {
  585. const { at, value, easing } = valueSequence[i];
  586. keyframes.push(value);
  587. valueOffset.push(motionUtils.progress(0, totalDuration, at));
  588. valueEasing.push(easing || "easeOut");
  589. }
  590. /**
  591. * If the first keyframe doesn't land on offset: 0
  592. * provide one by duplicating the initial keyframe. This ensures
  593. * it snaps to the first keyframe when the animation starts.
  594. */
  595. if (valueOffset[0] !== 0) {
  596. valueOffset.unshift(0);
  597. keyframes.unshift(keyframes[0]);
  598. valueEasing.unshift(defaultSegmentEasing);
  599. }
  600. /**
  601. * If the last keyframe doesn't land on offset: 1
  602. * provide one with a null wildcard value. This will ensure it
  603. * stays static until the end of the animation.
  604. */
  605. if (valueOffset[valueOffset.length - 1] !== 1) {
  606. valueOffset.push(1);
  607. keyframes.push(null);
  608. }
  609. if (!animationDefinitions.has(element)) {
  610. animationDefinitions.set(element, {
  611. keyframes: {},
  612. transition: {},
  613. });
  614. }
  615. const definition = animationDefinitions.get(element);
  616. definition.keyframes[key] = keyframes;
  617. definition.transition[key] = {
  618. ...defaultTransition,
  619. duration: totalDuration,
  620. ease: valueEasing,
  621. times: valueOffset,
  622. ...sequenceTransition,
  623. };
  624. }
  625. });
  626. return animationDefinitions;
  627. }
  628. function getSubjectSequence(subject, sequences) {
  629. !sequences.has(subject) && sequences.set(subject, {});
  630. return sequences.get(subject);
  631. }
  632. function getValueSequence(name, sequences) {
  633. if (!sequences[name])
  634. sequences[name] = [];
  635. return sequences[name];
  636. }
  637. function keyframesAsList(keyframes) {
  638. return Array.isArray(keyframes) ? keyframes : [keyframes];
  639. }
  640. function getValueTransition(transition, key) {
  641. return transition && transition[key]
  642. ? {
  643. ...transition,
  644. ...transition[key],
  645. }
  646. : { ...transition };
  647. }
  648. const isNumber = (keyframe) => typeof keyframe === "number";
  649. const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
  650. const visualElementStore = new WeakMap();
  651. /**
  652. * Generate a list of every possible transform key.
  653. */
  654. const transformPropOrder = [
  655. "transformPerspective",
  656. "x",
  657. "y",
  658. "z",
  659. "translateX",
  660. "translateY",
  661. "translateZ",
  662. "scale",
  663. "scaleX",
  664. "scaleY",
  665. "rotate",
  666. "rotateX",
  667. "rotateY",
  668. "rotateZ",
  669. "skew",
  670. "skewX",
  671. "skewY",
  672. ];
  673. /**
  674. * A quick lookup for transform props.
  675. */
  676. const transformProps = new Set(transformPropOrder);
  677. const positionalKeys = new Set([
  678. "width",
  679. "height",
  680. "top",
  681. "left",
  682. "right",
  683. "bottom",
  684. ...transformPropOrder,
  685. ]);
  686. const isKeyframesTarget = (v) => {
  687. return Array.isArray(v);
  688. };
  689. const resolveFinalValueInKeyframes = (v) => {
  690. // TODO maybe throw if v.length - 1 is placeholder token?
  691. return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
  692. };
  693. function getValueState(visualElement) {
  694. const state = [{}, {}];
  695. visualElement?.values.forEach((value, key) => {
  696. state[0][key] = value.get();
  697. state[1][key] = value.getVelocity();
  698. });
  699. return state;
  700. }
  701. function resolveVariantFromProps(props, definition, custom, visualElement) {
  702. /**
  703. * If the variant definition is a function, resolve.
  704. */
  705. if (typeof definition === "function") {
  706. const [current, velocity] = getValueState(visualElement);
  707. definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
  708. }
  709. /**
  710. * If the variant definition is a variant label, or
  711. * the function returned a variant label, resolve.
  712. */
  713. if (typeof definition === "string") {
  714. definition = props.variants && props.variants[definition];
  715. }
  716. /**
  717. * At this point we've resolved both functions and variant labels,
  718. * but the resolved variant label might itself have been a function.
  719. * If so, resolve. This can only have returned a valid target object.
  720. */
  721. if (typeof definition === "function") {
  722. const [current, velocity] = getValueState(visualElement);
  723. definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
  724. }
  725. return definition;
  726. }
  727. function resolveVariant(visualElement, definition, custom) {
  728. const props = visualElement.getProps();
  729. return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
  730. }
  731. /**
  732. * Set VisualElement's MotionValue, creating a new MotionValue for it if
  733. * it doesn't exist.
  734. */
  735. function setMotionValue(visualElement, key, value) {
  736. if (visualElement.hasValue(key)) {
  737. visualElement.getValue(key).set(value);
  738. }
  739. else {
  740. visualElement.addValue(key, motionDom.motionValue(value));
  741. }
  742. }
  743. function setTarget(visualElement, definition) {
  744. const resolved = resolveVariant(visualElement, definition);
  745. let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
  746. target = { ...target, ...transitionEnd };
  747. for (const key in target) {
  748. const value = resolveFinalValueInKeyframes(target[key]);
  749. setMotionValue(visualElement, key, value);
  750. }
  751. }
  752. function isWillChangeMotionValue(value) {
  753. return Boolean(isMotionValue(value) && value.add);
  754. }
  755. function addValueToWillChange(visualElement, key) {
  756. const willChange = visualElement.getValue("willChange");
  757. /**
  758. * It could be that a user has set willChange to a regular MotionValue,
  759. * in which case we can't add the value to it.
  760. */
  761. if (isWillChangeMotionValue(willChange)) {
  762. return willChange.add(key);
  763. }
  764. else if (!willChange && motionUtils.MotionGlobalConfig.WillChange) {
  765. const newWillChange = new motionUtils.MotionGlobalConfig.WillChange("auto");
  766. visualElement.addValue("willChange", newWillChange);
  767. newWillChange.add(key);
  768. }
  769. }
  770. /**
  771. * Convert camelCase to dash-case properties.
  772. */
  773. const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
  774. const optimizedAppearDataId = "framerAppearId";
  775. const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
  776. function getOptimisedAppearId(visualElement) {
  777. return visualElement.props[optimizedAppearDataAttribute];
  778. }
  779. /*
  780. Bezier function generator
  781. This has been modified from Gaëtan Renaudeau's BezierEasing
  782. https://github.com/gre/bezier-easing/blob/master/src/index.js
  783. https://github.com/gre/bezier-easing/blob/master/LICENSE
  784. I've removed the newtonRaphsonIterate algo because in benchmarking it
  785. wasn't noticiably faster than binarySubdivision, indeed removing it
  786. usually improved times, depending on the curve.
  787. I also removed the lookup table, as for the added bundle size and loop we're
  788. only cutting ~4 or so subdivision iterations. I bumped the max iterations up
  789. to 12 to compensate and this still tended to be faster for no perceivable
  790. loss in accuracy.
  791. Usage
  792. const easeOut = cubicBezier(.17,.67,.83,.67);
  793. const x = easeOut(0.5); // returns 0.627...
  794. */
  795. // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  796. const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
  797. t;
  798. const subdivisionPrecision = 0.0000001;
  799. const subdivisionMaxIterations = 12;
  800. function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
  801. let currentX;
  802. let currentT;
  803. let i = 0;
  804. do {
  805. currentT = lowerBound + (upperBound - lowerBound) / 2.0;
  806. currentX = calcBezier(currentT, mX1, mX2) - x;
  807. if (currentX > 0.0) {
  808. upperBound = currentT;
  809. }
  810. else {
  811. lowerBound = currentT;
  812. }
  813. } while (Math.abs(currentX) > subdivisionPrecision &&
  814. ++i < subdivisionMaxIterations);
  815. return currentT;
  816. }
  817. function cubicBezier(mX1, mY1, mX2, mY2) {
  818. // If this is a linear gradient, return linear easing
  819. if (mX1 === mY1 && mX2 === mY2)
  820. return motionUtils.noop;
  821. const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
  822. // If animation is at start/end, return t without easing
  823. return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
  824. }
  825. // Accepts an easing function and returns a new one that outputs mirrored values for
  826. // the second half of the animation. Turns easeIn into easeInOut.
  827. const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
  828. // Accepts an easing function and returns a new one that outputs reversed values.
  829. // Turns easeIn into easeOut.
  830. const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
  831. const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
  832. const backIn = /*@__PURE__*/ reverseEasing(backOut);
  833. const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
  834. const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
  835. const circIn = (p) => 1 - Math.sin(Math.acos(p));
  836. const circOut = reverseEasing(circIn);
  837. const circInOut = mirrorEasing(circIn);
  838. /**
  839. * Check if the value is a zero value string like "0px" or "0%"
  840. */
  841. const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
  842. function isNone(value) {
  843. if (typeof value === "number") {
  844. return value === 0;
  845. }
  846. else if (value !== null) {
  847. return value === "none" || value === "0" || isZeroValueString(value);
  848. }
  849. else {
  850. return true;
  851. }
  852. }
  853. const number = {
  854. test: (v) => typeof v === "number",
  855. parse: parseFloat,
  856. transform: (v) => v,
  857. };
  858. const alpha = {
  859. ...number,
  860. transform: (v) => clamp(0, 1, v),
  861. };
  862. const scale = {
  863. ...number,
  864. default: 1,
  865. };
  866. // If this number is a decimal, make it just five decimal places
  867. // to avoid exponents
  868. const sanitize = (v) => Math.round(v * 100000) / 100000;
  869. const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu;
  870. function isNullish(v) {
  871. return v == null;
  872. }
  873. const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu;
  874. /**
  875. * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
  876. * but false if a number or multiple colors
  877. */
  878. const isColorString = (type, testProp) => (v) => {
  879. return Boolean((typeof v === "string" &&
  880. singleColorRegex.test(v) &&
  881. v.startsWith(type)) ||
  882. (testProp &&
  883. !isNullish(v) &&
  884. Object.prototype.hasOwnProperty.call(v, testProp)));
  885. };
  886. const splitColor = (aName, bName, cName) => (v) => {
  887. if (typeof v !== "string")
  888. return v;
  889. const [a, b, c, alpha] = v.match(floatRegex);
  890. return {
  891. [aName]: parseFloat(a),
  892. [bName]: parseFloat(b),
  893. [cName]: parseFloat(c),
  894. alpha: alpha !== undefined ? parseFloat(alpha) : 1,
  895. };
  896. };
  897. const clampRgbUnit = (v) => clamp(0, 255, v);
  898. const rgbUnit = {
  899. ...number,
  900. transform: (v) => Math.round(clampRgbUnit(v)),
  901. };
  902. const rgba = {
  903. test: /*@__PURE__*/ isColorString("rgb", "red"),
  904. parse: /*@__PURE__*/ splitColor("red", "green", "blue"),
  905. transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
  906. rgbUnit.transform(red) +
  907. ", " +
  908. rgbUnit.transform(green) +
  909. ", " +
  910. rgbUnit.transform(blue) +
  911. ", " +
  912. sanitize(alpha.transform(alpha$1)) +
  913. ")",
  914. };
  915. function parseHex(v) {
  916. let r = "";
  917. let g = "";
  918. let b = "";
  919. let a = "";
  920. // If we have 6 characters, ie #FF0000
  921. if (v.length > 5) {
  922. r = v.substring(1, 3);
  923. g = v.substring(3, 5);
  924. b = v.substring(5, 7);
  925. a = v.substring(7, 9);
  926. // Or we have 3 characters, ie #F00
  927. }
  928. else {
  929. r = v.substring(1, 2);
  930. g = v.substring(2, 3);
  931. b = v.substring(3, 4);
  932. a = v.substring(4, 5);
  933. r += r;
  934. g += g;
  935. b += b;
  936. a += a;
  937. }
  938. return {
  939. red: parseInt(r, 16),
  940. green: parseInt(g, 16),
  941. blue: parseInt(b, 16),
  942. alpha: a ? parseInt(a, 16) / 255 : 1,
  943. };
  944. }
  945. const hex = {
  946. test: /*@__PURE__*/ isColorString("#"),
  947. parse: parseHex,
  948. transform: rgba.transform,
  949. };
  950. const createUnitType = (unit) => ({
  951. test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1,
  952. parse: parseFloat,
  953. transform: (v) => `${v}${unit}`,
  954. });
  955. const degrees = /*@__PURE__*/ createUnitType("deg");
  956. const percent = /*@__PURE__*/ createUnitType("%");
  957. const px = /*@__PURE__*/ createUnitType("px");
  958. const vh = /*@__PURE__*/ createUnitType("vh");
  959. const vw = /*@__PURE__*/ createUnitType("vw");
  960. const progressPercentage = {
  961. ...percent,
  962. parse: (v) => percent.parse(v) / 100,
  963. transform: (v) => percent.transform(v * 100),
  964. };
  965. const hsla = {
  966. test: /*@__PURE__*/ isColorString("hsl", "hue"),
  967. parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"),
  968. transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
  969. return ("hsla(" +
  970. Math.round(hue) +
  971. ", " +
  972. percent.transform(sanitize(saturation)) +
  973. ", " +
  974. percent.transform(sanitize(lightness)) +
  975. ", " +
  976. sanitize(alpha.transform(alpha$1)) +
  977. ")");
  978. },
  979. };
  980. const color = {
  981. test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
  982. parse: (v) => {
  983. if (rgba.test(v)) {
  984. return rgba.parse(v);
  985. }
  986. else if (hsla.test(v)) {
  987. return hsla.parse(v);
  988. }
  989. else {
  990. return hex.parse(v);
  991. }
  992. },
  993. transform: (v) => {
  994. return typeof v === "string"
  995. ? v
  996. : v.hasOwnProperty("red")
  997. ? rgba.transform(v)
  998. : hsla.transform(v);
  999. },
  1000. };
  1001. const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;
  1002. function test(v) {
  1003. return (isNaN(v) &&
  1004. typeof v === "string" &&
  1005. (v.match(floatRegex)?.length || 0) +
  1006. (v.match(colorRegex)?.length || 0) >
  1007. 0);
  1008. }
  1009. const NUMBER_TOKEN = "number";
  1010. const COLOR_TOKEN = "color";
  1011. const VAR_TOKEN = "var";
  1012. const VAR_FUNCTION_TOKEN = "var(";
  1013. const SPLIT_TOKEN = "${}";
  1014. // this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex`
  1015. 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;
  1016. function analyseComplexValue(value) {
  1017. const originalValue = value.toString();
  1018. const values = [];
  1019. const indexes = {
  1020. color: [],
  1021. number: [],
  1022. var: [],
  1023. };
  1024. const types = [];
  1025. let i = 0;
  1026. const tokenised = originalValue.replace(complexRegex, (parsedValue) => {
  1027. if (color.test(parsedValue)) {
  1028. indexes.color.push(i);
  1029. types.push(COLOR_TOKEN);
  1030. values.push(color.parse(parsedValue));
  1031. }
  1032. else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) {
  1033. indexes.var.push(i);
  1034. types.push(VAR_TOKEN);
  1035. values.push(parsedValue);
  1036. }
  1037. else {
  1038. indexes.number.push(i);
  1039. types.push(NUMBER_TOKEN);
  1040. values.push(parseFloat(parsedValue));
  1041. }
  1042. ++i;
  1043. return SPLIT_TOKEN;
  1044. });
  1045. const split = tokenised.split(SPLIT_TOKEN);
  1046. return { values, split, indexes, types };
  1047. }
  1048. function parseComplexValue(v) {
  1049. return analyseComplexValue(v).values;
  1050. }
  1051. function createTransformer(source) {
  1052. const { split, types } = analyseComplexValue(source);
  1053. const numSections = split.length;
  1054. return (v) => {
  1055. let output = "";
  1056. for (let i = 0; i < numSections; i++) {
  1057. output += split[i];
  1058. if (v[i] !== undefined) {
  1059. const type = types[i];
  1060. if (type === NUMBER_TOKEN) {
  1061. output += sanitize(v[i]);
  1062. }
  1063. else if (type === COLOR_TOKEN) {
  1064. output += color.transform(v[i]);
  1065. }
  1066. else {
  1067. output += v[i];
  1068. }
  1069. }
  1070. }
  1071. return output;
  1072. };
  1073. }
  1074. const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
  1075. function getAnimatableNone$1(v) {
  1076. const parsed = parseComplexValue(v);
  1077. const transformer = createTransformer(v);
  1078. return transformer(parsed.map(convertNumbersToZero));
  1079. }
  1080. const complex = {
  1081. test,
  1082. parse: parseComplexValue,
  1083. createTransformer,
  1084. getAnimatableNone: getAnimatableNone$1,
  1085. };
  1086. /**
  1087. * Properties that should default to 1 or 100%
  1088. */
  1089. const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
  1090. function applyDefaultFilter(v) {
  1091. const [name, value] = v.slice(0, -1).split("(");
  1092. if (name === "drop-shadow")
  1093. return v;
  1094. const [number] = value.match(floatRegex) || [];
  1095. if (!number)
  1096. return v;
  1097. const unit = value.replace(number, "");
  1098. let defaultValue = maxDefaults.has(name) ? 1 : 0;
  1099. if (number !== value)
  1100. defaultValue *= 100;
  1101. return name + "(" + defaultValue + unit + ")";
  1102. }
  1103. const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
  1104. const filter = {
  1105. ...complex,
  1106. getAnimatableNone: (v) => {
  1107. const functions = v.match(functionRegex);
  1108. return functions ? functions.map(applyDefaultFilter).join(" ") : v;
  1109. },
  1110. };
  1111. const browserNumberValueTypes = {
  1112. // Border props
  1113. borderWidth: px,
  1114. borderTopWidth: px,
  1115. borderRightWidth: px,
  1116. borderBottomWidth: px,
  1117. borderLeftWidth: px,
  1118. borderRadius: px,
  1119. radius: px,
  1120. borderTopLeftRadius: px,
  1121. borderTopRightRadius: px,
  1122. borderBottomRightRadius: px,
  1123. borderBottomLeftRadius: px,
  1124. // Positioning props
  1125. width: px,
  1126. maxWidth: px,
  1127. height: px,
  1128. maxHeight: px,
  1129. top: px,
  1130. right: px,
  1131. bottom: px,
  1132. left: px,
  1133. // Spacing props
  1134. padding: px,
  1135. paddingTop: px,
  1136. paddingRight: px,
  1137. paddingBottom: px,
  1138. paddingLeft: px,
  1139. margin: px,
  1140. marginTop: px,
  1141. marginRight: px,
  1142. marginBottom: px,
  1143. marginLeft: px,
  1144. // Misc
  1145. backgroundPositionX: px,
  1146. backgroundPositionY: px,
  1147. };
  1148. const transformValueTypes = {
  1149. rotate: degrees,
  1150. rotateX: degrees,
  1151. rotateY: degrees,
  1152. rotateZ: degrees,
  1153. scale,
  1154. scaleX: scale,
  1155. scaleY: scale,
  1156. scaleZ: scale,
  1157. skew: degrees,
  1158. skewX: degrees,
  1159. skewY: degrees,
  1160. distance: px,
  1161. translateX: px,
  1162. translateY: px,
  1163. translateZ: px,
  1164. x: px,
  1165. y: px,
  1166. z: px,
  1167. perspective: px,
  1168. transformPerspective: px,
  1169. opacity: alpha,
  1170. originX: progressPercentage,
  1171. originY: progressPercentage,
  1172. originZ: px,
  1173. };
  1174. const int = {
  1175. ...number,
  1176. transform: Math.round,
  1177. };
  1178. const numberValueTypes = {
  1179. ...browserNumberValueTypes,
  1180. ...transformValueTypes,
  1181. zIndex: int,
  1182. size: px,
  1183. // SVG
  1184. fillOpacity: alpha,
  1185. strokeOpacity: alpha,
  1186. numOctaves: int,
  1187. };
  1188. /**
  1189. * A map of default value types for common values
  1190. */
  1191. const defaultValueTypes = {
  1192. ...numberValueTypes,
  1193. // Color props
  1194. color,
  1195. backgroundColor: color,
  1196. outlineColor: color,
  1197. fill: color,
  1198. stroke: color,
  1199. // Border props
  1200. borderColor: color,
  1201. borderTopColor: color,
  1202. borderRightColor: color,
  1203. borderBottomColor: color,
  1204. borderLeftColor: color,
  1205. filter,
  1206. WebkitFilter: filter,
  1207. };
  1208. /**
  1209. * Gets the default ValueType for the provided value key
  1210. */
  1211. const getDefaultValueType = (key) => defaultValueTypes[key];
  1212. function getAnimatableNone(key, value) {
  1213. let defaultValueType = getDefaultValueType(key);
  1214. if (defaultValueType !== filter)
  1215. defaultValueType = complex;
  1216. // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
  1217. return defaultValueType.getAnimatableNone
  1218. ? defaultValueType.getAnimatableNone(value)
  1219. : undefined;
  1220. }
  1221. /**
  1222. * If we encounter keyframes like "none" or "0" and we also have keyframes like
  1223. * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
  1224. * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
  1225. * zero equivalents, i.e. "#fff0" or "0px 0px".
  1226. */
  1227. const invalidTemplates = new Set(["auto", "none", "0"]);
  1228. function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
  1229. let i = 0;
  1230. let animatableTemplate = undefined;
  1231. while (i < unresolvedKeyframes.length && !animatableTemplate) {
  1232. const keyframe = unresolvedKeyframes[i];
  1233. if (typeof keyframe === "string" &&
  1234. !invalidTemplates.has(keyframe) &&
  1235. analyseComplexValue(keyframe).values.length) {
  1236. animatableTemplate = unresolvedKeyframes[i];
  1237. }
  1238. i++;
  1239. }
  1240. if (animatableTemplate && name) {
  1241. for (const noneIndex of noneKeyframeIndexes) {
  1242. unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
  1243. }
  1244. }
  1245. }
  1246. const radToDeg = (rad) => (rad * 180) / Math.PI;
  1247. const rotate = (v) => {
  1248. const angle = radToDeg(Math.atan2(v[1], v[0]));
  1249. return rebaseAngle(angle);
  1250. };
  1251. const matrix2dParsers = {
  1252. x: 4,
  1253. y: 5,
  1254. translateX: 4,
  1255. translateY: 5,
  1256. scaleX: 0,
  1257. scaleY: 3,
  1258. scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
  1259. rotate,
  1260. rotateZ: rotate,
  1261. skewX: (v) => radToDeg(Math.atan(v[1])),
  1262. skewY: (v) => radToDeg(Math.atan(v[2])),
  1263. skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
  1264. };
  1265. const rebaseAngle = (angle) => {
  1266. angle = angle % 360;
  1267. if (angle < 0)
  1268. angle += 360;
  1269. return angle;
  1270. };
  1271. const rotateZ = rotate;
  1272. const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  1273. const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
  1274. const matrix3dParsers = {
  1275. x: 12,
  1276. y: 13,
  1277. z: 14,
  1278. translateX: 12,
  1279. translateY: 13,
  1280. translateZ: 14,
  1281. scaleX,
  1282. scaleY,
  1283. scale: (v) => (scaleX(v) + scaleY(v)) / 2,
  1284. rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
  1285. rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
  1286. rotateZ,
  1287. rotate: rotateZ,
  1288. skewX: (v) => radToDeg(Math.atan(v[4])),
  1289. skewY: (v) => radToDeg(Math.atan(v[1])),
  1290. skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
  1291. };
  1292. function defaultTransformValue(name) {
  1293. return name.includes("scale") ? 1 : 0;
  1294. }
  1295. function parseValueFromTransform(transform, name) {
  1296. if (!transform || transform === "none") {
  1297. return defaultTransformValue(name);
  1298. }
  1299. const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
  1300. let parsers;
  1301. let match;
  1302. if (matrix3dMatch) {
  1303. parsers = matrix3dParsers;
  1304. match = matrix3dMatch;
  1305. }
  1306. else {
  1307. const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
  1308. parsers = matrix2dParsers;
  1309. match = matrix2dMatch;
  1310. }
  1311. if (!match) {
  1312. return defaultTransformValue(name);
  1313. }
  1314. const valueParser = parsers[name];
  1315. const values = match[1].split(",").map(convertTransformToNumber);
  1316. return typeof valueParser === "function"
  1317. ? valueParser(values)
  1318. : values[valueParser];
  1319. }
  1320. const readTransformValue = (instance, name) => {
  1321. const { transform = "none" } = getComputedStyle(instance);
  1322. return parseValueFromTransform(transform, name);
  1323. };
  1324. function convertTransformToNumber(value) {
  1325. return parseFloat(value.trim());
  1326. }
  1327. const isNumOrPxType = (v) => v === number || v === px;
  1328. const transformKeys = new Set(["x", "y", "z"]);
  1329. const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
  1330. function removeNonTranslationalTransform(visualElement) {
  1331. const removedTransforms = [];
  1332. nonTranslationalTransformKeys.forEach((key) => {
  1333. const value = visualElement.getValue(key);
  1334. if (value !== undefined) {
  1335. removedTransforms.push([key, value.get()]);
  1336. value.set(key.startsWith("scale") ? 1 : 0);
  1337. }
  1338. });
  1339. return removedTransforms;
  1340. }
  1341. const positionalValues = {
  1342. // Dimensions
  1343. width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
  1344. height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
  1345. top: (_bbox, { top }) => parseFloat(top),
  1346. left: (_bbox, { left }) => parseFloat(left),
  1347. bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
  1348. right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
  1349. // Transform
  1350. x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
  1351. y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
  1352. };
  1353. // Alias translate longform names
  1354. positionalValues.translateX = positionalValues.x;
  1355. positionalValues.translateY = positionalValues.y;
  1356. const toResolve = new Set();
  1357. let isScheduled = false;
  1358. let anyNeedsMeasurement = false;
  1359. function measureAllKeyframes() {
  1360. if (anyNeedsMeasurement) {
  1361. const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
  1362. const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
  1363. const transformsToRestore = new Map();
  1364. /**
  1365. * Write pass
  1366. * If we're measuring elements we want to remove bounding box-changing transforms.
  1367. */
  1368. elementsToMeasure.forEach((element) => {
  1369. const removedTransforms = removeNonTranslationalTransform(element);
  1370. if (!removedTransforms.length)
  1371. return;
  1372. transformsToRestore.set(element, removedTransforms);
  1373. element.render();
  1374. });
  1375. // Read
  1376. resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
  1377. // Write
  1378. elementsToMeasure.forEach((element) => {
  1379. element.render();
  1380. const restore = transformsToRestore.get(element);
  1381. if (restore) {
  1382. restore.forEach(([key, value]) => {
  1383. element.getValue(key)?.set(value);
  1384. });
  1385. }
  1386. });
  1387. // Read
  1388. resolversToMeasure.forEach((resolver) => resolver.measureEndState());
  1389. // Write
  1390. resolversToMeasure.forEach((resolver) => {
  1391. if (resolver.suspendedScrollY !== undefined) {
  1392. window.scrollTo(0, resolver.suspendedScrollY);
  1393. }
  1394. });
  1395. }
  1396. anyNeedsMeasurement = false;
  1397. isScheduled = false;
  1398. toResolve.forEach((resolver) => resolver.complete());
  1399. toResolve.clear();
  1400. }
  1401. function readAllKeyframes() {
  1402. toResolve.forEach((resolver) => {
  1403. resolver.readKeyframes();
  1404. if (resolver.needsMeasurement) {
  1405. anyNeedsMeasurement = true;
  1406. }
  1407. });
  1408. }
  1409. function flushKeyframeResolvers() {
  1410. readAllKeyframes();
  1411. measureAllKeyframes();
  1412. }
  1413. class KeyframeResolver {
  1414. constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
  1415. /**
  1416. * Track whether this resolver has completed. Once complete, it never
  1417. * needs to attempt keyframe resolution again.
  1418. */
  1419. this.isComplete = false;
  1420. /**
  1421. * Track whether this resolver is async. If it is, it'll be added to the
  1422. * resolver queue and flushed in the next frame. Resolvers that aren't going
  1423. * to trigger read/write thrashing don't need to be async.
  1424. */
  1425. this.isAsync = false;
  1426. /**
  1427. * Track whether this resolver needs to perform a measurement
  1428. * to resolve its keyframes.
  1429. */
  1430. this.needsMeasurement = false;
  1431. /**
  1432. * Track whether this resolver is currently scheduled to resolve
  1433. * to allow it to be cancelled and resumed externally.
  1434. */
  1435. this.isScheduled = false;
  1436. this.unresolvedKeyframes = [...unresolvedKeyframes];
  1437. this.onComplete = onComplete;
  1438. this.name = name;
  1439. this.motionValue = motionValue;
  1440. this.element = element;
  1441. this.isAsync = isAsync;
  1442. }
  1443. scheduleResolve() {
  1444. this.isScheduled = true;
  1445. if (this.isAsync) {
  1446. toResolve.add(this);
  1447. if (!isScheduled) {
  1448. isScheduled = true;
  1449. motionDom.frame.read(readAllKeyframes);
  1450. motionDom.frame.resolveKeyframes(measureAllKeyframes);
  1451. }
  1452. }
  1453. else {
  1454. this.readKeyframes();
  1455. this.complete();
  1456. }
  1457. }
  1458. readKeyframes() {
  1459. const { unresolvedKeyframes, name, element, motionValue } = this;
  1460. /**
  1461. * If a keyframe is null, we hydrate it either by reading it from
  1462. * the instance, or propagating from previous keyframes.
  1463. */
  1464. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1465. if (unresolvedKeyframes[i] === null) {
  1466. /**
  1467. * If the first keyframe is null, we need to find its value by sampling the element
  1468. */
  1469. if (i === 0) {
  1470. const currentValue = motionValue?.get();
  1471. const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
  1472. if (currentValue !== undefined) {
  1473. unresolvedKeyframes[0] = currentValue;
  1474. }
  1475. else if (element && name) {
  1476. const valueAsRead = element.readValue(name, finalKeyframe);
  1477. if (valueAsRead !== undefined && valueAsRead !== null) {
  1478. unresolvedKeyframes[0] = valueAsRead;
  1479. }
  1480. }
  1481. if (unresolvedKeyframes[0] === undefined) {
  1482. unresolvedKeyframes[0] = finalKeyframe;
  1483. }
  1484. if (motionValue && currentValue === undefined) {
  1485. motionValue.set(unresolvedKeyframes[0]);
  1486. }
  1487. }
  1488. else {
  1489. unresolvedKeyframes[i] = unresolvedKeyframes[i - 1];
  1490. }
  1491. }
  1492. }
  1493. }
  1494. setFinalKeyframe() { }
  1495. measureInitialState() { }
  1496. renderEndStyles() { }
  1497. measureEndState() { }
  1498. complete() {
  1499. this.isComplete = true;
  1500. this.onComplete(this.unresolvedKeyframes, this.finalKeyframe);
  1501. toResolve.delete(this);
  1502. }
  1503. cancel() {
  1504. if (!this.isComplete) {
  1505. this.isScheduled = false;
  1506. toResolve.delete(this);
  1507. }
  1508. }
  1509. resume() {
  1510. if (!this.isComplete)
  1511. this.scheduleResolve();
  1512. }
  1513. }
  1514. /**
  1515. * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
  1516. */
  1517. const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
  1518. const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
  1519. const isCSSVariableName =
  1520. /*@__PURE__*/ checkStringStartsWith("--");
  1521. const startsAsVariableToken =
  1522. /*@__PURE__*/ checkStringStartsWith("var(--");
  1523. const isCSSVariableToken = (value) => {
  1524. const startsWithToken = startsAsVariableToken(value);
  1525. if (!startsWithToken)
  1526. return false;
  1527. // Ensure any comments are stripped from the value as this can harm performance of the regex.
  1528. return singleCssVariableRegex.test(value.split("/*")[0].trim());
  1529. };
  1530. const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu;
  1531. /**
  1532. * Parse Framer's special CSS variable format into a CSS token and a fallback.
  1533. *
  1534. * ```
  1535. * `var(--foo, #fff)` => [`--foo`, '#fff']
  1536. * ```
  1537. *
  1538. * @param current
  1539. */
  1540. const splitCSSVariableRegex =
  1541. // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
  1542. /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
  1543. function parseCSSVariable(current) {
  1544. const match = splitCSSVariableRegex.exec(current);
  1545. if (!match)
  1546. return [,];
  1547. const [, token1, token2, fallback] = match;
  1548. return [`--${token1 ?? token2}`, fallback];
  1549. }
  1550. const maxDepth = 4;
  1551. function getVariableValue(current, element, depth = 1) {
  1552. motionUtils.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
  1553. const [token, fallback] = parseCSSVariable(current);
  1554. // No CSS variable detected
  1555. if (!token)
  1556. return;
  1557. // Attempt to read this CSS variable off the element
  1558. const resolved = window.getComputedStyle(element).getPropertyValue(token);
  1559. if (resolved) {
  1560. const trimmed = resolved.trim();
  1561. return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
  1562. }
  1563. return isCSSVariableToken(fallback)
  1564. ? getVariableValue(fallback, element, depth + 1)
  1565. : fallback;
  1566. }
  1567. /**
  1568. * Tests a provided value against a ValueType
  1569. */
  1570. const testValueType = (v) => (type) => type.test(v);
  1571. /**
  1572. * ValueType for "auto"
  1573. */
  1574. const auto = {
  1575. test: (v) => v === "auto",
  1576. parse: (v) => v,
  1577. };
  1578. /**
  1579. * A list of value types commonly used for dimensions
  1580. */
  1581. const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
  1582. /**
  1583. * Tests a dimensional value against the list of dimension ValueTypes
  1584. */
  1585. const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
  1586. class DOMKeyframesResolver extends KeyframeResolver {
  1587. constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
  1588. super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
  1589. }
  1590. readKeyframes() {
  1591. const { unresolvedKeyframes, element, name } = this;
  1592. if (!element || !element.current)
  1593. return;
  1594. super.readKeyframes();
  1595. /**
  1596. * If any keyframe is a CSS variable, we need to find its value by sampling the element
  1597. */
  1598. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1599. let keyframe = unresolvedKeyframes[i];
  1600. if (typeof keyframe === "string") {
  1601. keyframe = keyframe.trim();
  1602. if (isCSSVariableToken(keyframe)) {
  1603. const resolved = getVariableValue(keyframe, element.current);
  1604. if (resolved !== undefined) {
  1605. unresolvedKeyframes[i] = resolved;
  1606. }
  1607. if (i === unresolvedKeyframes.length - 1) {
  1608. this.finalKeyframe = keyframe;
  1609. }
  1610. }
  1611. }
  1612. }
  1613. /**
  1614. * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
  1615. * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
  1616. * have a far bigger performance impact.
  1617. */
  1618. this.resolveNoneKeyframes();
  1619. /**
  1620. * Check to see if unit type has changed. If so schedule jobs that will
  1621. * temporarily set styles to the destination keyframes.
  1622. * Skip if we have more than two keyframes or this isn't a positional value.
  1623. * TODO: We can throw if there are multiple keyframes and the value type changes.
  1624. */
  1625. if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
  1626. return;
  1627. }
  1628. const [origin, target] = unresolvedKeyframes;
  1629. const originType = findDimensionValueType(origin);
  1630. const targetType = findDimensionValueType(target);
  1631. /**
  1632. * Either we don't recognise these value types or we can animate between them.
  1633. */
  1634. if (originType === targetType)
  1635. return;
  1636. /**
  1637. * If both values are numbers or pixels, we can animate between them by
  1638. * converting them to numbers.
  1639. */
  1640. if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
  1641. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1642. const value = unresolvedKeyframes[i];
  1643. if (typeof value === "string") {
  1644. unresolvedKeyframes[i] = parseFloat(value);
  1645. }
  1646. }
  1647. }
  1648. else {
  1649. /**
  1650. * Else, the only way to resolve this is by measuring the element.
  1651. */
  1652. this.needsMeasurement = true;
  1653. }
  1654. }
  1655. resolveNoneKeyframes() {
  1656. const { unresolvedKeyframes, name } = this;
  1657. const noneKeyframeIndexes = [];
  1658. for (let i = 0; i < unresolvedKeyframes.length; i++) {
  1659. if (isNone(unresolvedKeyframes[i])) {
  1660. noneKeyframeIndexes.push(i);
  1661. }
  1662. }
  1663. if (noneKeyframeIndexes.length) {
  1664. makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
  1665. }
  1666. }
  1667. measureInitialState() {
  1668. const { element, unresolvedKeyframes, name } = this;
  1669. if (!element || !element.current)
  1670. return;
  1671. if (name === "height") {
  1672. this.suspendedScrollY = window.pageYOffset;
  1673. }
  1674. this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  1675. unresolvedKeyframes[0] = this.measuredOrigin;
  1676. // Set final key frame to measure after next render
  1677. const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
  1678. if (measureKeyframe !== undefined) {
  1679. element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
  1680. }
  1681. }
  1682. measureEndState() {
  1683. const { element, name, unresolvedKeyframes } = this;
  1684. if (!element || !element.current)
  1685. return;
  1686. const value = element.getValue(name);
  1687. value && value.jump(this.measuredOrigin, false);
  1688. const finalKeyframeIndex = unresolvedKeyframes.length - 1;
  1689. const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
  1690. unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
  1691. if (finalKeyframe !== null && this.finalKeyframe === undefined) {
  1692. this.finalKeyframe = finalKeyframe;
  1693. }
  1694. // If we removed transform values, reapply them before the next render
  1695. if (this.removedTransforms?.length) {
  1696. this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
  1697. element
  1698. .getValue(unsetTransformName)
  1699. .set(unsetTransformValue);
  1700. });
  1701. }
  1702. this.resolveNoneKeyframes();
  1703. }
  1704. }
  1705. /**
  1706. * Check if a value is animatable. Examples:
  1707. *
  1708. * ✅: 100, "100px", "#fff"
  1709. * ❌: "block", "url(2.jpg)"
  1710. * @param value
  1711. *
  1712. * @internal
  1713. */
  1714. const isAnimatable = (value, name) => {
  1715. // If the list of keys tat might be non-animatable grows, replace with Set
  1716. if (name === "zIndex")
  1717. return false;
  1718. // If it's a number or a keyframes array, we can animate it. We might at some point
  1719. // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
  1720. // but for now lets leave it like this for performance reasons
  1721. if (typeof value === "number" || Array.isArray(value))
  1722. return true;
  1723. if (typeof value === "string" && // It's animatable if we have a string
  1724. (complex.test(value) || value === "0") && // And it contains numbers and/or colors
  1725. !value.startsWith("url(") // Unless it starts with "url("
  1726. ) {
  1727. return true;
  1728. }
  1729. return false;
  1730. };
  1731. function hasKeyframesChanged(keyframes) {
  1732. const current = keyframes[0];
  1733. if (keyframes.length === 1)
  1734. return true;
  1735. for (let i = 0; i < keyframes.length; i++) {
  1736. if (keyframes[i] !== current)
  1737. return true;
  1738. }
  1739. }
  1740. function canAnimate(keyframes, name, type, velocity) {
  1741. /**
  1742. * Check if we're able to animate between the start and end keyframes,
  1743. * and throw a warning if we're attempting to animate between one that's
  1744. * animatable and another that isn't.
  1745. */
  1746. const originKeyframe = keyframes[0];
  1747. if (originKeyframe === null)
  1748. return false;
  1749. /**
  1750. * These aren't traditionally animatable but we do support them.
  1751. * In future we could look into making this more generic or replacing
  1752. * this function with mix() === mixImmediate
  1753. */
  1754. if (name === "display" || name === "visibility")
  1755. return true;
  1756. const targetKeyframe = keyframes[keyframes.length - 1];
  1757. const isOriginAnimatable = isAnimatable(originKeyframe, name);
  1758. const isTargetAnimatable = isAnimatable(targetKeyframe, name);
  1759. 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.`);
  1760. // Always skip if any of these are true
  1761. if (!isOriginAnimatable || !isTargetAnimatable) {
  1762. return false;
  1763. }
  1764. return (hasKeyframesChanged(keyframes) ||
  1765. ((type === "spring" || motionDom.isGenerator(type)) && velocity));
  1766. }
  1767. const isNotNull = (value) => value !== null;
  1768. function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
  1769. const resolvedKeyframes = keyframes.filter(isNotNull);
  1770. const index = repeat && repeatType !== "loop" && repeat % 2 === 1
  1771. ? 0
  1772. : resolvedKeyframes.length - 1;
  1773. return !index || finalKeyframe === undefined
  1774. ? resolvedKeyframes[index]
  1775. : finalKeyframe;
  1776. }
  1777. /**
  1778. * Maximum time allowed between an animation being created and it being
  1779. * resolved for us to use the latter as the start time.
  1780. *
  1781. * This is to ensure that while we prefer to "start" an animation as soon
  1782. * as it's triggered, we also want to avoid a visual jump if there's a big delay
  1783. * between these two moments.
  1784. */
  1785. const MAX_RESOLVE_DELAY = 40;
  1786. class BaseAnimation {
  1787. constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", ...options }) {
  1788. // Track whether the animation has been stopped. Stopped animations won't restart.
  1789. this.isStopped = false;
  1790. this.hasAttemptedResolve = false;
  1791. this.createdAt = motionDom.time.now();
  1792. this.options = {
  1793. autoplay,
  1794. delay,
  1795. type,
  1796. repeat,
  1797. repeatDelay,
  1798. repeatType,
  1799. ...options,
  1800. };
  1801. this.updateFinishedPromise();
  1802. }
  1803. /**
  1804. * This method uses the createdAt and resolvedAt to calculate the
  1805. * animation startTime. *Ideally*, we would use the createdAt time as t=0
  1806. * as the following frame would then be the first frame of the animation in
  1807. * progress, which would feel snappier.
  1808. *
  1809. * However, if there's a delay (main thread work) between the creation of
  1810. * the animation and the first commited frame, we prefer to use resolvedAt
  1811. * to avoid a sudden jump into the animation.
  1812. */
  1813. calcStartTime() {
  1814. if (!this.resolvedAt)
  1815. return this.createdAt;
  1816. return this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
  1817. ? this.resolvedAt
  1818. : this.createdAt;
  1819. }
  1820. /**
  1821. * A getter for resolved data. If keyframes are not yet resolved, accessing
  1822. * this.resolved will synchronously flush all pending keyframe resolvers.
  1823. * This is a deoptimisation, but at its worst still batches read/writes.
  1824. */
  1825. get resolved() {
  1826. if (!this._resolved && !this.hasAttemptedResolve) {
  1827. flushKeyframeResolvers();
  1828. }
  1829. return this._resolved;
  1830. }
  1831. /**
  1832. * A method to be called when the keyframes resolver completes. This method
  1833. * will check if its possible to run the animation and, if not, skip it.
  1834. * Otherwise, it will call initPlayback on the implementing class.
  1835. */
  1836. onKeyframesResolved(keyframes, finalKeyframe) {
  1837. this.resolvedAt = motionDom.time.now();
  1838. this.hasAttemptedResolve = true;
  1839. const { name, type, velocity, delay, onComplete, onUpdate, isGenerator, } = this.options;
  1840. /**
  1841. * If we can't animate this value with the resolved keyframes
  1842. * then we should complete it immediately.
  1843. */
  1844. if (!isGenerator && !canAnimate(keyframes, name, type, velocity)) {
  1845. // Finish immediately
  1846. if (!delay) {
  1847. onUpdate &&
  1848. onUpdate(getFinalKeyframe(keyframes, this.options, finalKeyframe));
  1849. onComplete && onComplete();
  1850. this.resolveFinishedPromise();
  1851. return;
  1852. }
  1853. // Finish after a delay
  1854. else {
  1855. this.options.duration = 0;
  1856. }
  1857. }
  1858. const resolvedAnimation = this.initPlayback(keyframes, finalKeyframe);
  1859. if (resolvedAnimation === false)
  1860. return;
  1861. this._resolved = {
  1862. keyframes,
  1863. finalKeyframe,
  1864. ...resolvedAnimation,
  1865. };
  1866. this.onPostResolved();
  1867. }
  1868. onPostResolved() { }
  1869. /**
  1870. * Allows the returned animation to be awaited or promise-chained. Currently
  1871. * resolves when the animation finishes at all but in a future update could/should
  1872. * reject if its cancels.
  1873. */
  1874. then(resolve, reject) {
  1875. return this.currentFinishedPromise.then(resolve, reject);
  1876. }
  1877. flatten() {
  1878. if (!this.options.allowFlatten)
  1879. return;
  1880. this.options.type = "keyframes";
  1881. this.options.ease = "linear";
  1882. }
  1883. updateFinishedPromise() {
  1884. this.currentFinishedPromise = new Promise((resolve) => {
  1885. this.resolveFinishedPromise = resolve;
  1886. });
  1887. }
  1888. }
  1889. // Adapted from https://gist.github.com/mjackson/5311256
  1890. function hueToRgb(p, q, t) {
  1891. if (t < 0)
  1892. t += 1;
  1893. if (t > 1)
  1894. t -= 1;
  1895. if (t < 1 / 6)
  1896. return p + (q - p) * 6 * t;
  1897. if (t < 1 / 2)
  1898. return q;
  1899. if (t < 2 / 3)
  1900. return p + (q - p) * (2 / 3 - t) * 6;
  1901. return p;
  1902. }
  1903. function hslaToRgba({ hue, saturation, lightness, alpha }) {
  1904. hue /= 360;
  1905. saturation /= 100;
  1906. lightness /= 100;
  1907. let red = 0;
  1908. let green = 0;
  1909. let blue = 0;
  1910. if (!saturation) {
  1911. red = green = blue = lightness;
  1912. }
  1913. else {
  1914. const q = lightness < 0.5
  1915. ? lightness * (1 + saturation)
  1916. : lightness + saturation - lightness * saturation;
  1917. const p = 2 * lightness - q;
  1918. red = hueToRgb(p, q, hue + 1 / 3);
  1919. green = hueToRgb(p, q, hue);
  1920. blue = hueToRgb(p, q, hue - 1 / 3);
  1921. }
  1922. return {
  1923. red: Math.round(red * 255),
  1924. green: Math.round(green * 255),
  1925. blue: Math.round(blue * 255),
  1926. alpha,
  1927. };
  1928. }
  1929. function mixImmediate(a, b) {
  1930. return (p) => (p > 0 ? b : a);
  1931. }
  1932. // Linear color space blending
  1933. // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
  1934. // Demonstrated http://codepen.io/osublake/pen/xGVVaN
  1935. const mixLinearColor = (from, to, v) => {
  1936. const fromExpo = from * from;
  1937. const expo = v * (to * to - fromExpo) + fromExpo;
  1938. return expo < 0 ? 0 : Math.sqrt(expo);
  1939. };
  1940. const colorTypes = [hex, rgba, hsla];
  1941. const getColorType = (v) => colorTypes.find((type) => type.test(v));
  1942. function asRGBA(color) {
  1943. const type = getColorType(color);
  1944. motionUtils.warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
  1945. if (!Boolean(type))
  1946. return false;
  1947. let model = type.parse(color);
  1948. if (type === hsla) {
  1949. // TODO Remove this cast - needed since Motion's stricter typing
  1950. model = hslaToRgba(model);
  1951. }
  1952. return model;
  1953. }
  1954. const mixColor = (from, to) => {
  1955. const fromRGBA = asRGBA(from);
  1956. const toRGBA = asRGBA(to);
  1957. if (!fromRGBA || !toRGBA) {
  1958. return mixImmediate(from, to);
  1959. }
  1960. const blended = { ...fromRGBA };
  1961. return (v) => {
  1962. blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
  1963. blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
  1964. blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
  1965. blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v);
  1966. return rgba.transform(blended);
  1967. };
  1968. };
  1969. /**
  1970. * Pipe
  1971. * Compose other transformers to run linearily
  1972. * pipe(min(20), max(40))
  1973. * @param {...functions} transformers
  1974. * @return {function}
  1975. */
  1976. const combineFunctions = (a, b) => (v) => b(a(v));
  1977. const pipe = (...transformers) => transformers.reduce(combineFunctions);
  1978. const invisibleValues = new Set(["none", "hidden"]);
  1979. /**
  1980. * Returns a function that, when provided a progress value between 0 and 1,
  1981. * will return the "none" or "hidden" string only when the progress is that of
  1982. * the origin or target.
  1983. */
  1984. function mixVisibility(origin, target) {
  1985. if (invisibleValues.has(origin)) {
  1986. return (p) => (p <= 0 ? origin : target);
  1987. }
  1988. else {
  1989. return (p) => (p >= 1 ? target : origin);
  1990. }
  1991. }
  1992. function mixNumber(a, b) {
  1993. return (p) => mixNumber$1(a, b, p);
  1994. }
  1995. function getMixer$1(a) {
  1996. if (typeof a === "number") {
  1997. return mixNumber;
  1998. }
  1999. else if (typeof a === "string") {
  2000. return isCSSVariableToken(a)
  2001. ? mixImmediate
  2002. : color.test(a)
  2003. ? mixColor
  2004. : mixComplex;
  2005. }
  2006. else if (Array.isArray(a)) {
  2007. return mixArray;
  2008. }
  2009. else if (typeof a === "object") {
  2010. return color.test(a) ? mixColor : mixObject;
  2011. }
  2012. return mixImmediate;
  2013. }
  2014. function mixArray(a, b) {
  2015. const output = [...a];
  2016. const numValues = output.length;
  2017. const blendValue = a.map((v, i) => getMixer$1(v)(v, b[i]));
  2018. return (p) => {
  2019. for (let i = 0; i < numValues; i++) {
  2020. output[i] = blendValue[i](p);
  2021. }
  2022. return output;
  2023. };
  2024. }
  2025. function mixObject(a, b) {
  2026. const output = { ...a, ...b };
  2027. const blendValue = {};
  2028. for (const key in output) {
  2029. if (a[key] !== undefined && b[key] !== undefined) {
  2030. blendValue[key] = getMixer$1(a[key])(a[key], b[key]);
  2031. }
  2032. }
  2033. return (v) => {
  2034. for (const key in blendValue) {
  2035. output[key] = blendValue[key](v);
  2036. }
  2037. return output;
  2038. };
  2039. }
  2040. function matchOrder(origin, target) {
  2041. const orderedOrigin = [];
  2042. const pointers = { color: 0, var: 0, number: 0 };
  2043. for (let i = 0; i < target.values.length; i++) {
  2044. const type = target.types[i];
  2045. const originIndex = origin.indexes[type][pointers[type]];
  2046. const originValue = origin.values[originIndex] ?? 0;
  2047. orderedOrigin[i] = originValue;
  2048. pointers[type]++;
  2049. }
  2050. return orderedOrigin;
  2051. }
  2052. const mixComplex = (origin, target) => {
  2053. const template = complex.createTransformer(target);
  2054. const originStats = analyseComplexValue(origin);
  2055. const targetStats = analyseComplexValue(target);
  2056. const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length &&
  2057. originStats.indexes.color.length === targetStats.indexes.color.length &&
  2058. originStats.indexes.number.length >= targetStats.indexes.number.length;
  2059. if (canInterpolate) {
  2060. if ((invisibleValues.has(origin) &&
  2061. !targetStats.values.length) ||
  2062. (invisibleValues.has(target) &&
  2063. !originStats.values.length)) {
  2064. return mixVisibility(origin, target);
  2065. }
  2066. return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template);
  2067. }
  2068. else {
  2069. 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.`);
  2070. return mixImmediate(origin, target);
  2071. }
  2072. };
  2073. function mix(from, to, p) {
  2074. if (typeof from === "number" &&
  2075. typeof to === "number" &&
  2076. typeof p === "number") {
  2077. return mixNumber$1(from, to, p);
  2078. }
  2079. const mixer = getMixer$1(from);
  2080. return mixer(from, to);
  2081. }
  2082. function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
  2083. const origin = keyframes[0];
  2084. const state = {
  2085. done: false,
  2086. value: origin,
  2087. };
  2088. const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
  2089. const nearestBoundary = (v) => {
  2090. if (min === undefined)
  2091. return max;
  2092. if (max === undefined)
  2093. return min;
  2094. return Math.abs(min - v) < Math.abs(max - v) ? min : max;
  2095. };
  2096. let amplitude = power * velocity;
  2097. const ideal = origin + amplitude;
  2098. const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
  2099. /**
  2100. * If the target has changed we need to re-calculate the amplitude, otherwise
  2101. * the animation will start from the wrong position.
  2102. */
  2103. if (target !== ideal)
  2104. amplitude = target - origin;
  2105. const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
  2106. const calcLatest = (t) => target + calcDelta(t);
  2107. const applyFriction = (t) => {
  2108. const delta = calcDelta(t);
  2109. const latest = calcLatest(t);
  2110. state.done = Math.abs(delta) <= restDelta;
  2111. state.value = state.done ? target : latest;
  2112. };
  2113. /**
  2114. * Ideally this would resolve for t in a stateless way, we could
  2115. * do that by always precalculating the animation but as we know
  2116. * this will be done anyway we can assume that spring will
  2117. * be discovered during that.
  2118. */
  2119. let timeReachedBoundary;
  2120. let spring$1;
  2121. const checkCatchBoundary = (t) => {
  2122. if (!isOutOfBounds(state.value))
  2123. return;
  2124. timeReachedBoundary = t;
  2125. spring$1 = spring({
  2126. keyframes: [state.value, nearestBoundary(state.value)],
  2127. velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
  2128. damping: bounceDamping,
  2129. stiffness: bounceStiffness,
  2130. restDelta,
  2131. restSpeed,
  2132. });
  2133. };
  2134. checkCatchBoundary(0);
  2135. return {
  2136. calculatedDuration: null,
  2137. next: (t) => {
  2138. /**
  2139. * We need to resolve the friction to figure out if we need a
  2140. * spring but we don't want to do this twice per frame. So here
  2141. * we flag if we updated for this frame and later if we did
  2142. * we can skip doing it again.
  2143. */
  2144. let hasUpdatedFrame = false;
  2145. if (!spring$1 && timeReachedBoundary === undefined) {
  2146. hasUpdatedFrame = true;
  2147. applyFriction(t);
  2148. checkCatchBoundary(t);
  2149. }
  2150. /**
  2151. * If we have a spring and the provided t is beyond the moment the friction
  2152. * animation crossed the min/max boundary, use the spring.
  2153. */
  2154. if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
  2155. return spring$1.next(t - timeReachedBoundary);
  2156. }
  2157. else {
  2158. !hasUpdatedFrame && applyFriction(t);
  2159. return state;
  2160. }
  2161. },
  2162. };
  2163. }
  2164. const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
  2165. const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
  2166. const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
  2167. const easingLookup = {
  2168. linear: motionUtils.noop,
  2169. easeIn,
  2170. easeInOut,
  2171. easeOut,
  2172. circIn,
  2173. circInOut,
  2174. circOut,
  2175. backIn,
  2176. backInOut,
  2177. backOut,
  2178. anticipate,
  2179. };
  2180. const easingDefinitionToFunction = (definition) => {
  2181. if (motionDom.isBezierDefinition(definition)) {
  2182. // If cubic bezier definition, create bezier curve
  2183. motionUtils.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
  2184. const [x1, y1, x2, y2] = definition;
  2185. return cubicBezier(x1, y1, x2, y2);
  2186. }
  2187. else if (typeof definition === "string") {
  2188. // Else lookup from table
  2189. motionUtils.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
  2190. return easingLookup[definition];
  2191. }
  2192. return definition;
  2193. };
  2194. function createMixers(output, ease, customMixer) {
  2195. const mixers = [];
  2196. const mixerFactory = customMixer || mix;
  2197. const numMixers = output.length - 1;
  2198. for (let i = 0; i < numMixers; i++) {
  2199. let mixer = mixerFactory(output[i], output[i + 1]);
  2200. if (ease) {
  2201. const easingFunction = Array.isArray(ease) ? ease[i] || motionUtils.noop : ease;
  2202. mixer = pipe(easingFunction, mixer);
  2203. }
  2204. mixers.push(mixer);
  2205. }
  2206. return mixers;
  2207. }
  2208. /**
  2209. * Create a function that maps from a numerical input array to a generic output array.
  2210. *
  2211. * Accepts:
  2212. * - Numbers
  2213. * - Colors (hex, hsl, hsla, rgb, rgba)
  2214. * - Complex (combinations of one or more numbers or strings)
  2215. *
  2216. * ```jsx
  2217. * const mixColor = interpolate([0, 1], ['#fff', '#000'])
  2218. *
  2219. * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
  2220. * ```
  2221. *
  2222. * TODO Revist this approach once we've moved to data models for values,
  2223. * probably not needed to pregenerate mixer functions.
  2224. *
  2225. * @public
  2226. */
  2227. function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
  2228. const inputLength = input.length;
  2229. motionUtils.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
  2230. /**
  2231. * If we're only provided a single input, we can just make a function
  2232. * that returns the output.
  2233. */
  2234. if (inputLength === 1)
  2235. return () => output[0];
  2236. if (inputLength === 2 && output[0] === output[1])
  2237. return () => output[1];
  2238. const isZeroDeltaRange = input[0] === input[1];
  2239. // If input runs highest -> lowest, reverse both arrays
  2240. if (input[0] > input[inputLength - 1]) {
  2241. input = [...input].reverse();
  2242. output = [...output].reverse();
  2243. }
  2244. const mixers = createMixers(output, ease, mixer);
  2245. const numMixers = mixers.length;
  2246. const interpolator = (v) => {
  2247. if (isZeroDeltaRange && v < input[0])
  2248. return output[0];
  2249. let i = 0;
  2250. if (numMixers > 1) {
  2251. for (; i < input.length - 2; i++) {
  2252. if (v < input[i + 1])
  2253. break;
  2254. }
  2255. }
  2256. const progressInRange = motionUtils.progress(input[i], input[i + 1], v);
  2257. return mixers[i](progressInRange);
  2258. };
  2259. return isClamp
  2260. ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
  2261. : interpolator;
  2262. }
  2263. function convertOffsetToTimes(offset, duration) {
  2264. return offset.map((o) => o * duration);
  2265. }
  2266. function defaultEasing(values, easing) {
  2267. return values.map(() => easing || easeInOut).splice(0, values.length - 1);
  2268. }
  2269. function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
  2270. /**
  2271. * Easing functions can be externally defined as strings. Here we convert them
  2272. * into actual functions.
  2273. */
  2274. const easingFunctions = isEasingArray(ease)
  2275. ? ease.map(easingDefinitionToFunction)
  2276. : easingDefinitionToFunction(ease);
  2277. /**
  2278. * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
  2279. * to reduce GC during animation.
  2280. */
  2281. const state = {
  2282. done: false,
  2283. value: keyframeValues[0],
  2284. };
  2285. /**
  2286. * Create a times array based on the provided 0-1 offsets
  2287. */
  2288. const absoluteTimes = convertOffsetToTimes(
  2289. // Only use the provided offsets if they're the correct length
  2290. // TODO Maybe we should warn here if there's a length mismatch
  2291. times && times.length === keyframeValues.length
  2292. ? times
  2293. : defaultOffset$1(keyframeValues), duration);
  2294. const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
  2295. ease: Array.isArray(easingFunctions)
  2296. ? easingFunctions
  2297. : defaultEasing(keyframeValues, easingFunctions),
  2298. });
  2299. return {
  2300. calculatedDuration: duration,
  2301. next: (t) => {
  2302. state.value = mapTimeToKeyframe(t);
  2303. state.done = t >= duration;
  2304. return state;
  2305. },
  2306. };
  2307. }
  2308. const frameloopDriver = (update) => {
  2309. const passTimestamp = ({ timestamp }) => update(timestamp);
  2310. return {
  2311. start: () => motionDom.frame.update(passTimestamp, true),
  2312. stop: () => motionDom.cancelFrame(passTimestamp),
  2313. /**
  2314. * If we're processing this frame we can use the
  2315. * framelocked timestamp to keep things in sync.
  2316. */
  2317. now: () => (motionDom.frameData.isProcessing ? motionDom.frameData.timestamp : motionDom.time.now()),
  2318. };
  2319. };
  2320. const generators = {
  2321. decay: inertia,
  2322. inertia,
  2323. tween: keyframes,
  2324. keyframes: keyframes,
  2325. spring,
  2326. };
  2327. const percentToProgress = (percent) => percent / 100;
  2328. /**
  2329. * Animation that runs on the main thread. Designed to be WAAPI-spec in the subset of
  2330. * features we expose publically. Mostly the compatibility is to ensure visual identity
  2331. * between both WAAPI and main thread animations.
  2332. */
  2333. class MainThreadAnimation extends BaseAnimation {
  2334. constructor(options) {
  2335. super(options);
  2336. /**
  2337. * The time at which the animation was paused.
  2338. */
  2339. this.holdTime = null;
  2340. /**
  2341. * The time at which the animation was cancelled.
  2342. */
  2343. this.cancelTime = null;
  2344. /**
  2345. * The current time of the animation.
  2346. */
  2347. this.currentTime = 0;
  2348. /**
  2349. * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
  2350. */
  2351. this.playbackSpeed = 1;
  2352. /**
  2353. * The state of the animation to apply when the animation is resolved. This
  2354. * allows calls to the public API to control the animation before it is resolved,
  2355. * without us having to resolve it first.
  2356. */
  2357. this.pendingPlayState = "running";
  2358. /**
  2359. * The time at which the animation was started.
  2360. */
  2361. this.startTime = null;
  2362. this.state = "idle";
  2363. /**
  2364. * This method is bound to the instance to fix a pattern where
  2365. * animation.stop is returned as a reference from a useEffect.
  2366. */
  2367. this.stop = () => {
  2368. this.resolver.cancel();
  2369. this.isStopped = true;
  2370. if (this.state === "idle")
  2371. return;
  2372. this.teardown();
  2373. const { onStop } = this.options;
  2374. onStop && onStop();
  2375. };
  2376. const { name, motionValue, element, keyframes } = this.options;
  2377. const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
  2378. const onResolved = (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe);
  2379. this.resolver = new KeyframeResolver$1(keyframes, onResolved, name, motionValue, element);
  2380. this.resolver.scheduleResolve();
  2381. }
  2382. flatten() {
  2383. super.flatten();
  2384. // If we've already resolved the animation, re-initialise it
  2385. if (this._resolved) {
  2386. Object.assign(this._resolved, this.initPlayback(this._resolved.keyframes));
  2387. }
  2388. }
  2389. initPlayback(keyframes$1) {
  2390. const { type = "keyframes", repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = this.options;
  2391. const generatorFactory = motionDom.isGenerator(type)
  2392. ? type
  2393. : generators[type] || keyframes;
  2394. /**
  2395. * If our generator doesn't support mixing numbers, we need to replace keyframes with
  2396. * [0, 100] and then make a function that maps that to the actual keyframes.
  2397. *
  2398. * 100 is chosen instead of 1 as it works nicer with spring animations.
  2399. */
  2400. let mapPercentToKeyframes;
  2401. let mirroredGenerator;
  2402. if (process.env.NODE_ENV !== "production" &&
  2403. generatorFactory !== keyframes) {
  2404. motionUtils.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
  2405. }
  2406. if (generatorFactory !== keyframes &&
  2407. typeof keyframes$1[0] !== "number") {
  2408. mapPercentToKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
  2409. keyframes$1 = [0, 100];
  2410. }
  2411. const generator = generatorFactory({ ...this.options, keyframes: keyframes$1 });
  2412. /**
  2413. * If we have a mirror repeat type we need to create a second generator that outputs the
  2414. * mirrored (not reversed) animation and later ping pong between the two generators.
  2415. */
  2416. if (repeatType === "mirror") {
  2417. mirroredGenerator = generatorFactory({
  2418. ...this.options,
  2419. keyframes: [...keyframes$1].reverse(),
  2420. velocity: -velocity,
  2421. });
  2422. }
  2423. /**
  2424. * If duration is undefined and we have repeat options,
  2425. * we need to calculate a duration from the generator.
  2426. *
  2427. * We set it to the generator itself to cache the duration.
  2428. * Any timeline resolver will need to have already precalculated
  2429. * the duration by this step.
  2430. */
  2431. if (generator.calculatedDuration === null) {
  2432. generator.calculatedDuration = motionDom.calcGeneratorDuration(generator);
  2433. }
  2434. const { calculatedDuration } = generator;
  2435. const resolvedDuration = calculatedDuration + repeatDelay;
  2436. const totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
  2437. return {
  2438. generator,
  2439. mirroredGenerator,
  2440. mapPercentToKeyframes,
  2441. calculatedDuration,
  2442. resolvedDuration,
  2443. totalDuration,
  2444. };
  2445. }
  2446. onPostResolved() {
  2447. const { autoplay = true } = this.options;
  2448. motionDom.activeAnimations.mainThread++;
  2449. this.play();
  2450. if (this.pendingPlayState === "paused" || !autoplay) {
  2451. this.pause();
  2452. }
  2453. else {
  2454. this.state = this.pendingPlayState;
  2455. }
  2456. }
  2457. tick(timestamp, sample = false) {
  2458. const { resolved } = this;
  2459. // If the animations has failed to resolve, return the final keyframe.
  2460. if (!resolved) {
  2461. const { keyframes } = this.options;
  2462. return { done: true, value: keyframes[keyframes.length - 1] };
  2463. }
  2464. const { finalKeyframe, generator, mirroredGenerator, mapPercentToKeyframes, keyframes, calculatedDuration, totalDuration, resolvedDuration, } = resolved;
  2465. if (this.startTime === null)
  2466. return generator.next(0);
  2467. const { delay, repeat, repeatType, repeatDelay, onUpdate } = this.options;
  2468. /**
  2469. * requestAnimationFrame timestamps can come through as lower than
  2470. * the startTime as set by performance.now(). Here we prevent this,
  2471. * though in the future it could be possible to make setting startTime
  2472. * a pending operation that gets resolved here.
  2473. */
  2474. if (this.speed > 0) {
  2475. this.startTime = Math.min(this.startTime, timestamp);
  2476. }
  2477. else if (this.speed < 0) {
  2478. this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
  2479. }
  2480. // Update currentTime
  2481. if (sample) {
  2482. this.currentTime = timestamp;
  2483. }
  2484. else if (this.holdTime !== null) {
  2485. this.currentTime = this.holdTime;
  2486. }
  2487. else {
  2488. // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
  2489. // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
  2490. // example.
  2491. this.currentTime =
  2492. Math.round(timestamp - this.startTime) * this.speed;
  2493. }
  2494. // Rebase on delay
  2495. const timeWithoutDelay = this.currentTime - delay * (this.speed >= 0 ? 1 : -1);
  2496. const isInDelayPhase = this.speed >= 0
  2497. ? timeWithoutDelay < 0
  2498. : timeWithoutDelay > totalDuration;
  2499. this.currentTime = Math.max(timeWithoutDelay, 0);
  2500. // If this animation has finished, set the current time to the total duration.
  2501. if (this.state === "finished" && this.holdTime === null) {
  2502. this.currentTime = totalDuration;
  2503. }
  2504. let elapsed = this.currentTime;
  2505. let frameGenerator = generator;
  2506. if (repeat) {
  2507. /**
  2508. * Get the current progress (0-1) of the animation. If t is >
  2509. * than duration we'll get values like 2.5 (midway through the
  2510. * third iteration)
  2511. */
  2512. const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
  2513. /**
  2514. * Get the current iteration (0 indexed). For instance the floor of
  2515. * 2.5 is 2.
  2516. */
  2517. let currentIteration = Math.floor(progress);
  2518. /**
  2519. * Get the current progress of the iteration by taking the remainder
  2520. * so 2.5 is 0.5 through iteration 2
  2521. */
  2522. let iterationProgress = progress % 1.0;
  2523. /**
  2524. * If iteration progress is 1 we count that as the end
  2525. * of the previous iteration.
  2526. */
  2527. if (!iterationProgress && progress >= 1) {
  2528. iterationProgress = 1;
  2529. }
  2530. iterationProgress === 1 && currentIteration--;
  2531. currentIteration = Math.min(currentIteration, repeat + 1);
  2532. /**
  2533. * Reverse progress if we're not running in "normal" direction
  2534. */
  2535. const isOddIteration = Boolean(currentIteration % 2);
  2536. if (isOddIteration) {
  2537. if (repeatType === "reverse") {
  2538. iterationProgress = 1 - iterationProgress;
  2539. if (repeatDelay) {
  2540. iterationProgress -= repeatDelay / resolvedDuration;
  2541. }
  2542. }
  2543. else if (repeatType === "mirror") {
  2544. frameGenerator = mirroredGenerator;
  2545. }
  2546. }
  2547. elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
  2548. }
  2549. /**
  2550. * If we're in negative time, set state as the initial keyframe.
  2551. * This prevents delay: x, duration: 0 animations from finishing
  2552. * instantly.
  2553. */
  2554. const state = isInDelayPhase
  2555. ? { done: false, value: keyframes[0] }
  2556. : frameGenerator.next(elapsed);
  2557. if (mapPercentToKeyframes) {
  2558. state.value = mapPercentToKeyframes(state.value);
  2559. }
  2560. let { done } = state;
  2561. if (!isInDelayPhase && calculatedDuration !== null) {
  2562. done =
  2563. this.speed >= 0
  2564. ? this.currentTime >= totalDuration
  2565. : this.currentTime <= 0;
  2566. }
  2567. const isAnimationFinished = this.holdTime === null &&
  2568. (this.state === "finished" || (this.state === "running" && done));
  2569. if (isAnimationFinished && finalKeyframe !== undefined) {
  2570. state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe);
  2571. }
  2572. if (onUpdate) {
  2573. onUpdate(state.value);
  2574. }
  2575. if (isAnimationFinished) {
  2576. this.finish();
  2577. }
  2578. return state;
  2579. }
  2580. get duration() {
  2581. const { resolved } = this;
  2582. return resolved ? motionUtils.millisecondsToSeconds(resolved.calculatedDuration) : 0;
  2583. }
  2584. get time() {
  2585. return motionUtils.millisecondsToSeconds(this.currentTime);
  2586. }
  2587. set time(newTime) {
  2588. newTime = motionUtils.secondsToMilliseconds(newTime);
  2589. this.currentTime = newTime;
  2590. if (this.holdTime !== null || this.speed === 0) {
  2591. this.holdTime = newTime;
  2592. }
  2593. else if (this.driver) {
  2594. this.startTime = this.driver.now() - newTime / this.speed;
  2595. }
  2596. }
  2597. get speed() {
  2598. return this.playbackSpeed;
  2599. }
  2600. set speed(newSpeed) {
  2601. const hasChanged = this.playbackSpeed !== newSpeed;
  2602. this.playbackSpeed = newSpeed;
  2603. if (hasChanged) {
  2604. this.time = motionUtils.millisecondsToSeconds(this.currentTime);
  2605. }
  2606. }
  2607. play() {
  2608. if (!this.resolver.isScheduled) {
  2609. this.resolver.resume();
  2610. }
  2611. if (!this._resolved) {
  2612. this.pendingPlayState = "running";
  2613. return;
  2614. }
  2615. if (this.isStopped)
  2616. return;
  2617. const { driver = frameloopDriver, onPlay, startTime } = this.options;
  2618. if (!this.driver) {
  2619. this.driver = driver((timestamp) => this.tick(timestamp));
  2620. }
  2621. onPlay && onPlay();
  2622. const now = this.driver.now();
  2623. if (this.holdTime !== null) {
  2624. this.startTime = now - this.holdTime;
  2625. }
  2626. else if (!this.startTime) {
  2627. this.startTime = startTime ?? this.calcStartTime();
  2628. }
  2629. else if (this.state === "finished") {
  2630. this.startTime = now;
  2631. }
  2632. if (this.state === "finished") {
  2633. this.updateFinishedPromise();
  2634. }
  2635. this.cancelTime = this.startTime;
  2636. this.holdTime = null;
  2637. /**
  2638. * Set playState to running only after we've used it in
  2639. * the previous logic.
  2640. */
  2641. this.state = "running";
  2642. this.driver.start();
  2643. }
  2644. pause() {
  2645. if (!this._resolved) {
  2646. this.pendingPlayState = "paused";
  2647. return;
  2648. }
  2649. this.state = "paused";
  2650. this.holdTime = this.currentTime ?? 0;
  2651. }
  2652. complete() {
  2653. if (this.state !== "running") {
  2654. this.play();
  2655. }
  2656. this.pendingPlayState = this.state = "finished";
  2657. this.holdTime = null;
  2658. }
  2659. finish() {
  2660. this.teardown();
  2661. this.state = "finished";
  2662. const { onComplete } = this.options;
  2663. onComplete && onComplete();
  2664. }
  2665. cancel() {
  2666. if (this.cancelTime !== null) {
  2667. this.tick(this.cancelTime);
  2668. }
  2669. this.teardown();
  2670. this.updateFinishedPromise();
  2671. }
  2672. teardown() {
  2673. this.state = "idle";
  2674. this.stopDriver();
  2675. this.resolveFinishedPromise();
  2676. this.updateFinishedPromise();
  2677. this.startTime = this.cancelTime = null;
  2678. this.resolver.cancel();
  2679. motionDom.activeAnimations.mainThread--;
  2680. }
  2681. stopDriver() {
  2682. if (!this.driver)
  2683. return;
  2684. this.driver.stop();
  2685. this.driver = undefined;
  2686. }
  2687. sample(time) {
  2688. this.startTime = 0;
  2689. return this.tick(time, true);
  2690. }
  2691. get finished() {
  2692. return this.currentFinishedPromise;
  2693. }
  2694. }
  2695. /**
  2696. * A list of values that can be hardware-accelerated.
  2697. */
  2698. const acceleratedValues = new Set([
  2699. "opacity",
  2700. "clipPath",
  2701. "filter",
  2702. "transform",
  2703. // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
  2704. // or until we implement support for linear() easing.
  2705. // "background-color"
  2706. ]);
  2707. const supportsWaapi = /*@__PURE__*/ motionUtils.memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
  2708. /**
  2709. * 10ms is chosen here as it strikes a balance between smooth
  2710. * results (more than one keyframe per frame at 60fps) and
  2711. * keyframe quantity.
  2712. */
  2713. const sampleDelta = 10; //ms
  2714. /**
  2715. * Implement a practical max duration for keyframe generation
  2716. * to prevent infinite loops
  2717. */
  2718. const maxDuration = 20000;
  2719. /**
  2720. * Check if an animation can run natively via WAAPI or requires pregenerated keyframes.
  2721. * WAAPI doesn't support spring or function easings so we run these as JS animation before
  2722. * handing off.
  2723. */
  2724. function requiresPregeneratedKeyframes(options) {
  2725. return (motionDom.isGenerator(options.type) ||
  2726. options.type === "spring" ||
  2727. !motionDom.isWaapiSupportedEasing(options.ease));
  2728. }
  2729. function pregenerateKeyframes(keyframes, options) {
  2730. /**
  2731. * Create a main-thread animation to pregenerate keyframes.
  2732. * We sample this at regular intervals to generate keyframes that we then
  2733. * linearly interpolate between.
  2734. */
  2735. const sampleAnimation = new MainThreadAnimation({
  2736. ...options,
  2737. keyframes,
  2738. repeat: 0,
  2739. delay: 0,
  2740. isGenerator: true,
  2741. });
  2742. let state = { done: false, value: keyframes[0] };
  2743. const pregeneratedKeyframes = [];
  2744. /**
  2745. * Bail after 20 seconds of pre-generated keyframes as it's likely
  2746. * we're heading for an infinite loop.
  2747. */
  2748. let t = 0;
  2749. while (!state.done && t < maxDuration) {
  2750. state = sampleAnimation.sample(t);
  2751. pregeneratedKeyframes.push(state.value);
  2752. t += sampleDelta;
  2753. }
  2754. return {
  2755. times: undefined,
  2756. keyframes: pregeneratedKeyframes,
  2757. duration: t - sampleDelta,
  2758. ease: "linear",
  2759. };
  2760. }
  2761. const unsupportedEasingFunctions = {
  2762. anticipate,
  2763. backInOut,
  2764. circInOut,
  2765. };
  2766. function isUnsupportedEase(key) {
  2767. return key in unsupportedEasingFunctions;
  2768. }
  2769. class AcceleratedAnimation extends BaseAnimation {
  2770. constructor(options) {
  2771. super(options);
  2772. const { name, motionValue, element, keyframes } = this.options;
  2773. this.resolver = new DOMKeyframesResolver(keyframes, (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe), name, motionValue, element);
  2774. this.resolver.scheduleResolve();
  2775. }
  2776. initPlayback(keyframes, finalKeyframe) {
  2777. let { duration = 300, times, ease, type, motionValue, name, startTime, } = this.options;
  2778. /**
  2779. * If element has since been unmounted, return false to indicate
  2780. * the animation failed to initialised.
  2781. */
  2782. if (!motionValue.owner || !motionValue.owner.current) {
  2783. return false;
  2784. }
  2785. /**
  2786. * If the user has provided an easing function name that isn't supported
  2787. * by WAAPI (like "anticipate"), we need to provide the corressponding
  2788. * function. This will later get converted to a linear() easing function.
  2789. */
  2790. if (typeof ease === "string" &&
  2791. motionDom.supportsLinearEasing() &&
  2792. isUnsupportedEase(ease)) {
  2793. ease = unsupportedEasingFunctions[ease];
  2794. }
  2795. /**
  2796. * If this animation needs pre-generated keyframes then generate.
  2797. */
  2798. if (requiresPregeneratedKeyframes(this.options)) {
  2799. const { onComplete, onUpdate, motionValue, element, ...options } = this.options;
  2800. const pregeneratedAnimation = pregenerateKeyframes(keyframes, options);
  2801. keyframes = pregeneratedAnimation.keyframes;
  2802. // If this is a very short animation, ensure we have
  2803. // at least two keyframes to animate between as older browsers
  2804. // can't animate between a single keyframe.
  2805. if (keyframes.length === 1) {
  2806. keyframes[1] = keyframes[0];
  2807. }
  2808. duration = pregeneratedAnimation.duration;
  2809. times = pregeneratedAnimation.times;
  2810. ease = pregeneratedAnimation.ease;
  2811. type = "keyframes";
  2812. }
  2813. const animation = motionDom.startWaapiAnimation(motionValue.owner.current, name, keyframes, { ...this.options, duration, times, ease });
  2814. // Override the browser calculated startTime with one synchronised to other JS
  2815. // and WAAPI animations starting this event loop.
  2816. animation.startTime = startTime ?? this.calcStartTime();
  2817. if (this.pendingTimeline) {
  2818. motionDom.attachTimeline(animation, this.pendingTimeline);
  2819. this.pendingTimeline = undefined;
  2820. }
  2821. else {
  2822. /**
  2823. * Prefer the `onfinish` prop as it's more widely supported than
  2824. * the `finished` promise.
  2825. *
  2826. * Here, we synchronously set the provided MotionValue to the end
  2827. * keyframe. If we didn't, when the WAAPI animation is finished it would
  2828. * be removed from the element which would then revert to its old styles.
  2829. */
  2830. animation.onfinish = () => {
  2831. const { onComplete } = this.options;
  2832. motionValue.set(getFinalKeyframe(keyframes, this.options, finalKeyframe));
  2833. onComplete && onComplete();
  2834. this.cancel();
  2835. this.resolveFinishedPromise();
  2836. };
  2837. }
  2838. return {
  2839. animation,
  2840. duration,
  2841. times,
  2842. type,
  2843. ease,
  2844. keyframes: keyframes,
  2845. };
  2846. }
  2847. get duration() {
  2848. const { resolved } = this;
  2849. if (!resolved)
  2850. return 0;
  2851. const { duration } = resolved;
  2852. return motionUtils.millisecondsToSeconds(duration);
  2853. }
  2854. get time() {
  2855. const { resolved } = this;
  2856. if (!resolved)
  2857. return 0;
  2858. const { animation } = resolved;
  2859. return motionUtils.millisecondsToSeconds(animation.currentTime || 0);
  2860. }
  2861. set time(newTime) {
  2862. const { resolved } = this;
  2863. if (!resolved)
  2864. return;
  2865. const { animation } = resolved;
  2866. animation.currentTime = motionUtils.secondsToMilliseconds(newTime);
  2867. }
  2868. get speed() {
  2869. const { resolved } = this;
  2870. if (!resolved)
  2871. return 1;
  2872. const { animation } = resolved;
  2873. return animation.playbackRate;
  2874. }
  2875. get finished() {
  2876. return this.resolved.animation.finished;
  2877. }
  2878. set speed(newSpeed) {
  2879. const { resolved } = this;
  2880. if (!resolved)
  2881. return;
  2882. const { animation } = resolved;
  2883. animation.playbackRate = newSpeed;
  2884. }
  2885. get state() {
  2886. const { resolved } = this;
  2887. if (!resolved)
  2888. return "idle";
  2889. const { animation } = resolved;
  2890. return animation.playState;
  2891. }
  2892. get startTime() {
  2893. const { resolved } = this;
  2894. if (!resolved)
  2895. return null;
  2896. const { animation } = resolved;
  2897. // Coerce to number as TypeScript incorrectly types this
  2898. // as CSSNumberish
  2899. return animation.startTime;
  2900. }
  2901. /**
  2902. * Replace the default DocumentTimeline with another AnimationTimeline.
  2903. * Currently used for scroll animations.
  2904. */
  2905. attachTimeline(timeline) {
  2906. if (!this._resolved) {
  2907. this.pendingTimeline = timeline;
  2908. }
  2909. else {
  2910. const { resolved } = this;
  2911. if (!resolved)
  2912. return motionUtils.noop;
  2913. const { animation } = resolved;
  2914. motionDom.attachTimeline(animation, timeline);
  2915. }
  2916. return motionUtils.noop;
  2917. }
  2918. play() {
  2919. if (this.isStopped)
  2920. return;
  2921. const { resolved } = this;
  2922. if (!resolved)
  2923. return;
  2924. const { animation } = resolved;
  2925. if (animation.playState === "finished") {
  2926. this.updateFinishedPromise();
  2927. }
  2928. animation.play();
  2929. }
  2930. pause() {
  2931. const { resolved } = this;
  2932. if (!resolved)
  2933. return;
  2934. const { animation } = resolved;
  2935. animation.pause();
  2936. }
  2937. stop() {
  2938. this.resolver.cancel();
  2939. this.isStopped = true;
  2940. if (this.state === "idle")
  2941. return;
  2942. this.resolveFinishedPromise();
  2943. this.updateFinishedPromise();
  2944. const { resolved } = this;
  2945. if (!resolved)
  2946. return;
  2947. const { animation, keyframes, duration, type, ease, times } = resolved;
  2948. if (animation.playState === "idle" ||
  2949. animation.playState === "finished") {
  2950. return;
  2951. }
  2952. /**
  2953. * WAAPI doesn't natively have any interruption capabilities.
  2954. *
  2955. * Rather than read commited styles back out of the DOM, we can
  2956. * create a renderless JS animation and sample it twice to calculate
  2957. * its current value, "previous" value, and therefore allow
  2958. * Motion to calculate velocity for any subsequent animation.
  2959. */
  2960. if (this.time) {
  2961. const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
  2962. const sampleAnimation = new MainThreadAnimation({
  2963. ...options,
  2964. keyframes,
  2965. duration,
  2966. type,
  2967. ease,
  2968. times,
  2969. isGenerator: true,
  2970. });
  2971. const sampleTime = motionUtils.secondsToMilliseconds(this.time);
  2972. motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
  2973. }
  2974. const { onStop } = this.options;
  2975. onStop && onStop();
  2976. this.cancel();
  2977. }
  2978. complete() {
  2979. const { resolved } = this;
  2980. if (!resolved)
  2981. return;
  2982. resolved.animation.finish();
  2983. }
  2984. cancel() {
  2985. const { resolved } = this;
  2986. if (!resolved)
  2987. return;
  2988. resolved.animation.cancel();
  2989. }
  2990. static supports(options) {
  2991. const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
  2992. if (!motionValue ||
  2993. !motionValue.owner ||
  2994. !(motionValue.owner.current instanceof HTMLElement)) {
  2995. return false;
  2996. }
  2997. const { onUpdate, transformTemplate } = motionValue.owner.getProps();
  2998. return (supportsWaapi() &&
  2999. name &&
  3000. acceleratedValues.has(name) &&
  3001. (name !== "transform" || !transformTemplate) &&
  3002. /**
  3003. * If we're outputting values to onUpdate then we can't use WAAPI as there's
  3004. * no way to read the value from WAAPI every frame.
  3005. */
  3006. !onUpdate &&
  3007. !repeatDelay &&
  3008. repeatType !== "mirror" &&
  3009. damping !== 0 &&
  3010. type !== "inertia");
  3011. }
  3012. }
  3013. const underDampedSpring = {
  3014. type: "spring",
  3015. stiffness: 500,
  3016. damping: 25,
  3017. restSpeed: 10,
  3018. };
  3019. const criticallyDampedSpring = (target) => ({
  3020. type: "spring",
  3021. stiffness: 550,
  3022. damping: target === 0 ? 2 * Math.sqrt(550) : 30,
  3023. restSpeed: 10,
  3024. });
  3025. const keyframesTransition = {
  3026. type: "keyframes",
  3027. duration: 0.8,
  3028. };
  3029. /**
  3030. * Default easing curve is a slightly shallower version of
  3031. * the default browser easing curve.
  3032. */
  3033. const ease = {
  3034. type: "keyframes",
  3035. ease: [0.25, 0.1, 0.35, 1],
  3036. duration: 0.3,
  3037. };
  3038. const getDefaultTransition = (valueKey, { keyframes }) => {
  3039. if (keyframes.length > 2) {
  3040. return keyframesTransition;
  3041. }
  3042. else if (transformProps.has(valueKey)) {
  3043. return valueKey.startsWith("scale")
  3044. ? criticallyDampedSpring(keyframes[1])
  3045. : underDampedSpring;
  3046. }
  3047. return ease;
  3048. };
  3049. /**
  3050. * Decide whether a transition is defined on a given Transition.
  3051. * This filters out orchestration options and returns true
  3052. * if any options are left.
  3053. */
  3054. function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
  3055. return !!Object.keys(transition).length;
  3056. }
  3057. const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
  3058. const valueTransition = motionDom.getValueTransition(transition, name) || {};
  3059. /**
  3060. * Most transition values are currently completely overwritten by value-specific
  3061. * transitions. In the future it'd be nicer to blend these transitions. But for now
  3062. * delay actually does inherit from the root transition if not value-specific.
  3063. */
  3064. const delay = valueTransition.delay || transition.delay || 0;
  3065. /**
  3066. * Elapsed isn't a public transition option but can be passed through from
  3067. * optimized appear effects in milliseconds.
  3068. */
  3069. let { elapsed = 0 } = transition;
  3070. elapsed = elapsed - motionUtils.secondsToMilliseconds(delay);
  3071. let options = {
  3072. keyframes: Array.isArray(target) ? target : [null, target],
  3073. ease: "easeOut",
  3074. velocity: value.getVelocity(),
  3075. ...valueTransition,
  3076. delay: -elapsed,
  3077. onUpdate: (v) => {
  3078. value.set(v);
  3079. valueTransition.onUpdate && valueTransition.onUpdate(v);
  3080. },
  3081. onComplete: () => {
  3082. onComplete();
  3083. valueTransition.onComplete && valueTransition.onComplete();
  3084. },
  3085. name,
  3086. motionValue: value,
  3087. element: isHandoff ? undefined : element,
  3088. };
  3089. /**
  3090. * If there's no transition defined for this value, we can generate
  3091. * unique transition settings for this value.
  3092. */
  3093. if (!isTransitionDefined(valueTransition)) {
  3094. options = {
  3095. ...options,
  3096. ...getDefaultTransition(name, options),
  3097. };
  3098. }
  3099. /**
  3100. * Both WAAPI and our internal animation functions use durations
  3101. * as defined by milliseconds, while our external API defines them
  3102. * as seconds.
  3103. */
  3104. if (options.duration) {
  3105. options.duration = motionUtils.secondsToMilliseconds(options.duration);
  3106. }
  3107. if (options.repeatDelay) {
  3108. options.repeatDelay = motionUtils.secondsToMilliseconds(options.repeatDelay);
  3109. }
  3110. if (options.from !== undefined) {
  3111. options.keyframes[0] = options.from;
  3112. }
  3113. let shouldSkip = false;
  3114. if (options.type === false ||
  3115. (options.duration === 0 && !options.repeatDelay)) {
  3116. options.duration = 0;
  3117. if (options.delay === 0) {
  3118. shouldSkip = true;
  3119. }
  3120. }
  3121. if (motionUtils.MotionGlobalConfig.skipAnimations) {
  3122. shouldSkip = true;
  3123. options.duration = 0;
  3124. options.delay = 0;
  3125. }
  3126. /**
  3127. * If the transition type or easing has been explicitly set by the user
  3128. * then we don't want to allow flattening the animation.
  3129. */
  3130. options.allowFlatten = !valueTransition.type && !valueTransition.ease;
  3131. /**
  3132. * If we can or must skip creating the animation, and apply only
  3133. * the final keyframe, do so. We also check once keyframes are resolved but
  3134. * this early check prevents the need to create an animation at all.
  3135. */
  3136. if (shouldSkip && !isHandoff && value.get() !== undefined) {
  3137. const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
  3138. if (finalKeyframe !== undefined) {
  3139. motionDom.frame.update(() => {
  3140. options.onUpdate(finalKeyframe);
  3141. options.onComplete();
  3142. });
  3143. // We still want to return some animation controls here rather
  3144. // than returning undefined
  3145. return new motionDom.GroupAnimationWithThen([]);
  3146. }
  3147. }
  3148. /**
  3149. * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
  3150. * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
  3151. * optimised animation.
  3152. */
  3153. if (!isHandoff && AcceleratedAnimation.supports(options)) {
  3154. return new AcceleratedAnimation(options);
  3155. }
  3156. else {
  3157. return new MainThreadAnimation(options);
  3158. }
  3159. };
  3160. /**
  3161. * Decide whether we should block this animation. Previously, we achieved this
  3162. * just by checking whether the key was listed in protectedKeys, but this
  3163. * posed problems if an animation was triggered by afterChildren and protectedKeys
  3164. * had been set to true in the meantime.
  3165. */
  3166. function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
  3167. const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
  3168. needsAnimating[key] = false;
  3169. return shouldBlock;
  3170. }
  3171. function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) {
  3172. let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = targetAndTransition;
  3173. if (transitionOverride)
  3174. transition = transitionOverride;
  3175. const animations = [];
  3176. const animationTypeState = type &&
  3177. visualElement.animationState &&
  3178. visualElement.animationState.getState()[type];
  3179. for (const key in target) {
  3180. const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
  3181. const valueTarget = target[key];
  3182. if (valueTarget === undefined ||
  3183. (animationTypeState &&
  3184. shouldBlockAnimation(animationTypeState, key))) {
  3185. continue;
  3186. }
  3187. const valueTransition = {
  3188. delay,
  3189. ...motionDom.getValueTransition(transition || {}, key),
  3190. };
  3191. /**
  3192. * If this is the first time a value is being animated, check
  3193. * to see if we're handling off from an existing animation.
  3194. */
  3195. let isHandoff = false;
  3196. if (window.MotionHandoffAnimation) {
  3197. const appearId = getOptimisedAppearId(visualElement);
  3198. if (appearId) {
  3199. const startTime = window.MotionHandoffAnimation(appearId, key, motionDom.frame);
  3200. if (startTime !== null) {
  3201. valueTransition.startTime = startTime;
  3202. isHandoff = true;
  3203. }
  3204. }
  3205. }
  3206. addValueToWillChange(visualElement, key);
  3207. value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
  3208. ? { type: false }
  3209. : valueTransition, visualElement, isHandoff));
  3210. const animation = value.animation;
  3211. if (animation) {
  3212. animations.push(animation);
  3213. }
  3214. }
  3215. if (transitionEnd) {
  3216. Promise.all(animations).then(() => {
  3217. motionDom.frame.update(() => {
  3218. transitionEnd && setTarget(visualElement, transitionEnd);
  3219. });
  3220. });
  3221. }
  3222. return animations;
  3223. }
  3224. function isSVGElement(element) {
  3225. return element instanceof SVGElement && element.tagName !== "svg";
  3226. }
  3227. const createAxis = () => ({ min: 0, max: 0 });
  3228. const createBox = () => ({
  3229. x: createAxis(),
  3230. y: createAxis(),
  3231. });
  3232. const featureProps = {
  3233. animation: [
  3234. "animate",
  3235. "variants",
  3236. "whileHover",
  3237. "whileTap",
  3238. "exit",
  3239. "whileInView",
  3240. "whileFocus",
  3241. "whileDrag",
  3242. ],
  3243. exit: ["exit"],
  3244. drag: ["drag", "dragControls"],
  3245. focus: ["whileFocus"],
  3246. hover: ["whileHover", "onHoverStart", "onHoverEnd"],
  3247. tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
  3248. pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
  3249. inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
  3250. layout: ["layout", "layoutId"],
  3251. };
  3252. const featureDefinitions = {};
  3253. for (const key in featureProps) {
  3254. featureDefinitions[key] = {
  3255. isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
  3256. };
  3257. }
  3258. const isBrowser = typeof window !== "undefined";
  3259. // Does this device prefer reduced motion? Returns `null` server-side.
  3260. const prefersReducedMotion = { current: null };
  3261. const hasReducedMotionListener = { current: false };
  3262. function initPrefersReducedMotion() {
  3263. hasReducedMotionListener.current = true;
  3264. if (!isBrowser)
  3265. return;
  3266. if (window.matchMedia) {
  3267. const motionMediaQuery = window.matchMedia("(prefers-reduced-motion)");
  3268. const setReducedMotionPreferences = () => (prefersReducedMotion.current = motionMediaQuery.matches);
  3269. motionMediaQuery.addListener(setReducedMotionPreferences);
  3270. setReducedMotionPreferences();
  3271. }
  3272. else {
  3273. prefersReducedMotion.current = false;
  3274. }
  3275. }
  3276. /**
  3277. * A list of all ValueTypes
  3278. */
  3279. const valueTypes = [...dimensionValueTypes, color, complex];
  3280. /**
  3281. * Tests a value against the list of ValueTypes
  3282. */
  3283. const findValueType = (v) => valueTypes.find(testValueType(v));
  3284. function isAnimationControls(v) {
  3285. return (v !== null &&
  3286. typeof v === "object" &&
  3287. typeof v.start === "function");
  3288. }
  3289. /**
  3290. * Decides if the supplied variable is variant label
  3291. */
  3292. function isVariantLabel(v) {
  3293. return typeof v === "string" || Array.isArray(v);
  3294. }
  3295. const variantPriorityOrder = [
  3296. "animate",
  3297. "whileInView",
  3298. "whileFocus",
  3299. "whileHover",
  3300. "whileTap",
  3301. "whileDrag",
  3302. "exit",
  3303. ];
  3304. const variantProps = ["initial", ...variantPriorityOrder];
  3305. function isControllingVariants(props) {
  3306. return (isAnimationControls(props.animate) ||
  3307. variantProps.some((name) => isVariantLabel(props[name])));
  3308. }
  3309. function isVariantNode(props) {
  3310. return Boolean(isControllingVariants(props) || props.variants);
  3311. }
  3312. function updateMotionValuesFromProps(element, next, prev) {
  3313. for (const key in next) {
  3314. const nextValue = next[key];
  3315. const prevValue = prev[key];
  3316. if (isMotionValue(nextValue)) {
  3317. /**
  3318. * If this is a motion value found in props or style, we want to add it
  3319. * to our visual element's motion value map.
  3320. */
  3321. element.addValue(key, nextValue);
  3322. /**
  3323. * Check the version of the incoming motion value with this version
  3324. * and warn against mismatches.
  3325. */
  3326. if (process.env.NODE_ENV === "development") {
  3327. motionUtils.warnOnce(nextValue.version === "12.7.3", `Attempting to mix Motion versions ${nextValue.version} with 12.7.3 may not work as expected.`);
  3328. }
  3329. }
  3330. else if (isMotionValue(prevValue)) {
  3331. /**
  3332. * If we're swapping from a motion value to a static value,
  3333. * create a new motion value from that
  3334. */
  3335. element.addValue(key, motionDom.motionValue(nextValue, { owner: element }));
  3336. }
  3337. else if (prevValue !== nextValue) {
  3338. /**
  3339. * If this is a flat value that has changed, update the motion value
  3340. * or create one if it doesn't exist. We only want to do this if we're
  3341. * not handling the value with our animation state.
  3342. */
  3343. if (element.hasValue(key)) {
  3344. const existingValue = element.getValue(key);
  3345. if (existingValue.liveStyle === true) {
  3346. existingValue.jump(nextValue);
  3347. }
  3348. else if (!existingValue.hasAnimated) {
  3349. existingValue.set(nextValue);
  3350. }
  3351. }
  3352. else {
  3353. const latestValue = element.getStaticValue(key);
  3354. element.addValue(key, motionDom.motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
  3355. }
  3356. }
  3357. }
  3358. // Handle removed values
  3359. for (const key in prev) {
  3360. if (next[key] === undefined)
  3361. element.removeValue(key);
  3362. }
  3363. return next;
  3364. }
  3365. const propEventHandlers = [
  3366. "AnimationStart",
  3367. "AnimationComplete",
  3368. "Update",
  3369. "BeforeLayoutMeasure",
  3370. "LayoutMeasure",
  3371. "LayoutAnimationStart",
  3372. "LayoutAnimationComplete",
  3373. ];
  3374. /**
  3375. * A VisualElement is an imperative abstraction around UI elements such as
  3376. * HTMLElement, SVGElement, Three.Object3D etc.
  3377. */
  3378. class VisualElement {
  3379. /**
  3380. * This method takes React props and returns found MotionValues. For example, HTML
  3381. * MotionValues will be found within the style prop, whereas for Three.js within attribute arrays.
  3382. *
  3383. * This isn't an abstract method as it needs calling in the constructor, but it is
  3384. * intended to be one.
  3385. */
  3386. scrapeMotionValuesFromProps(_props, _prevProps, _visualElement) {
  3387. return {};
  3388. }
  3389. constructor({ parent, props, presenceContext, reducedMotionConfig, blockInitialAnimation, visualState, }, options = {}) {
  3390. /**
  3391. * A reference to the current underlying Instance, e.g. a HTMLElement
  3392. * or Three.Mesh etc.
  3393. */
  3394. this.current = null;
  3395. /**
  3396. * A set containing references to this VisualElement's children.
  3397. */
  3398. this.children = new Set();
  3399. /**
  3400. * Determine what role this visual element should take in the variant tree.
  3401. */
  3402. this.isVariantNode = false;
  3403. this.isControllingVariants = false;
  3404. /**
  3405. * Decides whether this VisualElement should animate in reduced motion
  3406. * mode.
  3407. *
  3408. * TODO: This is currently set on every individual VisualElement but feels
  3409. * like it could be set globally.
  3410. */
  3411. this.shouldReduceMotion = null;
  3412. /**
  3413. * A map of all motion values attached to this visual element. Motion
  3414. * values are source of truth for any given animated value. A motion
  3415. * value might be provided externally by the component via props.
  3416. */
  3417. this.values = new Map();
  3418. this.KeyframeResolver = KeyframeResolver;
  3419. /**
  3420. * Cleanup functions for active features (hover/tap/exit etc)
  3421. */
  3422. this.features = {};
  3423. /**
  3424. * A map of every subscription that binds the provided or generated
  3425. * motion values onChange listeners to this visual element.
  3426. */
  3427. this.valueSubscriptions = new Map();
  3428. /**
  3429. * A reference to the previously-provided motion values as returned
  3430. * from scrapeMotionValuesFromProps. We use the keys in here to determine
  3431. * if any motion values need to be removed after props are updated.
  3432. */
  3433. this.prevMotionValues = {};
  3434. /**
  3435. * An object containing a SubscriptionManager for each active event.
  3436. */
  3437. this.events = {};
  3438. /**
  3439. * An object containing an unsubscribe function for each prop event subscription.
  3440. * For example, every "Update" event can have multiple subscribers via
  3441. * VisualElement.on(), but only one of those can be defined via the onUpdate prop.
  3442. */
  3443. this.propEventSubscriptions = {};
  3444. this.notifyUpdate = () => this.notify("Update", this.latestValues);
  3445. this.render = () => {
  3446. if (!this.current)
  3447. return;
  3448. this.triggerBuild();
  3449. this.renderInstance(this.current, this.renderState, this.props.style, this.projection);
  3450. };
  3451. this.renderScheduledAt = 0.0;
  3452. this.scheduleRender = () => {
  3453. const now = motionDom.time.now();
  3454. if (this.renderScheduledAt < now) {
  3455. this.renderScheduledAt = now;
  3456. motionDom.frame.render(this.render, false, true);
  3457. }
  3458. };
  3459. const { latestValues, renderState, onUpdate } = visualState;
  3460. this.onUpdate = onUpdate;
  3461. this.latestValues = latestValues;
  3462. this.baseTarget = { ...latestValues };
  3463. this.initialValues = props.initial ? { ...latestValues } : {};
  3464. this.renderState = renderState;
  3465. this.parent = parent;
  3466. this.props = props;
  3467. this.presenceContext = presenceContext;
  3468. this.depth = parent ? parent.depth + 1 : 0;
  3469. this.reducedMotionConfig = reducedMotionConfig;
  3470. this.options = options;
  3471. this.blockInitialAnimation = Boolean(blockInitialAnimation);
  3472. this.isControllingVariants = isControllingVariants(props);
  3473. this.isVariantNode = isVariantNode(props);
  3474. if (this.isVariantNode) {
  3475. this.variantChildren = new Set();
  3476. }
  3477. this.manuallyAnimateOnMount = Boolean(parent && parent.current);
  3478. /**
  3479. * Any motion values that are provided to the element when created
  3480. * aren't yet bound to the element, as this would technically be impure.
  3481. * However, we iterate through the motion values and set them to the
  3482. * initial values for this component.
  3483. *
  3484. * TODO: This is impure and we should look at changing this to run on mount.
  3485. * Doing so will break some tests but this isn't necessarily a breaking change,
  3486. * more a reflection of the test.
  3487. */
  3488. const { willChange, ...initialMotionValues } = this.scrapeMotionValuesFromProps(props, {}, this);
  3489. for (const key in initialMotionValues) {
  3490. const value = initialMotionValues[key];
  3491. if (latestValues[key] !== undefined && isMotionValue(value)) {
  3492. value.set(latestValues[key], false);
  3493. }
  3494. }
  3495. }
  3496. mount(instance) {
  3497. this.current = instance;
  3498. visualElementStore.set(instance, this);
  3499. if (this.projection && !this.projection.instance) {
  3500. this.projection.mount(instance);
  3501. }
  3502. if (this.parent && this.isVariantNode && !this.isControllingVariants) {
  3503. this.removeFromVariantTree = this.parent.addVariantChild(this);
  3504. }
  3505. this.values.forEach((value, key) => this.bindToMotionValue(key, value));
  3506. if (!hasReducedMotionListener.current) {
  3507. initPrefersReducedMotion();
  3508. }
  3509. this.shouldReduceMotion =
  3510. this.reducedMotionConfig === "never"
  3511. ? false
  3512. : this.reducedMotionConfig === "always"
  3513. ? true
  3514. : prefersReducedMotion.current;
  3515. if (process.env.NODE_ENV !== "production") {
  3516. motionUtils.warnOnce(this.shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.");
  3517. }
  3518. if (this.parent)
  3519. this.parent.children.add(this);
  3520. this.update(this.props, this.presenceContext);
  3521. }
  3522. unmount() {
  3523. this.projection && this.projection.unmount();
  3524. motionDom.cancelFrame(this.notifyUpdate);
  3525. motionDom.cancelFrame(this.render);
  3526. this.valueSubscriptions.forEach((remove) => remove());
  3527. this.valueSubscriptions.clear();
  3528. this.removeFromVariantTree && this.removeFromVariantTree();
  3529. this.parent && this.parent.children.delete(this);
  3530. for (const key in this.events) {
  3531. this.events[key].clear();
  3532. }
  3533. for (const key in this.features) {
  3534. const feature = this.features[key];
  3535. if (feature) {
  3536. feature.unmount();
  3537. feature.isMounted = false;
  3538. }
  3539. }
  3540. this.current = null;
  3541. }
  3542. bindToMotionValue(key, value) {
  3543. if (this.valueSubscriptions.has(key)) {
  3544. this.valueSubscriptions.get(key)();
  3545. }
  3546. const valueIsTransform = transformProps.has(key);
  3547. if (valueIsTransform && this.onBindTransform) {
  3548. this.onBindTransform();
  3549. }
  3550. const removeOnChange = value.on("change", (latestValue) => {
  3551. this.latestValues[key] = latestValue;
  3552. this.props.onUpdate && motionDom.frame.preRender(this.notifyUpdate);
  3553. if (valueIsTransform && this.projection) {
  3554. this.projection.isTransformDirty = true;
  3555. }
  3556. });
  3557. const removeOnRenderRequest = value.on("renderRequest", this.scheduleRender);
  3558. let removeSyncCheck;
  3559. if (window.MotionCheckAppearSync) {
  3560. removeSyncCheck = window.MotionCheckAppearSync(this, key, value);
  3561. }
  3562. this.valueSubscriptions.set(key, () => {
  3563. removeOnChange();
  3564. removeOnRenderRequest();
  3565. if (removeSyncCheck)
  3566. removeSyncCheck();
  3567. if (value.owner)
  3568. value.stop();
  3569. });
  3570. }
  3571. sortNodePosition(other) {
  3572. /**
  3573. * If these nodes aren't even of the same type we can't compare their depth.
  3574. */
  3575. if (!this.current ||
  3576. !this.sortInstanceNodePosition ||
  3577. this.type !== other.type) {
  3578. return 0;
  3579. }
  3580. return this.sortInstanceNodePosition(this.current, other.current);
  3581. }
  3582. updateFeatures() {
  3583. let key = "animation";
  3584. for (key in featureDefinitions) {
  3585. const featureDefinition = featureDefinitions[key];
  3586. if (!featureDefinition)
  3587. continue;
  3588. const { isEnabled, Feature: FeatureConstructor } = featureDefinition;
  3589. /**
  3590. * If this feature is enabled but not active, make a new instance.
  3591. */
  3592. if (!this.features[key] &&
  3593. FeatureConstructor &&
  3594. isEnabled(this.props)) {
  3595. this.features[key] = new FeatureConstructor(this);
  3596. }
  3597. /**
  3598. * If we have a feature, mount or update it.
  3599. */
  3600. if (this.features[key]) {
  3601. const feature = this.features[key];
  3602. if (feature.isMounted) {
  3603. feature.update();
  3604. }
  3605. else {
  3606. feature.mount();
  3607. feature.isMounted = true;
  3608. }
  3609. }
  3610. }
  3611. }
  3612. triggerBuild() {
  3613. this.build(this.renderState, this.latestValues, this.props);
  3614. }
  3615. /**
  3616. * Measure the current viewport box with or without transforms.
  3617. * Only measures axis-aligned boxes, rotate and skew must be manually
  3618. * removed with a re-render to work.
  3619. */
  3620. measureViewportBox() {
  3621. return this.current
  3622. ? this.measureInstanceViewportBox(this.current, this.props)
  3623. : createBox();
  3624. }
  3625. getStaticValue(key) {
  3626. return this.latestValues[key];
  3627. }
  3628. setStaticValue(key, value) {
  3629. this.latestValues[key] = value;
  3630. }
  3631. /**
  3632. * Update the provided props. Ensure any newly-added motion values are
  3633. * added to our map, old ones removed, and listeners updated.
  3634. */
  3635. update(props, presenceContext) {
  3636. if (props.transformTemplate || this.props.transformTemplate) {
  3637. this.scheduleRender();
  3638. }
  3639. this.prevProps = this.props;
  3640. this.props = props;
  3641. this.prevPresenceContext = this.presenceContext;
  3642. this.presenceContext = presenceContext;
  3643. /**
  3644. * Update prop event handlers ie onAnimationStart, onAnimationComplete
  3645. */
  3646. for (let i = 0; i < propEventHandlers.length; i++) {
  3647. const key = propEventHandlers[i];
  3648. if (this.propEventSubscriptions[key]) {
  3649. this.propEventSubscriptions[key]();
  3650. delete this.propEventSubscriptions[key];
  3651. }
  3652. const listenerName = ("on" + key);
  3653. const listener = props[listenerName];
  3654. if (listener) {
  3655. this.propEventSubscriptions[key] = this.on(key, listener);
  3656. }
  3657. }
  3658. this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps, this), this.prevMotionValues);
  3659. if (this.handleChildMotionValue) {
  3660. this.handleChildMotionValue();
  3661. }
  3662. this.onUpdate && this.onUpdate(this);
  3663. }
  3664. getProps() {
  3665. return this.props;
  3666. }
  3667. /**
  3668. * Returns the variant definition with a given name.
  3669. */
  3670. getVariant(name) {
  3671. return this.props.variants ? this.props.variants[name] : undefined;
  3672. }
  3673. /**
  3674. * Returns the defined default transition on this component.
  3675. */
  3676. getDefaultTransition() {
  3677. return this.props.transition;
  3678. }
  3679. getTransformPagePoint() {
  3680. return this.props.transformPagePoint;
  3681. }
  3682. getClosestVariantNode() {
  3683. return this.isVariantNode
  3684. ? this
  3685. : this.parent
  3686. ? this.parent.getClosestVariantNode()
  3687. : undefined;
  3688. }
  3689. /**
  3690. * Add a child visual element to our set of children.
  3691. */
  3692. addVariantChild(child) {
  3693. const closestVariantNode = this.getClosestVariantNode();
  3694. if (closestVariantNode) {
  3695. closestVariantNode.variantChildren &&
  3696. closestVariantNode.variantChildren.add(child);
  3697. return () => closestVariantNode.variantChildren.delete(child);
  3698. }
  3699. }
  3700. /**
  3701. * Add a motion value and bind it to this visual element.
  3702. */
  3703. addValue(key, value) {
  3704. // Remove existing value if it exists
  3705. const existingValue = this.values.get(key);
  3706. if (value !== existingValue) {
  3707. if (existingValue)
  3708. this.removeValue(key);
  3709. this.bindToMotionValue(key, value);
  3710. this.values.set(key, value);
  3711. this.latestValues[key] = value.get();
  3712. }
  3713. }
  3714. /**
  3715. * Remove a motion value and unbind any active subscriptions.
  3716. */
  3717. removeValue(key) {
  3718. this.values.delete(key);
  3719. const unsubscribe = this.valueSubscriptions.get(key);
  3720. if (unsubscribe) {
  3721. unsubscribe();
  3722. this.valueSubscriptions.delete(key);
  3723. }
  3724. delete this.latestValues[key];
  3725. this.removeValueFromRenderState(key, this.renderState);
  3726. }
  3727. /**
  3728. * Check whether we have a motion value for this key
  3729. */
  3730. hasValue(key) {
  3731. return this.values.has(key);
  3732. }
  3733. getValue(key, defaultValue) {
  3734. if (this.props.values && this.props.values[key]) {
  3735. return this.props.values[key];
  3736. }
  3737. let value = this.values.get(key);
  3738. if (value === undefined && defaultValue !== undefined) {
  3739. value = motionDom.motionValue(defaultValue === null ? undefined : defaultValue, { owner: this });
  3740. this.addValue(key, value);
  3741. }
  3742. return value;
  3743. }
  3744. /**
  3745. * If we're trying to animate to a previously unencountered value,
  3746. * we need to check for it in our state and as a last resort read it
  3747. * directly from the instance (which might have performance implications).
  3748. */
  3749. readValue(key, target) {
  3750. let value = this.latestValues[key] !== undefined || !this.current
  3751. ? this.latestValues[key]
  3752. : this.getBaseTargetFromProps(this.props, key) ??
  3753. this.readValueFromInstance(this.current, key, this.options);
  3754. if (value !== undefined && value !== null) {
  3755. if (typeof value === "string" &&
  3756. (isNumericalString(value) || isZeroValueString(value))) {
  3757. // If this is a number read as a string, ie "0" or "200", convert it to a number
  3758. value = parseFloat(value);
  3759. }
  3760. else if (!findValueType(value) && complex.test(target)) {
  3761. value = getAnimatableNone(key, target);
  3762. }
  3763. this.setBaseTarget(key, isMotionValue(value) ? value.get() : value);
  3764. }
  3765. return isMotionValue(value) ? value.get() : value;
  3766. }
  3767. /**
  3768. * Set the base target to later animate back to. This is currently
  3769. * only hydrated on creation and when we first read a value.
  3770. */
  3771. setBaseTarget(key, value) {
  3772. this.baseTarget[key] = value;
  3773. }
  3774. /**
  3775. * Find the base target for a value thats been removed from all animation
  3776. * props.
  3777. */
  3778. getBaseTarget(key) {
  3779. const { initial } = this.props;
  3780. let valueFromInitial;
  3781. if (typeof initial === "string" || typeof initial === "object") {
  3782. const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom);
  3783. if (variant) {
  3784. valueFromInitial = variant[key];
  3785. }
  3786. }
  3787. /**
  3788. * If this value still exists in the current initial variant, read that.
  3789. */
  3790. if (initial && valueFromInitial !== undefined) {
  3791. return valueFromInitial;
  3792. }
  3793. /**
  3794. * Alternatively, if this VisualElement config has defined a getBaseTarget
  3795. * so we can read the value from an alternative source, try that.
  3796. */
  3797. const target = this.getBaseTargetFromProps(this.props, key);
  3798. if (target !== undefined && !isMotionValue(target))
  3799. return target;
  3800. /**
  3801. * If the value was initially defined on initial, but it doesn't any more,
  3802. * return undefined. Otherwise return the value as initially read from the DOM.
  3803. */
  3804. return this.initialValues[key] !== undefined &&
  3805. valueFromInitial === undefined
  3806. ? undefined
  3807. : this.baseTarget[key];
  3808. }
  3809. on(eventName, callback) {
  3810. if (!this.events[eventName]) {
  3811. this.events[eventName] = new motionUtils.SubscriptionManager();
  3812. }
  3813. return this.events[eventName].add(callback);
  3814. }
  3815. notify(eventName, ...args) {
  3816. if (this.events[eventName]) {
  3817. this.events[eventName].notify(...args);
  3818. }
  3819. }
  3820. }
  3821. class DOMVisualElement extends VisualElement {
  3822. constructor() {
  3823. super(...arguments);
  3824. this.KeyframeResolver = DOMKeyframesResolver;
  3825. }
  3826. sortInstanceNodePosition(a, b) {
  3827. /**
  3828. * compareDocumentPosition returns a bitmask, by using the bitwise &
  3829. * we're returning true if 2 in that bitmask is set to true. 2 is set
  3830. * to true if b preceeds a.
  3831. */
  3832. return a.compareDocumentPosition(b) & 2 ? 1 : -1;
  3833. }
  3834. getBaseTargetFromProps(props, key) {
  3835. return props.style
  3836. ? props.style[key]
  3837. : undefined;
  3838. }
  3839. removeValueFromRenderState(key, { vars, style }) {
  3840. delete vars[key];
  3841. delete style[key];
  3842. }
  3843. handleChildMotionValue() {
  3844. if (this.childSubscription) {
  3845. this.childSubscription();
  3846. delete this.childSubscription;
  3847. }
  3848. const { children } = this.props;
  3849. if (isMotionValue(children)) {
  3850. this.childSubscription = children.on("change", (latest) => {
  3851. if (this.current) {
  3852. this.current.textContent = `${latest}`;
  3853. }
  3854. });
  3855. }
  3856. }
  3857. }
  3858. /**
  3859. * Provided a value and a ValueType, returns the value as that value type.
  3860. */
  3861. const getValueAsType = (value, type) => {
  3862. return type && typeof value === "number"
  3863. ? type.transform(value)
  3864. : value;
  3865. };
  3866. const translateAlias = {
  3867. x: "translateX",
  3868. y: "translateY",
  3869. z: "translateZ",
  3870. transformPerspective: "perspective",
  3871. };
  3872. const numTransforms = transformPropOrder.length;
  3873. /**
  3874. * Build a CSS transform style from individual x/y/scale etc properties.
  3875. *
  3876. * This outputs with a default order of transforms/scales/rotations, this can be customised by
  3877. * providing a transformTemplate function.
  3878. */
  3879. function buildTransform(latestValues, transform, transformTemplate) {
  3880. // The transform string we're going to build into.
  3881. let transformString = "";
  3882. let transformIsDefault = true;
  3883. /**
  3884. * Loop over all possible transforms in order, adding the ones that
  3885. * are present to the transform string.
  3886. */
  3887. for (let i = 0; i < numTransforms; i++) {
  3888. const key = transformPropOrder[i];
  3889. const value = latestValues[key];
  3890. if (value === undefined)
  3891. continue;
  3892. let valueIsDefault = true;
  3893. if (typeof value === "number") {
  3894. valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
  3895. }
  3896. else {
  3897. valueIsDefault = parseFloat(value) === 0;
  3898. }
  3899. if (!valueIsDefault || transformTemplate) {
  3900. const valueAsType = getValueAsType(value, numberValueTypes[key]);
  3901. if (!valueIsDefault) {
  3902. transformIsDefault = false;
  3903. const transformName = translateAlias[key] || key;
  3904. transformString += `${transformName}(${valueAsType}) `;
  3905. }
  3906. if (transformTemplate) {
  3907. transform[key] = valueAsType;
  3908. }
  3909. }
  3910. }
  3911. transformString = transformString.trim();
  3912. // If we have a custom `transform` template, pass our transform values and
  3913. // generated transformString to that before returning
  3914. if (transformTemplate) {
  3915. transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
  3916. }
  3917. else if (transformIsDefault) {
  3918. transformString = "none";
  3919. }
  3920. return transformString;
  3921. }
  3922. function buildHTMLStyles(state, latestValues, transformTemplate) {
  3923. const { style, vars, transformOrigin } = state;
  3924. // Track whether we encounter any transform or transformOrigin values.
  3925. let hasTransform = false;
  3926. let hasTransformOrigin = false;
  3927. /**
  3928. * Loop over all our latest animated values and decide whether to handle them
  3929. * as a style or CSS variable.
  3930. *
  3931. * Transforms and transform origins are kept separately for further processing.
  3932. */
  3933. for (const key in latestValues) {
  3934. const value = latestValues[key];
  3935. if (transformProps.has(key)) {
  3936. // If this is a transform, flag to enable further transform processing
  3937. hasTransform = true;
  3938. continue;
  3939. }
  3940. else if (isCSSVariableName(key)) {
  3941. vars[key] = value;
  3942. continue;
  3943. }
  3944. else {
  3945. // Convert the value to its default value type, ie 0 -> "0px"
  3946. const valueAsType = getValueAsType(value, numberValueTypes[key]);
  3947. if (key.startsWith("origin")) {
  3948. // If this is a transform origin, flag and enable further transform-origin processing
  3949. hasTransformOrigin = true;
  3950. transformOrigin[key] =
  3951. valueAsType;
  3952. }
  3953. else {
  3954. style[key] = valueAsType;
  3955. }
  3956. }
  3957. }
  3958. if (!latestValues.transform) {
  3959. if (hasTransform || transformTemplate) {
  3960. style.transform = buildTransform(latestValues, state.transform, transformTemplate);
  3961. }
  3962. else if (style.transform) {
  3963. /**
  3964. * If we have previously created a transform but currently don't have any,
  3965. * reset transform style to none.
  3966. */
  3967. style.transform = "none";
  3968. }
  3969. }
  3970. /**
  3971. * Build a transformOrigin style. Uses the same defaults as the browser for
  3972. * undefined origins.
  3973. */
  3974. if (hasTransformOrigin) {
  3975. const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
  3976. style.transformOrigin = `${originX} ${originY} ${originZ}`;
  3977. }
  3978. }
  3979. const dashKeys = {
  3980. offset: "stroke-dashoffset",
  3981. array: "stroke-dasharray",
  3982. };
  3983. const camelKeys = {
  3984. offset: "strokeDashoffset",
  3985. array: "strokeDasharray",
  3986. };
  3987. /**
  3988. * Build SVG path properties. Uses the path's measured length to convert
  3989. * our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
  3990. * and stroke-dasharray attributes.
  3991. *
  3992. * This function is mutative to reduce per-frame GC.
  3993. */
  3994. function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
  3995. // Normalise path length by setting SVG attribute pathLength to 1
  3996. attrs.pathLength = 1;
  3997. // We use dash case when setting attributes directly to the DOM node and camel case
  3998. // when defining props on a React component.
  3999. const keys = useDashCase ? dashKeys : camelKeys;
  4000. // Build the dash offset
  4001. attrs[keys.offset] = px.transform(-offset);
  4002. // Build the dash array
  4003. const pathLength = px.transform(length);
  4004. const pathSpacing = px.transform(spacing);
  4005. attrs[keys.array] = `${pathLength} ${pathSpacing}`;
  4006. }
  4007. function calcOrigin(origin, offset, size) {
  4008. return typeof origin === "string"
  4009. ? origin
  4010. : px.transform(offset + size * origin);
  4011. }
  4012. /**
  4013. * The SVG transform origin defaults are different to CSS and is less intuitive,
  4014. * so we use the measured dimensions of the SVG to reconcile these.
  4015. */
  4016. function calcSVGTransformOrigin(dimensions, originX, originY) {
  4017. const pxOriginX = calcOrigin(originX, dimensions.x, dimensions.width);
  4018. const pxOriginY = calcOrigin(originY, dimensions.y, dimensions.height);
  4019. return `${pxOriginX} ${pxOriginY}`;
  4020. }
  4021. /**
  4022. * Build SVG visual attrbutes, like cx and style.transform
  4023. */
  4024. function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0,
  4025. // This is object creation, which we try to avoid per-frame.
  4026. ...latest }, isSVGTag, transformTemplate) {
  4027. buildHTMLStyles(state, latest, transformTemplate);
  4028. /**
  4029. * For svg tags we just want to make sure viewBox is animatable and treat all the styles
  4030. * as normal HTML tags.
  4031. */
  4032. if (isSVGTag) {
  4033. if (state.style.viewBox) {
  4034. state.attrs.viewBox = state.style.viewBox;
  4035. }
  4036. return;
  4037. }
  4038. state.attrs = state.style;
  4039. state.style = {};
  4040. const { attrs, style, dimensions } = state;
  4041. /**
  4042. * However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
  4043. * and copy it into style.
  4044. */
  4045. if (attrs.transform) {
  4046. if (dimensions)
  4047. style.transform = attrs.transform;
  4048. delete attrs.transform;
  4049. }
  4050. // Parse transformOrigin
  4051. if (dimensions &&
  4052. (originX !== undefined || originY !== undefined || style.transform)) {
  4053. style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
  4054. }
  4055. // Render attrX/attrY/attrScale as attributes
  4056. if (attrX !== undefined)
  4057. attrs.x = attrX;
  4058. if (attrY !== undefined)
  4059. attrs.y = attrY;
  4060. if (attrScale !== undefined)
  4061. attrs.scale = attrScale;
  4062. // Build SVG path if one has been defined
  4063. if (pathLength !== undefined) {
  4064. buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
  4065. }
  4066. }
  4067. /**
  4068. * A set of attribute names that are always read/written as camel case.
  4069. */
  4070. const camelCaseAttributes = new Set([
  4071. "baseFrequency",
  4072. "diffuseConstant",
  4073. "kernelMatrix",
  4074. "kernelUnitLength",
  4075. "keySplines",
  4076. "keyTimes",
  4077. "limitingConeAngle",
  4078. "markerHeight",
  4079. "markerWidth",
  4080. "numOctaves",
  4081. "targetX",
  4082. "targetY",
  4083. "surfaceScale",
  4084. "specularConstant",
  4085. "specularExponent",
  4086. "stdDeviation",
  4087. "tableValues",
  4088. "viewBox",
  4089. "gradientTransform",
  4090. "pathLength",
  4091. "startOffset",
  4092. "textLength",
  4093. "lengthAdjust",
  4094. ]);
  4095. const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
  4096. function updateSVGDimensions(instance, renderState) {
  4097. try {
  4098. renderState.dimensions =
  4099. typeof instance.getBBox === "function"
  4100. ? instance.getBBox()
  4101. : instance.getBoundingClientRect();
  4102. }
  4103. catch (e) {
  4104. // Most likely trying to measure an unrendered element under Firefox
  4105. renderState.dimensions = {
  4106. x: 0,
  4107. y: 0,
  4108. width: 0,
  4109. height: 0,
  4110. };
  4111. }
  4112. }
  4113. function renderHTML(element, { style, vars }, styleProp, projection) {
  4114. Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp));
  4115. // Loop over any CSS variables and assign those.
  4116. for (const key in vars) {
  4117. element.style.setProperty(key, vars[key]);
  4118. }
  4119. }
  4120. function renderSVG(element, renderState, _styleProp, projection) {
  4121. renderHTML(element, renderState, undefined, projection);
  4122. for (const key in renderState.attrs) {
  4123. element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
  4124. }
  4125. }
  4126. const scaleCorrectors = {};
  4127. function isForcedMotionValue(key, { layout, layoutId }) {
  4128. return (transformProps.has(key) ||
  4129. key.startsWith("origin") ||
  4130. ((layout || layoutId !== undefined) &&
  4131. (!!scaleCorrectors[key] || key === "opacity")));
  4132. }
  4133. function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
  4134. const { style } = props;
  4135. const newValues = {};
  4136. for (const key in style) {
  4137. if (isMotionValue(style[key]) ||
  4138. (prevProps.style &&
  4139. isMotionValue(prevProps.style[key])) ||
  4140. isForcedMotionValue(key, props) ||
  4141. visualElement?.getValue(key)?.liveStyle !== undefined) {
  4142. newValues[key] = style[key];
  4143. }
  4144. }
  4145. return newValues;
  4146. }
  4147. function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  4148. const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
  4149. for (const key in props) {
  4150. if (isMotionValue(props[key]) ||
  4151. isMotionValue(prevProps[key])) {
  4152. const targetKey = transformPropOrder.indexOf(key) !== -1
  4153. ? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
  4154. : key;
  4155. newValues[targetKey] = props[key];
  4156. }
  4157. }
  4158. return newValues;
  4159. }
  4160. class SVGVisualElement extends DOMVisualElement {
  4161. constructor() {
  4162. super(...arguments);
  4163. this.type = "svg";
  4164. this.isSVGTag = false;
  4165. this.measureInstanceViewportBox = createBox;
  4166. this.updateDimensions = () => {
  4167. if (this.current && !this.renderState.dimensions) {
  4168. updateSVGDimensions(this.current, this.renderState);
  4169. }
  4170. };
  4171. }
  4172. getBaseTargetFromProps(props, key) {
  4173. return props[key];
  4174. }
  4175. readValueFromInstance(instance, key) {
  4176. if (transformProps.has(key)) {
  4177. const defaultType = getDefaultValueType(key);
  4178. return defaultType ? defaultType.default || 0 : 0;
  4179. }
  4180. key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
  4181. return instance.getAttribute(key);
  4182. }
  4183. scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  4184. return scrapeMotionValuesFromProps(props, prevProps, visualElement);
  4185. }
  4186. onBindTransform() {
  4187. if (this.current && !this.renderState.dimensions) {
  4188. motionDom.frame.postRender(this.updateDimensions);
  4189. }
  4190. }
  4191. build(renderState, latestValues, props) {
  4192. buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate);
  4193. }
  4194. renderInstance(instance, renderState, styleProp, projection) {
  4195. renderSVG(instance, renderState, styleProp, projection);
  4196. }
  4197. mount(instance) {
  4198. this.isSVGTag = isSVGTag(instance.tagName);
  4199. super.mount(instance);
  4200. }
  4201. }
  4202. /**
  4203. * Bounding boxes tend to be defined as top, left, right, bottom. For various operations
  4204. * it's easier to consider each axis individually. This function returns a bounding box
  4205. * as a map of single-axis min/max values.
  4206. */
  4207. function convertBoundingBoxToBox({ top, left, right, bottom, }) {
  4208. return {
  4209. x: { min: left, max: right },
  4210. y: { min: top, max: bottom },
  4211. };
  4212. }
  4213. /**
  4214. * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
  4215. * provided by Framer to allow measured points to be corrected for device scaling. This is used
  4216. * when measuring DOM elements and DOM event points.
  4217. */
  4218. function transformBoxPoints(point, transformPoint) {
  4219. if (!transformPoint)
  4220. return point;
  4221. const topLeft = transformPoint({ x: point.left, y: point.top });
  4222. const bottomRight = transformPoint({ x: point.right, y: point.bottom });
  4223. return {
  4224. top: topLeft.y,
  4225. left: topLeft.x,
  4226. bottom: bottomRight.y,
  4227. right: bottomRight.x,
  4228. };
  4229. }
  4230. function measureViewportBox(instance, transformPoint) {
  4231. return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
  4232. }
  4233. function getComputedStyle$1(element) {
  4234. return window.getComputedStyle(element);
  4235. }
  4236. class HTMLVisualElement extends DOMVisualElement {
  4237. constructor() {
  4238. super(...arguments);
  4239. this.type = "html";
  4240. this.renderInstance = renderHTML;
  4241. }
  4242. readValueFromInstance(instance, key) {
  4243. if (transformProps.has(key)) {
  4244. return readTransformValue(instance, key);
  4245. }
  4246. else {
  4247. const computedStyle = getComputedStyle$1(instance);
  4248. const value = (isCSSVariableName(key)
  4249. ? computedStyle.getPropertyValue(key)
  4250. : computedStyle[key]) || 0;
  4251. return typeof value === "string" ? value.trim() : value;
  4252. }
  4253. }
  4254. measureInstanceViewportBox(instance, { transformPagePoint }) {
  4255. return measureViewportBox(instance, transformPagePoint);
  4256. }
  4257. build(renderState, latestValues, props) {
  4258. buildHTMLStyles(renderState, latestValues, props.transformTemplate);
  4259. }
  4260. scrapeMotionValuesFromProps(props, prevProps, visualElement) {
  4261. return scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
  4262. }
  4263. }
  4264. function isObjectKey(key, object) {
  4265. return key in object;
  4266. }
  4267. class ObjectVisualElement extends VisualElement {
  4268. constructor() {
  4269. super(...arguments);
  4270. this.type = "object";
  4271. }
  4272. readValueFromInstance(instance, key) {
  4273. if (isObjectKey(key, instance)) {
  4274. const value = instance[key];
  4275. if (typeof value === "string" || typeof value === "number") {
  4276. return value;
  4277. }
  4278. }
  4279. return undefined;
  4280. }
  4281. getBaseTargetFromProps() {
  4282. return undefined;
  4283. }
  4284. removeValueFromRenderState(key, renderState) {
  4285. delete renderState.output[key];
  4286. }
  4287. measureInstanceViewportBox() {
  4288. return createBox();
  4289. }
  4290. build(renderState, latestValues) {
  4291. Object.assign(renderState.output, latestValues);
  4292. }
  4293. renderInstance(instance, { output }) {
  4294. Object.assign(instance, output);
  4295. }
  4296. sortInstanceNodePosition() {
  4297. return 0;
  4298. }
  4299. }
  4300. function createDOMVisualElement(element) {
  4301. const options = {
  4302. presenceContext: null,
  4303. props: {},
  4304. visualState: {
  4305. renderState: {
  4306. transform: {},
  4307. transformOrigin: {},
  4308. style: {},
  4309. vars: {},
  4310. attrs: {},
  4311. },
  4312. latestValues: {},
  4313. },
  4314. };
  4315. const node = isSVGElement(element)
  4316. ? new SVGVisualElement(options)
  4317. : new HTMLVisualElement(options);
  4318. node.mount(element);
  4319. visualElementStore.set(element, node);
  4320. }
  4321. function createObjectVisualElement(subject) {
  4322. const options = {
  4323. presenceContext: null,
  4324. props: {},
  4325. visualState: {
  4326. renderState: {
  4327. output: {},
  4328. },
  4329. latestValues: {},
  4330. },
  4331. };
  4332. const node = new ObjectVisualElement(options);
  4333. node.mount(subject);
  4334. visualElementStore.set(subject, node);
  4335. }
  4336. function animateSingleValue(value, keyframes, options) {
  4337. const motionValue = isMotionValue(value) ? value : motionDom.motionValue(value);
  4338. motionValue.start(animateMotionValue("", motionValue, keyframes, options));
  4339. return motionValue.animation;
  4340. }
  4341. function isSingleValue(subject, keyframes) {
  4342. return (isMotionValue(subject) ||
  4343. typeof subject === "number" ||
  4344. (typeof subject === "string" && !isDOMKeyframes(keyframes)));
  4345. }
  4346. /**
  4347. * Implementation
  4348. */
  4349. function animateSubject(subject, keyframes, options, scope) {
  4350. const animations = [];
  4351. if (isSingleValue(subject, keyframes)) {
  4352. animations.push(animateSingleValue(subject, isDOMKeyframes(keyframes)
  4353. ? keyframes.default || keyframes
  4354. : keyframes, options ? options.default || options : options));
  4355. }
  4356. else {
  4357. const subjects = resolveSubjects(subject, keyframes, scope);
  4358. const numSubjects = subjects.length;
  4359. motionUtils.invariant(Boolean(numSubjects), "No valid elements provided.");
  4360. for (let i = 0; i < numSubjects; i++) {
  4361. const thisSubject = subjects[i];
  4362. const createVisualElement = thisSubject instanceof Element
  4363. ? createDOMVisualElement
  4364. : createObjectVisualElement;
  4365. if (!visualElementStore.has(thisSubject)) {
  4366. createVisualElement(thisSubject);
  4367. }
  4368. const visualElement = visualElementStore.get(thisSubject);
  4369. const transition = { ...options };
  4370. /**
  4371. * Resolve stagger function if provided.
  4372. */
  4373. if ("delay" in transition &&
  4374. typeof transition.delay === "function") {
  4375. transition.delay = transition.delay(i, numSubjects);
  4376. }
  4377. animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {}));
  4378. }
  4379. }
  4380. return animations;
  4381. }
  4382. function animateSequence(sequence, options, scope) {
  4383. const animations = [];
  4384. const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring });
  4385. animationDefinitions.forEach(({ keyframes, transition }, subject) => {
  4386. animations.push(...animateSubject(subject, keyframes, transition));
  4387. });
  4388. return animations;
  4389. }
  4390. function isSequence(value) {
  4391. return Array.isArray(value) && value.some(Array.isArray);
  4392. }
  4393. /**
  4394. * Creates an animation function that is optionally scoped
  4395. * to a specific element.
  4396. */
  4397. function createScopedAnimate(scope) {
  4398. /**
  4399. * Implementation
  4400. */
  4401. function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
  4402. let animations = [];
  4403. if (isSequence(subjectOrSequence)) {
  4404. animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope);
  4405. }
  4406. else {
  4407. animations = animateSubject(subjectOrSequence, optionsOrKeyframes, options, scope);
  4408. }
  4409. const animation = new motionDom.GroupAnimationWithThen(animations);
  4410. if (scope) {
  4411. scope.animations.push(animation);
  4412. }
  4413. return animation;
  4414. }
  4415. return scopedAnimate;
  4416. }
  4417. const animate = createScopedAnimate();
  4418. function animateElements(elementOrSelector, keyframes, options, scope) {
  4419. const elements = motionDom.resolveElements(elementOrSelector, scope);
  4420. const numElements = elements.length;
  4421. motionUtils.invariant(Boolean(numElements), "No valid element provided.");
  4422. const animations = [];
  4423. for (let i = 0; i < numElements; i++) {
  4424. const element = elements[i];
  4425. const elementTransition = { ...options };
  4426. /**
  4427. * Resolve stagger function if provided.
  4428. */
  4429. if (typeof elementTransition.delay === "function") {
  4430. elementTransition.delay = elementTransition.delay(i, numElements);
  4431. }
  4432. for (const valueName in keyframes) {
  4433. const valueKeyframes = keyframes[valueName];
  4434. const valueOptions = {
  4435. ...motionDom.getValueTransition(elementTransition, valueName),
  4436. };
  4437. valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
  4438. valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
  4439. animations.push(new motionDom.NativeAnimation({
  4440. element,
  4441. name: valueName,
  4442. keyframes: valueKeyframes,
  4443. transition: valueOptions,
  4444. allowFlatten: !elementTransition.type && !elementTransition.ease,
  4445. }));
  4446. }
  4447. }
  4448. return animations;
  4449. }
  4450. const createScopedWaapiAnimate = (scope) => {
  4451. function scopedAnimate(elementOrSelector, keyframes, options) {
  4452. return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
  4453. }
  4454. return scopedAnimate;
  4455. };
  4456. const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
  4457. function observeTimeline(update, timeline) {
  4458. let prevProgress;
  4459. const onFrame = () => {
  4460. const { currentTime } = timeline;
  4461. const percentage = currentTime === null ? 0 : currentTime.value;
  4462. const progress = percentage / 100;
  4463. if (prevProgress !== progress) {
  4464. update(progress);
  4465. }
  4466. prevProgress = progress;
  4467. };
  4468. motionDom.frame.update(onFrame, true);
  4469. return () => motionDom.cancelFrame(onFrame);
  4470. }
  4471. const resizeHandlers = new WeakMap();
  4472. let observer;
  4473. function getElementSize(target, borderBoxSize) {
  4474. if (borderBoxSize) {
  4475. const { inlineSize, blockSize } = borderBoxSize[0];
  4476. return { width: inlineSize, height: blockSize };
  4477. }
  4478. else if (target instanceof SVGElement && "getBBox" in target) {
  4479. return target.getBBox();
  4480. }
  4481. else {
  4482. return {
  4483. width: target.offsetWidth,
  4484. height: target.offsetHeight,
  4485. };
  4486. }
  4487. }
  4488. function notifyTarget({ target, contentRect, borderBoxSize, }) {
  4489. resizeHandlers.get(target)?.forEach((handler) => {
  4490. handler({
  4491. target,
  4492. contentSize: contentRect,
  4493. get size() {
  4494. return getElementSize(target, borderBoxSize);
  4495. },
  4496. });
  4497. });
  4498. }
  4499. function notifyAll(entries) {
  4500. entries.forEach(notifyTarget);
  4501. }
  4502. function createResizeObserver() {
  4503. if (typeof ResizeObserver === "undefined")
  4504. return;
  4505. observer = new ResizeObserver(notifyAll);
  4506. }
  4507. function resizeElement(target, handler) {
  4508. if (!observer)
  4509. createResizeObserver();
  4510. const elements = motionDom.resolveElements(target);
  4511. elements.forEach((element) => {
  4512. let elementHandlers = resizeHandlers.get(element);
  4513. if (!elementHandlers) {
  4514. elementHandlers = new Set();
  4515. resizeHandlers.set(element, elementHandlers);
  4516. }
  4517. elementHandlers.add(handler);
  4518. observer?.observe(element);
  4519. });
  4520. return () => {
  4521. elements.forEach((element) => {
  4522. const elementHandlers = resizeHandlers.get(element);
  4523. elementHandlers?.delete(handler);
  4524. if (!elementHandlers?.size) {
  4525. observer?.unobserve(element);
  4526. }
  4527. });
  4528. };
  4529. }
  4530. const windowCallbacks = new Set();
  4531. let windowResizeHandler;
  4532. function createWindowResizeHandler() {
  4533. windowResizeHandler = () => {
  4534. const size = {
  4535. width: window.innerWidth,
  4536. height: window.innerHeight,
  4537. };
  4538. const info = {
  4539. target: window,
  4540. size,
  4541. contentSize: size,
  4542. };
  4543. windowCallbacks.forEach((callback) => callback(info));
  4544. };
  4545. window.addEventListener("resize", windowResizeHandler);
  4546. }
  4547. function resizeWindow(callback) {
  4548. windowCallbacks.add(callback);
  4549. if (!windowResizeHandler)
  4550. createWindowResizeHandler();
  4551. return () => {
  4552. windowCallbacks.delete(callback);
  4553. if (!windowCallbacks.size && windowResizeHandler) {
  4554. windowResizeHandler = undefined;
  4555. }
  4556. };
  4557. }
  4558. function resize(a, b) {
  4559. return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
  4560. }
  4561. /**
  4562. * A time in milliseconds, beyond which we consider the scroll velocity to be 0.
  4563. */
  4564. const maxElapsed = 50;
  4565. const createAxisInfo = () => ({
  4566. current: 0,
  4567. offset: [],
  4568. progress: 0,
  4569. scrollLength: 0,
  4570. targetOffset: 0,
  4571. targetLength: 0,
  4572. containerLength: 0,
  4573. velocity: 0,
  4574. });
  4575. const createScrollInfo = () => ({
  4576. time: 0,
  4577. x: createAxisInfo(),
  4578. y: createAxisInfo(),
  4579. });
  4580. const keys = {
  4581. x: {
  4582. length: "Width",
  4583. position: "Left",
  4584. },
  4585. y: {
  4586. length: "Height",
  4587. position: "Top",
  4588. },
  4589. };
  4590. function updateAxisInfo(element, axisName, info, time) {
  4591. const axis = info[axisName];
  4592. const { length, position } = keys[axisName];
  4593. const prev = axis.current;
  4594. const prevTime = info.time;
  4595. axis.current = element[`scroll${position}`];
  4596. axis.scrollLength = element[`scroll${length}`] - element[`client${length}`];
  4597. axis.offset.length = 0;
  4598. axis.offset[0] = 0;
  4599. axis.offset[1] = axis.scrollLength;
  4600. axis.progress = motionUtils.progress(0, axis.scrollLength, axis.current);
  4601. const elapsed = time - prevTime;
  4602. axis.velocity =
  4603. elapsed > maxElapsed
  4604. ? 0
  4605. : motionUtils.velocityPerSecond(axis.current - prev, elapsed);
  4606. }
  4607. function updateScrollInfo(element, info, time) {
  4608. updateAxisInfo(element, "x", info, time);
  4609. updateAxisInfo(element, "y", info, time);
  4610. info.time = time;
  4611. }
  4612. function calcInset(element, container) {
  4613. const inset = { x: 0, y: 0 };
  4614. let current = element;
  4615. while (current && current !== container) {
  4616. if (current instanceof HTMLElement) {
  4617. inset.x += current.offsetLeft;
  4618. inset.y += current.offsetTop;
  4619. current = current.offsetParent;
  4620. }
  4621. else if (current.tagName === "svg") {
  4622. /**
  4623. * This isn't an ideal approach to measuring the offset of <svg /> tags.
  4624. * It would be preferable, given they behave like HTMLElements in most ways
  4625. * to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
  4626. * can't use .getBBox() like most SVG elements as these provide the offset
  4627. * relative to the SVG itself, which for <svg /> is usually 0x0.
  4628. */
  4629. const svgBoundingBox = current.getBoundingClientRect();
  4630. current = current.parentElement;
  4631. const parentBoundingBox = current.getBoundingClientRect();
  4632. inset.x += svgBoundingBox.left - parentBoundingBox.left;
  4633. inset.y += svgBoundingBox.top - parentBoundingBox.top;
  4634. }
  4635. else if (current instanceof SVGGraphicsElement) {
  4636. const { x, y } = current.getBBox();
  4637. inset.x += x;
  4638. inset.y += y;
  4639. let svg = null;
  4640. let parent = current.parentNode;
  4641. while (!svg) {
  4642. if (parent.tagName === "svg") {
  4643. svg = parent;
  4644. }
  4645. parent = current.parentNode;
  4646. }
  4647. current = svg;
  4648. }
  4649. else {
  4650. break;
  4651. }
  4652. }
  4653. return inset;
  4654. }
  4655. const namedEdges = {
  4656. start: 0,
  4657. center: 0.5,
  4658. end: 1,
  4659. };
  4660. function resolveEdge(edge, length, inset = 0) {
  4661. let delta = 0;
  4662. /**
  4663. * If we have this edge defined as a preset, replace the definition
  4664. * with the numerical value.
  4665. */
  4666. if (edge in namedEdges) {
  4667. edge = namedEdges[edge];
  4668. }
  4669. /**
  4670. * Handle unit values
  4671. */
  4672. if (typeof edge === "string") {
  4673. const asNumber = parseFloat(edge);
  4674. if (edge.endsWith("px")) {
  4675. delta = asNumber;
  4676. }
  4677. else if (edge.endsWith("%")) {
  4678. edge = asNumber / 100;
  4679. }
  4680. else if (edge.endsWith("vw")) {
  4681. delta = (asNumber / 100) * document.documentElement.clientWidth;
  4682. }
  4683. else if (edge.endsWith("vh")) {
  4684. delta = (asNumber / 100) * document.documentElement.clientHeight;
  4685. }
  4686. else {
  4687. edge = asNumber;
  4688. }
  4689. }
  4690. /**
  4691. * If the edge is defined as a number, handle as a progress value.
  4692. */
  4693. if (typeof edge === "number") {
  4694. delta = length * edge;
  4695. }
  4696. return inset + delta;
  4697. }
  4698. const defaultOffset = [0, 0];
  4699. function resolveOffset(offset, containerLength, targetLength, targetInset) {
  4700. let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset;
  4701. let targetPoint = 0;
  4702. let containerPoint = 0;
  4703. if (typeof offset === "number") {
  4704. /**
  4705. * If we're provided offset: [0, 0.5, 1] then each number x should become
  4706. * [x, x], so we default to the behaviour of mapping 0 => 0 of both target
  4707. * and container etc.
  4708. */
  4709. offsetDefinition = [offset, offset];
  4710. }
  4711. else if (typeof offset === "string") {
  4712. offset = offset.trim();
  4713. if (offset.includes(" ")) {
  4714. offsetDefinition = offset.split(" ");
  4715. }
  4716. else {
  4717. /**
  4718. * If we're provided a definition like "100px" then we want to apply
  4719. * that only to the top of the target point, leaving the container at 0.
  4720. * Whereas a named offset like "end" should be applied to both.
  4721. */
  4722. offsetDefinition = [offset, namedEdges[offset] ? offset : `0`];
  4723. }
  4724. }
  4725. targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset);
  4726. containerPoint = resolveEdge(offsetDefinition[1], containerLength);
  4727. return targetPoint - containerPoint;
  4728. }
  4729. const ScrollOffset = {
  4730. Enter: [
  4731. [0, 1],
  4732. [1, 1],
  4733. ],
  4734. Exit: [
  4735. [0, 0],
  4736. [1, 0],
  4737. ],
  4738. Any: [
  4739. [1, 0],
  4740. [0, 1],
  4741. ],
  4742. All: [
  4743. [0, 0],
  4744. [1, 1],
  4745. ],
  4746. };
  4747. const point = { x: 0, y: 0 };
  4748. function getTargetSize(target) {
  4749. return "getBBox" in target && target.tagName !== "svg"
  4750. ? target.getBBox()
  4751. : { width: target.clientWidth, height: target.clientHeight };
  4752. }
  4753. function resolveOffsets(container, info, options) {
  4754. const { offset: offsetDefinition = ScrollOffset.All } = options;
  4755. const { target = container, axis = "y" } = options;
  4756. const lengthLabel = axis === "y" ? "height" : "width";
  4757. const inset = target !== container ? calcInset(target, container) : point;
  4758. /**
  4759. * Measure the target and container. If they're the same thing then we
  4760. * use the container's scrollWidth/Height as the target, from there
  4761. * all other calculations can remain the same.
  4762. */
  4763. const targetSize = target === container
  4764. ? { width: container.scrollWidth, height: container.scrollHeight }
  4765. : getTargetSize(target);
  4766. const containerSize = {
  4767. width: container.clientWidth,
  4768. height: container.clientHeight,
  4769. };
  4770. /**
  4771. * Reset the length of the resolved offset array rather than creating a new one.
  4772. * TODO: More reusable data structures for targetSize/containerSize would also be good.
  4773. */
  4774. info[axis].offset.length = 0;
  4775. /**
  4776. * Populate the offset array by resolving the user's offset definition into
  4777. * a list of pixel scroll offets.
  4778. */
  4779. let hasChanged = !info[axis].interpolate;
  4780. const numOffsets = offsetDefinition.length;
  4781. for (let i = 0; i < numOffsets; i++) {
  4782. const offset = resolveOffset(offsetDefinition[i], containerSize[lengthLabel], targetSize[lengthLabel], inset[axis]);
  4783. if (!hasChanged && offset !== info[axis].interpolatorOffsets[i]) {
  4784. hasChanged = true;
  4785. }
  4786. info[axis].offset[i] = offset;
  4787. }
  4788. /**
  4789. * If the pixel scroll offsets have changed, create a new interpolator function
  4790. * to map scroll value into a progress.
  4791. */
  4792. if (hasChanged) {
  4793. info[axis].interpolate = interpolate(info[axis].offset, defaultOffset$1(offsetDefinition), { clamp: false });
  4794. info[axis].interpolatorOffsets = [...info[axis].offset];
  4795. }
  4796. info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current));
  4797. }
  4798. function measure(container, target = container, info) {
  4799. /**
  4800. * Find inset of target within scrollable container
  4801. */
  4802. info.x.targetOffset = 0;
  4803. info.y.targetOffset = 0;
  4804. if (target !== container) {
  4805. let node = target;
  4806. while (node && node !== container) {
  4807. info.x.targetOffset += node.offsetLeft;
  4808. info.y.targetOffset += node.offsetTop;
  4809. node = node.offsetParent;
  4810. }
  4811. }
  4812. info.x.targetLength =
  4813. target === container ? target.scrollWidth : target.clientWidth;
  4814. info.y.targetLength =
  4815. target === container ? target.scrollHeight : target.clientHeight;
  4816. info.x.containerLength = container.clientWidth;
  4817. info.y.containerLength = container.clientHeight;
  4818. /**
  4819. * In development mode ensure scroll containers aren't position: static as this makes
  4820. * it difficult to measure their relative positions.
  4821. */
  4822. if (process.env.NODE_ENV !== "production") {
  4823. if (container && target && target !== container) {
  4824. motionUtils.warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.");
  4825. }
  4826. }
  4827. }
  4828. function createOnScrollHandler(element, onScroll, info, options = {}) {
  4829. return {
  4830. measure: () => measure(element, options.target, info),
  4831. update: (time) => {
  4832. updateScrollInfo(element, info, time);
  4833. if (options.offset || options.target) {
  4834. resolveOffsets(element, info, options);
  4835. }
  4836. },
  4837. notify: () => onScroll(info),
  4838. };
  4839. }
  4840. const scrollListeners = new WeakMap();
  4841. const resizeListeners = new WeakMap();
  4842. const onScrollHandlers = new WeakMap();
  4843. const getEventTarget = (element) => element === document.documentElement ? window : element;
  4844. function scrollInfo(onScroll, { container = document.documentElement, ...options } = {}) {
  4845. let containerHandlers = onScrollHandlers.get(container);
  4846. /**
  4847. * Get the onScroll handlers for this container.
  4848. * If one isn't found, create a new one.
  4849. */
  4850. if (!containerHandlers) {
  4851. containerHandlers = new Set();
  4852. onScrollHandlers.set(container, containerHandlers);
  4853. }
  4854. /**
  4855. * Create a new onScroll handler for the provided callback.
  4856. */
  4857. const info = createScrollInfo();
  4858. const containerHandler = createOnScrollHandler(container, onScroll, info, options);
  4859. containerHandlers.add(containerHandler);
  4860. /**
  4861. * Check if there's a scroll event listener for this container.
  4862. * If not, create one.
  4863. */
  4864. if (!scrollListeners.has(container)) {
  4865. const measureAll = () => {
  4866. for (const handler of containerHandlers)
  4867. handler.measure();
  4868. };
  4869. const updateAll = () => {
  4870. for (const handler of containerHandlers) {
  4871. handler.update(motionDom.frameData.timestamp);
  4872. }
  4873. };
  4874. const notifyAll = () => {
  4875. for (const handler of containerHandlers)
  4876. handler.notify();
  4877. };
  4878. const listener = () => {
  4879. motionDom.frame.read(measureAll, false, true);
  4880. motionDom.frame.read(updateAll, false, true);
  4881. motionDom.frame.update(notifyAll, false, true);
  4882. };
  4883. scrollListeners.set(container, listener);
  4884. const target = getEventTarget(container);
  4885. window.addEventListener("resize", listener, { passive: true });
  4886. if (container !== document.documentElement) {
  4887. resizeListeners.set(container, resize(container, listener));
  4888. }
  4889. target.addEventListener("scroll", listener, { passive: true });
  4890. }
  4891. const listener = scrollListeners.get(container);
  4892. motionDom.frame.read(listener, false, true);
  4893. return () => {
  4894. motionDom.cancelFrame(listener);
  4895. /**
  4896. * Check if we even have any handlers for this container.
  4897. */
  4898. const currentHandlers = onScrollHandlers.get(container);
  4899. if (!currentHandlers)
  4900. return;
  4901. currentHandlers.delete(containerHandler);
  4902. if (currentHandlers.size)
  4903. return;
  4904. /**
  4905. * If no more handlers, remove the scroll listener too.
  4906. */
  4907. const scrollListener = scrollListeners.get(container);
  4908. scrollListeners.delete(container);
  4909. if (scrollListener) {
  4910. getEventTarget(container).removeEventListener("scroll", scrollListener);
  4911. resizeListeners.get(container)?.();
  4912. window.removeEventListener("resize", scrollListener);
  4913. }
  4914. };
  4915. }
  4916. function scrollTimelineFallback({ source, container, axis = "y", }) {
  4917. // Support legacy source argument. Deprecate later.
  4918. if (source)
  4919. container = source;
  4920. // ScrollTimeline records progress as a percentage CSSUnitValue
  4921. const currentTime = { value: 0 };
  4922. const cancel = scrollInfo((info) => {
  4923. currentTime.value = info[axis].progress * 100;
  4924. }, { container, axis });
  4925. return { currentTime, cancel };
  4926. }
  4927. const timelineCache = new Map();
  4928. function getTimeline({ source, container = document.documentElement, axis = "y", } = {}) {
  4929. // Support legacy source argument. Deprecate later.
  4930. if (source)
  4931. container = source;
  4932. if (!timelineCache.has(container)) {
  4933. timelineCache.set(container, {});
  4934. }
  4935. const elementCache = timelineCache.get(container);
  4936. if (!elementCache[axis]) {
  4937. elementCache[axis] = motionDom.supportsScrollTimeline()
  4938. ? new ScrollTimeline({ source: container, axis })
  4939. : scrollTimelineFallback({ source: container, axis });
  4940. }
  4941. return elementCache[axis];
  4942. }
  4943. /**
  4944. * If the onScroll function has two arguments, it's expecting
  4945. * more specific information about the scroll from scrollInfo.
  4946. */
  4947. function isOnScrollWithInfo(onScroll) {
  4948. return onScroll.length === 2;
  4949. }
  4950. /**
  4951. * Currently, we only support element tracking with `scrollInfo`, though in
  4952. * the future we can also offer ViewTimeline support.
  4953. */
  4954. function needsElementTracking(options) {
  4955. return options && (options.target || options.offset);
  4956. }
  4957. function scrollFunction(onScroll, options) {
  4958. if (isOnScrollWithInfo(onScroll) || needsElementTracking(options)) {
  4959. return scrollInfo((info) => {
  4960. onScroll(info[options.axis].progress, info);
  4961. }, options);
  4962. }
  4963. else {
  4964. return observeTimeline(onScroll, getTimeline(options));
  4965. }
  4966. }
  4967. function scrollAnimation(animation, options) {
  4968. animation.flatten();
  4969. if (needsElementTracking(options)) {
  4970. animation.pause();
  4971. return scrollInfo((info) => {
  4972. animation.time = animation.duration * info[options.axis].progress;
  4973. }, options);
  4974. }
  4975. else {
  4976. const timeline = getTimeline(options);
  4977. if (animation.attachTimeline) {
  4978. return animation.attachTimeline(timeline, (valueAnimation) => {
  4979. valueAnimation.pause();
  4980. return observeTimeline((progress) => {
  4981. valueAnimation.time = valueAnimation.duration * progress;
  4982. }, timeline);
  4983. });
  4984. }
  4985. else {
  4986. return motionUtils.noop;
  4987. }
  4988. }
  4989. }
  4990. function scroll(onScroll, { axis = "y", ...options } = {}) {
  4991. const optionsWithDefaults = { axis, ...options };
  4992. return typeof onScroll === "function"
  4993. ? scrollFunction(onScroll, optionsWithDefaults)
  4994. : scrollAnimation(onScroll, optionsWithDefaults);
  4995. }
  4996. const thresholds = {
  4997. some: 0,
  4998. all: 1,
  4999. };
  5000. function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
  5001. const elements = motionDom.resolveElements(elementOrSelector);
  5002. const activeIntersections = new WeakMap();
  5003. const onIntersectionChange = (entries) => {
  5004. entries.forEach((entry) => {
  5005. const onEnd = activeIntersections.get(entry.target);
  5006. /**
  5007. * If there's no change to the intersection, we don't need to
  5008. * do anything here.
  5009. */
  5010. if (entry.isIntersecting === Boolean(onEnd))
  5011. return;
  5012. if (entry.isIntersecting) {
  5013. const newOnEnd = onStart(entry.target, entry);
  5014. if (typeof newOnEnd === "function") {
  5015. activeIntersections.set(entry.target, newOnEnd);
  5016. }
  5017. else {
  5018. observer.unobserve(entry.target);
  5019. }
  5020. }
  5021. else if (typeof onEnd === "function") {
  5022. onEnd(entry);
  5023. activeIntersections.delete(entry.target);
  5024. }
  5025. });
  5026. };
  5027. const observer = new IntersectionObserver(onIntersectionChange, {
  5028. root,
  5029. rootMargin,
  5030. threshold: typeof amount === "number" ? amount : thresholds[amount],
  5031. });
  5032. elements.forEach((element) => observer.observe(element));
  5033. return () => observer.disconnect();
  5034. }
  5035. function steps(numSteps, direction = "end") {
  5036. return (progress) => {
  5037. progress =
  5038. direction === "end"
  5039. ? Math.min(progress, 0.999)
  5040. : Math.max(progress, 0.001);
  5041. const expanded = progress * numSteps;
  5042. const rounded = direction === "end" ? Math.floor(expanded) : Math.ceil(expanded);
  5043. return clamp(0, 1, rounded / numSteps);
  5044. };
  5045. }
  5046. function getOriginIndex(from, total) {
  5047. if (from === "first") {
  5048. return 0;
  5049. }
  5050. else {
  5051. const lastIndex = total - 1;
  5052. return from === "last" ? lastIndex : lastIndex / 2;
  5053. }
  5054. }
  5055. function stagger(duration = 0.1, { startDelay = 0, from = 0, ease } = {}) {
  5056. return (i, total) => {
  5057. const fromIndex = typeof from === "number" ? from : getOriginIndex(from, total);
  5058. const distance = Math.abs(fromIndex - i);
  5059. let delay = duration * distance;
  5060. if (ease) {
  5061. const maxDelay = total * duration;
  5062. const easingFunction = easingDefinitionToFunction(ease);
  5063. delay = easingFunction(delay / maxDelay) * maxDelay;
  5064. }
  5065. return startDelay + delay;
  5066. };
  5067. }
  5068. /**
  5069. * Timeout defined in ms
  5070. */
  5071. function delay(callback, timeout) {
  5072. const start = motionDom.time.now();
  5073. const checkElapsed = ({ timestamp }) => {
  5074. const elapsed = timestamp - start;
  5075. if (elapsed >= timeout) {
  5076. motionDom.cancelFrame(checkElapsed);
  5077. callback(elapsed - timeout);
  5078. }
  5079. };
  5080. motionDom.frame.read(checkElapsed, true);
  5081. return () => motionDom.cancelFrame(checkElapsed);
  5082. }
  5083. function delayInSeconds(callback, timeout) {
  5084. return delay(callback, motionUtils.secondsToMilliseconds(timeout));
  5085. }
  5086. const distance = (a, b) => Math.abs(a - b);
  5087. function distance2D(a, b) {
  5088. // Multi-dimensional
  5089. const xDelta = distance(a.x, b.x);
  5090. const yDelta = distance(a.y, b.y);
  5091. return Math.sqrt(xDelta ** 2 + yDelta ** 2);
  5092. }
  5093. const isCustomValueType = (v) => {
  5094. return v && typeof v === "object" && v.mix;
  5095. };
  5096. const getMixer = (v) => (isCustomValueType(v) ? v.mix : undefined);
  5097. function transform(...args) {
  5098. const useImmediate = !Array.isArray(args[0]);
  5099. const argOffset = useImmediate ? 0 : -1;
  5100. const inputValue = args[0 + argOffset];
  5101. const inputRange = args[1 + argOffset];
  5102. const outputRange = args[2 + argOffset];
  5103. const options = args[3 + argOffset];
  5104. const interpolator = interpolate(inputRange, outputRange, {
  5105. mixer: getMixer(outputRange[0]),
  5106. ...options,
  5107. });
  5108. return useImmediate ? interpolator(inputValue) : interpolator;
  5109. }
  5110. Object.defineProperty(exports, "MotionValue", {
  5111. enumerable: true,
  5112. get: function () { return motionDom.MotionValue; }
  5113. });
  5114. Object.defineProperty(exports, "cancelFrame", {
  5115. enumerable: true,
  5116. get: function () { return motionDom.cancelFrame; }
  5117. });
  5118. Object.defineProperty(exports, "cancelSync", {
  5119. enumerable: true,
  5120. get: function () { return motionDom.cancelSync; }
  5121. });
  5122. Object.defineProperty(exports, "frame", {
  5123. enumerable: true,
  5124. get: function () { return motionDom.frame; }
  5125. });
  5126. Object.defineProperty(exports, "frameData", {
  5127. enumerable: true,
  5128. get: function () { return motionDom.frameData; }
  5129. });
  5130. Object.defineProperty(exports, "hover", {
  5131. enumerable: true,
  5132. get: function () { return motionDom.hover; }
  5133. });
  5134. Object.defineProperty(exports, "isDragActive", {
  5135. enumerable: true,
  5136. get: function () { return motionDom.isDragActive; }
  5137. });
  5138. Object.defineProperty(exports, "motionValue", {
  5139. enumerable: true,
  5140. get: function () { return motionDom.motionValue; }
  5141. });
  5142. Object.defineProperty(exports, "press", {
  5143. enumerable: true,
  5144. get: function () { return motionDom.press; }
  5145. });
  5146. Object.defineProperty(exports, "sync", {
  5147. enumerable: true,
  5148. get: function () { return motionDom.sync; }
  5149. });
  5150. Object.defineProperty(exports, "time", {
  5151. enumerable: true,
  5152. get: function () { return motionDom.time; }
  5153. });
  5154. Object.defineProperty(exports, "invariant", {
  5155. enumerable: true,
  5156. get: function () { return motionUtils.invariant; }
  5157. });
  5158. Object.defineProperty(exports, "noop", {
  5159. enumerable: true,
  5160. get: function () { return motionUtils.noop; }
  5161. });
  5162. Object.defineProperty(exports, "progress", {
  5163. enumerable: true,
  5164. get: function () { return motionUtils.progress; }
  5165. });
  5166. exports.animate = animate;
  5167. exports.animateMini = animateMini;
  5168. exports.anticipate = anticipate;
  5169. exports.backIn = backIn;
  5170. exports.backInOut = backInOut;
  5171. exports.backOut = backOut;
  5172. exports.circIn = circIn;
  5173. exports.circInOut = circInOut;
  5174. exports.circOut = circOut;
  5175. exports.clamp = clamp;
  5176. exports.createScopedAnimate = createScopedAnimate;
  5177. exports.cubicBezier = cubicBezier;
  5178. exports.delay = delayInSeconds;
  5179. exports.distance = distance;
  5180. exports.distance2D = distance2D;
  5181. exports.easeIn = easeIn;
  5182. exports.easeInOut = easeInOut;
  5183. exports.easeOut = easeOut;
  5184. exports.inView = inView;
  5185. exports.inertia = inertia;
  5186. exports.interpolate = interpolate;
  5187. exports.keyframes = keyframes;
  5188. exports.mirrorEasing = mirrorEasing;
  5189. exports.mix = mix;
  5190. exports.pipe = pipe;
  5191. exports.reverseEasing = reverseEasing;
  5192. exports.scroll = scroll;
  5193. exports.scrollInfo = scrollInfo;
  5194. exports.spring = spring;
  5195. exports.stagger = stagger;
  5196. exports.steps = steps;
  5197. exports.transform = transform;
  5198. exports.wrap = wrap;