muuri.js 259 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025
  1. /**
  2. * Muuri v0.9.3
  3. * https://muuri.dev/
  4. * Copyright (c) 2015-present, Haltu Oy
  5. * Released under the MIT license
  6. * https://github.com/haltu/muuri/blob/master/LICENSE.md
  7. * @license MIT
  8. *
  9. * Muuri Packer
  10. * Copyright (c) 2016-present, Niklas Rämö <inramo@gmail.com>
  11. * @license MIT
  12. *
  13. * Muuri Ticker / Muuri Emitter / Muuri Dragger
  14. * Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
  15. * @license MIT
  16. *
  17. * Muuri AutoScroller
  18. * Copyright (c) 2019-present, Niklas Rämö <inramo@gmail.com>
  19. * @license MIT
  20. */
  21. (function (global, factory) {
  22. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  23. typeof define === 'function' && define.amd ? define(factory) :
  24. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Muuri = factory());
  25. }(this, (function () { 'use strict';
  26. var GRID_INSTANCES = {};
  27. var ITEM_ELEMENT_MAP = typeof Map === 'function' ? new Map() : null;
  28. var ACTION_SWAP = 'swap';
  29. var ACTION_MOVE = 'move';
  30. var EVENT_SYNCHRONIZE = 'synchronize';
  31. var EVENT_LAYOUT_START = 'layoutStart';
  32. var EVENT_LAYOUT_END = 'layoutEnd';
  33. var EVENT_LAYOUT_ABORT = 'layoutAbort';
  34. var EVENT_ADD = 'add';
  35. var EVENT_REMOVE = 'remove';
  36. var EVENT_SHOW_START = 'showStart';
  37. var EVENT_SHOW_END = 'showEnd';
  38. var EVENT_HIDE_START = 'hideStart';
  39. var EVENT_HIDE_END = 'hideEnd';
  40. var EVENT_FILTER = 'filter';
  41. var EVENT_SORT = 'sort';
  42. var EVENT_MOVE = 'move';
  43. var EVENT_SEND = 'send';
  44. var EVENT_BEFORE_SEND = 'beforeSend';
  45. var EVENT_RECEIVE = 'receive';
  46. var EVENT_BEFORE_RECEIVE = 'beforeReceive';
  47. var EVENT_DRAG_INIT = 'dragInit';
  48. var EVENT_DRAG_START = 'dragStart';
  49. var EVENT_DRAG_MOVE = 'dragMove';
  50. var EVENT_DRAG_SCROLL = 'dragScroll';
  51. var EVENT_DRAG_END = 'dragEnd';
  52. var EVENT_DRAG_RELEASE_START = 'dragReleaseStart';
  53. var EVENT_DRAG_RELEASE_END = 'dragReleaseEnd';
  54. var EVENT_DESTROY = 'destroy';
  55. var HAS_TOUCH_EVENTS = 'ontouchstart' in window;
  56. var HAS_POINTER_EVENTS = !!window.PointerEvent;
  57. var HAS_MS_POINTER_EVENTS = !!window.navigator.msPointerEnabled;
  58. var MAX_SAFE_FLOAT32_INTEGER = 16777216;
  59. /**
  60. * Event emitter constructor.
  61. *
  62. * @class
  63. */
  64. function Emitter() {
  65. this._events = {};
  66. this._queue = [];
  67. this._counter = 0;
  68. this._clearOnEmit = false;
  69. }
  70. /**
  71. * Public prototype methods
  72. * ************************
  73. */
  74. /**
  75. * Bind an event listener.
  76. *
  77. * @public
  78. * @param {String} event
  79. * @param {Function} listener
  80. * @returns {Emitter}
  81. */
  82. Emitter.prototype.on = function (event, listener) {
  83. if (!this._events || !event || !listener) return this;
  84. // Get listeners queue and create it if it does not exist.
  85. var listeners = this._events[event];
  86. if (!listeners) listeners = this._events[event] = [];
  87. // Add the listener to the queue.
  88. listeners.push(listener);
  89. return this;
  90. };
  91. /**
  92. * Unbind all event listeners that match the provided listener function.
  93. *
  94. * @public
  95. * @param {String} event
  96. * @param {Function} listener
  97. * @returns {Emitter}
  98. */
  99. Emitter.prototype.off = function (event, listener) {
  100. if (!this._events || !event || !listener) return this;
  101. // Get listeners and return immediately if none is found.
  102. var listeners = this._events[event];
  103. if (!listeners || !listeners.length) return this;
  104. // Remove all matching listeners.
  105. var index;
  106. while ((index = listeners.indexOf(listener)) !== -1) {
  107. listeners.splice(index, 1);
  108. }
  109. return this;
  110. };
  111. /**
  112. * Unbind all listeners of the provided event.
  113. *
  114. * @public
  115. * @param {String} event
  116. * @returns {Emitter}
  117. */
  118. Emitter.prototype.clear = function (event) {
  119. if (!this._events || !event) return this;
  120. var listeners = this._events[event];
  121. if (listeners) {
  122. listeners.length = 0;
  123. delete this._events[event];
  124. }
  125. return this;
  126. };
  127. /**
  128. * Emit all listeners in a specified event with the provided arguments.
  129. *
  130. * @public
  131. * @param {String} event
  132. * @param {...*} [args]
  133. * @returns {Emitter}
  134. */
  135. Emitter.prototype.emit = function (event) {
  136. if (!this._events || !event) {
  137. this._clearOnEmit = false;
  138. return this;
  139. }
  140. // Get event listeners and quit early if there's no listeners.
  141. var listeners = this._events[event];
  142. if (!listeners || !listeners.length) {
  143. this._clearOnEmit = false;
  144. return this;
  145. }
  146. var queue = this._queue;
  147. var startIndex = queue.length;
  148. var argsLength = arguments.length - 1;
  149. var args;
  150. // If we have more than 3 arguments let's put the arguments in an array and
  151. // apply it to the listeners.
  152. if (argsLength > 3) {
  153. args = [];
  154. args.push.apply(args, arguments);
  155. args.shift();
  156. }
  157. // Add the current listeners to the callback queue before we process them.
  158. // This is necessary to guarantee that all of the listeners are called in
  159. // correct order even if new event listeners are removed/added during
  160. // processing and/or events are emitted during processing.
  161. queue.push.apply(queue, listeners);
  162. // Reset the event's listeners if need be.
  163. if (this._clearOnEmit) {
  164. listeners.length = 0;
  165. this._clearOnEmit = false;
  166. }
  167. // Increment queue counter. This is needed for the scenarios where emit is
  168. // triggered while the queue is already processing. We need to keep track of
  169. // how many "queue processors" there are active so that we can safely reset
  170. // the queue in the end when the last queue processor is finished.
  171. ++this._counter;
  172. // Process the queue (the specific part of it for this emit).
  173. var i = startIndex;
  174. var endIndex = queue.length;
  175. for (; i < endIndex; i++) {
  176. // prettier-ignore
  177. argsLength === 0 ? queue[i]() :
  178. argsLength === 1 ? queue[i](arguments[1]) :
  179. argsLength === 2 ? queue[i](arguments[1], arguments[2]) :
  180. argsLength === 3 ? queue[i](arguments[1], arguments[2], arguments[3]) :
  181. queue[i].apply(null, args);
  182. // Stop processing if the emitter is destroyed.
  183. if (!this._events) return this;
  184. }
  185. // Decrement queue process counter.
  186. --this._counter;
  187. // Reset the queue if there are no more queue processes running.
  188. if (!this._counter) queue.length = 0;
  189. return this;
  190. };
  191. /**
  192. * Emit all listeners in a specified event with the provided arguments and
  193. * remove the event's listeners just before calling the them. This method allows
  194. * the emitter to serve as a queue where all listeners are called only once.
  195. *
  196. * @public
  197. * @param {String} event
  198. * @param {...*} [args]
  199. * @returns {Emitter}
  200. */
  201. Emitter.prototype.burst = function () {
  202. if (!this._events) return this;
  203. this._clearOnEmit = true;
  204. this.emit.apply(this, arguments);
  205. return this;
  206. };
  207. /**
  208. * Check how many listeners there are for a specific event.
  209. *
  210. * @public
  211. * @param {String} event
  212. * @returns {Boolean}
  213. */
  214. Emitter.prototype.countListeners = function (event) {
  215. if (!this._events) return 0;
  216. var listeners = this._events[event];
  217. return listeners ? listeners.length : 0;
  218. };
  219. /**
  220. * Destroy emitter instance. Basically just removes all bound listeners.
  221. *
  222. * @public
  223. * @returns {Emitter}
  224. */
  225. Emitter.prototype.destroy = function () {
  226. if (!this._events) return this;
  227. this._queue.length = this._counter = 0;
  228. this._events = null;
  229. return this;
  230. };
  231. var pointerout = HAS_POINTER_EVENTS ? 'pointerout' : HAS_MS_POINTER_EVENTS ? 'MSPointerOut' : '';
  232. var waitDuration = 100;
  233. /**
  234. * If you happen to use Edge or IE on a touch capable device there is a
  235. * a specific case where pointercancel and pointerend events are never emitted,
  236. * even though one them should always be emitted when you release your finger
  237. * from the screen. The bug appears specifically when Muuri shifts the dragged
  238. * element's position in the DOM after pointerdown event, IE and Edge don't like
  239. * that behaviour and quite often forget to emit the pointerend/pointercancel
  240. * event. But, they do emit pointerout event so we utilize that here.
  241. * Specifically, if there has been no pointermove event within 100 milliseconds
  242. * since the last pointerout event we force cancel the drag operation. This hack
  243. * works surprisingly well 99% of the time. There is that 1% chance there still
  244. * that dragged items get stuck but it is what it is.
  245. *
  246. * @class
  247. * @param {Dragger} dragger
  248. */
  249. function EdgeHack(dragger) {
  250. if (!pointerout) return;
  251. this._dragger = dragger;
  252. this._timeout = null;
  253. this._outEvent = null;
  254. this._isActive = false;
  255. this._addBehaviour = this._addBehaviour.bind(this);
  256. this._removeBehaviour = this._removeBehaviour.bind(this);
  257. this._onTimeout = this._onTimeout.bind(this);
  258. this._resetData = this._resetData.bind(this);
  259. this._onStart = this._onStart.bind(this);
  260. this._onOut = this._onOut.bind(this);
  261. this._dragger.on('start', this._onStart);
  262. }
  263. /**
  264. * @private
  265. */
  266. EdgeHack.prototype._addBehaviour = function () {
  267. if (this._isActive) return;
  268. this._isActive = true;
  269. this._dragger.on('move', this._resetData);
  270. this._dragger.on('cancel', this._removeBehaviour);
  271. this._dragger.on('end', this._removeBehaviour);
  272. window.addEventListener(pointerout, this._onOut);
  273. };
  274. /**
  275. * @private
  276. */
  277. EdgeHack.prototype._removeBehaviour = function () {
  278. if (!this._isActive) return;
  279. this._dragger.off('move', this._resetData);
  280. this._dragger.off('cancel', this._removeBehaviour);
  281. this._dragger.off('end', this._removeBehaviour);
  282. window.removeEventListener(pointerout, this._onOut);
  283. this._resetData();
  284. this._isActive = false;
  285. };
  286. /**
  287. * @private
  288. */
  289. EdgeHack.prototype._resetData = function () {
  290. window.clearTimeout(this._timeout);
  291. this._timeout = null;
  292. this._outEvent = null;
  293. };
  294. /**
  295. * @private
  296. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  297. */
  298. EdgeHack.prototype._onStart = function (e) {
  299. if (e.pointerType === 'mouse') return;
  300. this._addBehaviour();
  301. };
  302. /**
  303. * @private
  304. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  305. */
  306. EdgeHack.prototype._onOut = function (e) {
  307. if (!this._dragger._getTrackedTouch(e)) return;
  308. this._resetData();
  309. this._outEvent = e;
  310. this._timeout = window.setTimeout(this._onTimeout, waitDuration);
  311. };
  312. /**
  313. * @private
  314. */
  315. EdgeHack.prototype._onTimeout = function () {
  316. var e = this._outEvent;
  317. this._resetData();
  318. if (this._dragger.isActive()) this._dragger._onCancel(e);
  319. };
  320. /**
  321. * @public
  322. */
  323. EdgeHack.prototype.destroy = function () {
  324. if (!pointerout) return;
  325. this._dragger.off('start', this._onStart);
  326. this._removeBehaviour();
  327. };
  328. // Playing it safe here, test all potential prefixes capitalized and lowercase.
  329. var vendorPrefixes = ['', 'webkit', 'moz', 'ms', 'o', 'Webkit', 'Moz', 'MS', 'O'];
  330. var cache = {};
  331. /**
  332. * Get prefixed CSS property name when given a non-prefixed CSS property name.
  333. * Returns null if the property is not supported at all.
  334. *
  335. * @param {CSSStyleDeclaration} style
  336. * @param {String} prop
  337. * @returns {String}
  338. */
  339. function getPrefixedPropName(style, prop) {
  340. var prefixedProp = cache[prop] || '';
  341. if (prefixedProp) return prefixedProp;
  342. var camelProp = prop[0].toUpperCase() + prop.slice(1);
  343. var i = 0;
  344. while (i < vendorPrefixes.length) {
  345. prefixedProp = vendorPrefixes[i] ? vendorPrefixes[i] + camelProp : prop;
  346. if (prefixedProp in style) {
  347. cache[prop] = prefixedProp;
  348. return prefixedProp;
  349. }
  350. ++i;
  351. }
  352. return '';
  353. }
  354. /**
  355. * Check if passive events are supported.
  356. * https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
  357. *
  358. * @returns {Boolean}
  359. */
  360. function hasPassiveEvents() {
  361. var isPassiveEventsSupported = false;
  362. try {
  363. var passiveOpts = Object.defineProperty({}, 'passive', {
  364. get: function () {
  365. isPassiveEventsSupported = true;
  366. },
  367. });
  368. window.addEventListener('testPassive', null, passiveOpts);
  369. window.removeEventListener('testPassive', null, passiveOpts);
  370. } catch (e) {}
  371. return isPassiveEventsSupported;
  372. }
  373. var ua = window.navigator.userAgent.toLowerCase();
  374. var isEdge = ua.indexOf('edge') > -1;
  375. var isIE = ua.indexOf('trident') > -1;
  376. var isFirefox = ua.indexOf('firefox') > -1;
  377. var isAndroid = ua.indexOf('android') > -1;
  378. var listenerOptions = hasPassiveEvents() ? { passive: true } : false;
  379. var taProp = 'touchAction';
  380. var taPropPrefixed = getPrefixedPropName(document.documentElement.style, taProp);
  381. var taDefaultValue = 'auto';
  382. /**
  383. * Creates a new Dragger instance for an element.
  384. *
  385. * @public
  386. * @class
  387. * @param {HTMLElement} element
  388. * @param {Object} [cssProps]
  389. */
  390. function Dragger(element, cssProps) {
  391. this._element = element;
  392. this._emitter = new Emitter();
  393. this._isDestroyed = false;
  394. this._cssProps = {};
  395. this._touchAction = '';
  396. this._isActive = false;
  397. this._pointerId = null;
  398. this._startTime = 0;
  399. this._startX = 0;
  400. this._startY = 0;
  401. this._currentX = 0;
  402. this._currentY = 0;
  403. this._onStart = this._onStart.bind(this);
  404. this._onMove = this._onMove.bind(this);
  405. this._onCancel = this._onCancel.bind(this);
  406. this._onEnd = this._onEnd.bind(this);
  407. // Can't believe had to build a freaking class for a hack!
  408. this._edgeHack = null;
  409. if ((isEdge || isIE) && (HAS_POINTER_EVENTS || HAS_MS_POINTER_EVENTS)) {
  410. this._edgeHack = new EdgeHack(this);
  411. }
  412. // Apply initial CSS props.
  413. this.setCssProps(cssProps);
  414. // If touch action was not provided with initial CSS props let's assume it's
  415. // auto.
  416. if (!this._touchAction) {
  417. this.setTouchAction(taDefaultValue);
  418. }
  419. // Prevent native link/image dragging for the item and it's children.
  420. element.addEventListener('dragstart', Dragger._preventDefault, false);
  421. // Listen to start event.
  422. element.addEventListener(Dragger._inputEvents.start, this._onStart, listenerOptions);
  423. }
  424. /**
  425. * Protected properties
  426. * ********************
  427. */
  428. Dragger._pointerEvents = {
  429. start: 'pointerdown',
  430. move: 'pointermove',
  431. cancel: 'pointercancel',
  432. end: 'pointerup',
  433. };
  434. Dragger._msPointerEvents = {
  435. start: 'MSPointerDown',
  436. move: 'MSPointerMove',
  437. cancel: 'MSPointerCancel',
  438. end: 'MSPointerUp',
  439. };
  440. Dragger._touchEvents = {
  441. start: 'touchstart',
  442. move: 'touchmove',
  443. cancel: 'touchcancel',
  444. end: 'touchend',
  445. };
  446. Dragger._mouseEvents = {
  447. start: 'mousedown',
  448. move: 'mousemove',
  449. cancel: '',
  450. end: 'mouseup',
  451. };
  452. Dragger._inputEvents = (function () {
  453. if (HAS_TOUCH_EVENTS) return Dragger._touchEvents;
  454. if (HAS_POINTER_EVENTS) return Dragger._pointerEvents;
  455. if (HAS_MS_POINTER_EVENTS) return Dragger._msPointerEvents;
  456. return Dragger._mouseEvents;
  457. })();
  458. Dragger._emitter = new Emitter();
  459. Dragger._emitterEvents = {
  460. start: 'start',
  461. move: 'move',
  462. end: 'end',
  463. cancel: 'cancel',
  464. };
  465. Dragger._activeInstances = [];
  466. /**
  467. * Protected static methods
  468. * ************************
  469. */
  470. Dragger._preventDefault = function (e) {
  471. if (e.preventDefault && e.cancelable !== false) e.preventDefault();
  472. };
  473. Dragger._activateInstance = function (instance) {
  474. var index = Dragger._activeInstances.indexOf(instance);
  475. if (index > -1) return;
  476. Dragger._activeInstances.push(instance);
  477. Dragger._emitter.on(Dragger._emitterEvents.move, instance._onMove);
  478. Dragger._emitter.on(Dragger._emitterEvents.cancel, instance._onCancel);
  479. Dragger._emitter.on(Dragger._emitterEvents.end, instance._onEnd);
  480. if (Dragger._activeInstances.length === 1) {
  481. Dragger._bindListeners();
  482. }
  483. };
  484. Dragger._deactivateInstance = function (instance) {
  485. var index = Dragger._activeInstances.indexOf(instance);
  486. if (index === -1) return;
  487. Dragger._activeInstances.splice(index, 1);
  488. Dragger._emitter.off(Dragger._emitterEvents.move, instance._onMove);
  489. Dragger._emitter.off(Dragger._emitterEvents.cancel, instance._onCancel);
  490. Dragger._emitter.off(Dragger._emitterEvents.end, instance._onEnd);
  491. if (!Dragger._activeInstances.length) {
  492. Dragger._unbindListeners();
  493. }
  494. };
  495. Dragger._bindListeners = function () {
  496. window.addEventListener(Dragger._inputEvents.move, Dragger._onMove, listenerOptions);
  497. window.addEventListener(Dragger._inputEvents.end, Dragger._onEnd, listenerOptions);
  498. if (Dragger._inputEvents.cancel) {
  499. window.addEventListener(Dragger._inputEvents.cancel, Dragger._onCancel, listenerOptions);
  500. }
  501. };
  502. Dragger._unbindListeners = function () {
  503. window.removeEventListener(Dragger._inputEvents.move, Dragger._onMove, listenerOptions);
  504. window.removeEventListener(Dragger._inputEvents.end, Dragger._onEnd, listenerOptions);
  505. if (Dragger._inputEvents.cancel) {
  506. window.removeEventListener(Dragger._inputEvents.cancel, Dragger._onCancel, listenerOptions);
  507. }
  508. };
  509. Dragger._getEventPointerId = function (event) {
  510. // If we have pointer id available let's use it.
  511. if (typeof event.pointerId === 'number') {
  512. return event.pointerId;
  513. }
  514. // For touch events let's get the first changed touch's identifier.
  515. if (event.changedTouches) {
  516. return event.changedTouches[0] ? event.changedTouches[0].identifier : null;
  517. }
  518. // For mouse/other events let's provide a static id.
  519. return 1;
  520. };
  521. Dragger._getTouchById = function (event, id) {
  522. // If we have a pointer event return the whole event if there's a match, and
  523. // null otherwise.
  524. if (typeof event.pointerId === 'number') {
  525. return event.pointerId === id ? event : null;
  526. }
  527. // For touch events let's check if there's a changed touch object that matches
  528. // the pointerId in which case return the touch object.
  529. if (event.changedTouches) {
  530. for (var i = 0; i < event.changedTouches.length; i++) {
  531. if (event.changedTouches[i].identifier === id) {
  532. return event.changedTouches[i];
  533. }
  534. }
  535. return null;
  536. }
  537. // For mouse/other events let's assume there's only one pointer and just
  538. // return the event.
  539. return event;
  540. };
  541. Dragger._onMove = function (e) {
  542. Dragger._emitter.emit(Dragger._emitterEvents.move, e);
  543. };
  544. Dragger._onCancel = function (e) {
  545. Dragger._emitter.emit(Dragger._emitterEvents.cancel, e);
  546. };
  547. Dragger._onEnd = function (e) {
  548. Dragger._emitter.emit(Dragger._emitterEvents.end, e);
  549. };
  550. /**
  551. * Private prototype methods
  552. * *************************
  553. */
  554. /**
  555. * Reset current drag operation (if any).
  556. *
  557. * @private
  558. */
  559. Dragger.prototype._reset = function () {
  560. this._pointerId = null;
  561. this._startTime = 0;
  562. this._startX = 0;
  563. this._startY = 0;
  564. this._currentX = 0;
  565. this._currentY = 0;
  566. this._isActive = false;
  567. Dragger._deactivateInstance(this);
  568. };
  569. /**
  570. * Create a custom dragger event from a raw event.
  571. *
  572. * @private
  573. * @param {String} type
  574. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  575. * @returns {Object}
  576. */
  577. Dragger.prototype._createEvent = function (type, e) {
  578. var touch = this._getTrackedTouch(e);
  579. return {
  580. // Hammer.js compatibility interface.
  581. type: type,
  582. srcEvent: e,
  583. distance: this.getDistance(),
  584. deltaX: this.getDeltaX(),
  585. deltaY: this.getDeltaY(),
  586. deltaTime: type === Dragger._emitterEvents.start ? 0 : this.getDeltaTime(),
  587. isFirst: type === Dragger._emitterEvents.start,
  588. isFinal: type === Dragger._emitterEvents.end || type === Dragger._emitterEvents.cancel,
  589. pointerType: e.pointerType || (e.touches ? 'touch' : 'mouse'),
  590. // Partial Touch API interface.
  591. identifier: this._pointerId,
  592. screenX: touch.screenX,
  593. screenY: touch.screenY,
  594. clientX: touch.clientX,
  595. clientY: touch.clientY,
  596. pageX: touch.pageX,
  597. pageY: touch.pageY,
  598. target: touch.target,
  599. };
  600. };
  601. /**
  602. * Emit a raw event as dragger event internally.
  603. *
  604. * @private
  605. * @param {String} type
  606. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  607. */
  608. Dragger.prototype._emit = function (type, e) {
  609. this._emitter.emit(type, this._createEvent(type, e));
  610. };
  611. /**
  612. * If the provided event is a PointerEvent this method will return it if it has
  613. * the same pointerId as the instance. If the provided event is a TouchEvent
  614. * this method will try to look for a Touch instance in the changedTouches that
  615. * has an identifier matching this instance's pointerId. If the provided event
  616. * is a MouseEvent (or just any other event than PointerEvent or TouchEvent)
  617. * it will be returned immediately.
  618. *
  619. * @private
  620. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  621. * @returns {?(Touch|PointerEvent|MouseEvent)}
  622. */
  623. Dragger.prototype._getTrackedTouch = function (e) {
  624. if (this._pointerId === null) return null;
  625. return Dragger._getTouchById(e, this._pointerId);
  626. };
  627. /**
  628. * Handler for start event.
  629. *
  630. * @private
  631. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  632. */
  633. Dragger.prototype._onStart = function (e) {
  634. if (this._isDestroyed) return;
  635. // If pointer id is already assigned let's return early.
  636. if (this._pointerId !== null) return;
  637. // Get (and set) pointer id.
  638. this._pointerId = Dragger._getEventPointerId(e);
  639. if (this._pointerId === null) return;
  640. // Setup initial data and emit start event.
  641. var touch = this._getTrackedTouch(e);
  642. this._startX = this._currentX = touch.clientX;
  643. this._startY = this._currentY = touch.clientY;
  644. this._startTime = Date.now();
  645. this._isActive = true;
  646. this._emit(Dragger._emitterEvents.start, e);
  647. // If the drag procedure was not reset within the start procedure let's
  648. // activate the instance (start listening to move/cancel/end events).
  649. if (this._isActive) {
  650. Dragger._activateInstance(this);
  651. }
  652. };
  653. /**
  654. * Handler for move event.
  655. *
  656. * @private
  657. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  658. */
  659. Dragger.prototype._onMove = function (e) {
  660. var touch = this._getTrackedTouch(e);
  661. if (!touch) return;
  662. this._currentX = touch.clientX;
  663. this._currentY = touch.clientY;
  664. this._emit(Dragger._emitterEvents.move, e);
  665. };
  666. /**
  667. * Handler for cancel event.
  668. *
  669. * @private
  670. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  671. */
  672. Dragger.prototype._onCancel = function (e) {
  673. if (!this._getTrackedTouch(e)) return;
  674. this._emit(Dragger._emitterEvents.cancel, e);
  675. this._reset();
  676. };
  677. /**
  678. * Handler for end event.
  679. *
  680. * @private
  681. * @param {(PointerEvent|TouchEvent|MouseEvent)} e
  682. */
  683. Dragger.prototype._onEnd = function (e) {
  684. if (!this._getTrackedTouch(e)) return;
  685. this._emit(Dragger._emitterEvents.end, e);
  686. this._reset();
  687. };
  688. /**
  689. * Public prototype methods
  690. * ************************
  691. */
  692. /**
  693. * Check if the element is being dragged at the moment.
  694. *
  695. * @public
  696. * @returns {Boolean}
  697. */
  698. Dragger.prototype.isActive = function () {
  699. return this._isActive;
  700. };
  701. /**
  702. * Set element's touch-action CSS property.
  703. *
  704. * @public
  705. * @param {String} value
  706. */
  707. Dragger.prototype.setTouchAction = function (value) {
  708. // Store unmodified touch action value (we trust user input here).
  709. this._touchAction = value;
  710. // Set touch-action style.
  711. if (taPropPrefixed) {
  712. this._cssProps[taPropPrefixed] = '';
  713. this._element.style[taPropPrefixed] = value;
  714. }
  715. // If we have an unsupported touch-action value let's add a special listener
  716. // that prevents default action on touch start event. A dirty hack, but best
  717. // we can do for now. The other options would be to somehow polyfill the
  718. // unsupported touch action behavior with custom heuristics which sounds like
  719. // a can of worms. We do a special exception here for Firefox Android which's
  720. // touch-action does not work properly if the dragged element is moved in the
  721. // the DOM tree on touchstart.
  722. if (HAS_TOUCH_EVENTS) {
  723. this._element.removeEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
  724. if (this._element.style[taPropPrefixed] !== value || (isFirefox && isAndroid)) {
  725. this._element.addEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
  726. }
  727. }
  728. };
  729. /**
  730. * Update element's CSS properties. Accepts an object with camel cased style
  731. * props with value pairs as it's first argument.
  732. *
  733. * @public
  734. * @param {Object} [newProps]
  735. */
  736. Dragger.prototype.setCssProps = function (newProps) {
  737. if (!newProps) return;
  738. var currentProps = this._cssProps;
  739. var element = this._element;
  740. var prop;
  741. var prefixedProp;
  742. // Reset current props.
  743. for (prop in currentProps) {
  744. element.style[prop] = currentProps[prop];
  745. delete currentProps[prop];
  746. }
  747. // Set new props.
  748. for (prop in newProps) {
  749. // Make sure we have a value for the prop.
  750. if (!newProps[prop]) continue;
  751. // Special handling for touch-action.
  752. if (prop === taProp) {
  753. this.setTouchAction(newProps[prop]);
  754. continue;
  755. }
  756. // Get prefixed prop and skip if it does not exist.
  757. prefixedProp = getPrefixedPropName(element.style, prop);
  758. if (!prefixedProp) continue;
  759. // Store the prop and add the style.
  760. currentProps[prefixedProp] = '';
  761. element.style[prefixedProp] = newProps[prop];
  762. }
  763. };
  764. /**
  765. * How much the pointer has moved on x-axis from start position, in pixels.
  766. * Positive value indicates movement from left to right.
  767. *
  768. * @public
  769. * @returns {Number}
  770. */
  771. Dragger.prototype.getDeltaX = function () {
  772. return this._currentX - this._startX;
  773. };
  774. /**
  775. * How much the pointer has moved on y-axis from start position, in pixels.
  776. * Positive value indicates movement from top to bottom.
  777. *
  778. * @public
  779. * @returns {Number}
  780. */
  781. Dragger.prototype.getDeltaY = function () {
  782. return this._currentY - this._startY;
  783. };
  784. /**
  785. * How far (in pixels) has pointer moved from start position.
  786. *
  787. * @public
  788. * @returns {Number}
  789. */
  790. Dragger.prototype.getDistance = function () {
  791. var x = this.getDeltaX();
  792. var y = this.getDeltaY();
  793. return Math.sqrt(x * x + y * y);
  794. };
  795. /**
  796. * How long has pointer been dragged.
  797. *
  798. * @public
  799. * @returns {Number}
  800. */
  801. Dragger.prototype.getDeltaTime = function () {
  802. return this._startTime ? Date.now() - this._startTime : 0;
  803. };
  804. /**
  805. * Bind drag event listeners.
  806. *
  807. * @public
  808. * @param {String} eventName
  809. * - 'start', 'move', 'cancel' or 'end'.
  810. * @param {Function} listener
  811. */
  812. Dragger.prototype.on = function (eventName, listener) {
  813. this._emitter.on(eventName, listener);
  814. };
  815. /**
  816. * Unbind drag event listeners.
  817. *
  818. * @public
  819. * @param {String} eventName
  820. * - 'start', 'move', 'cancel' or 'end'.
  821. * @param {Function} listener
  822. */
  823. Dragger.prototype.off = function (eventName, listener) {
  824. this._emitter.off(eventName, listener);
  825. };
  826. /**
  827. * Destroy the instance and unbind all drag event listeners.
  828. *
  829. * @public
  830. */
  831. Dragger.prototype.destroy = function () {
  832. if (this._isDestroyed) return;
  833. var element = this._element;
  834. if (this._edgeHack) this._edgeHack.destroy();
  835. // Reset data and deactivate the instance.
  836. this._reset();
  837. // Destroy emitter.
  838. this._emitter.destroy();
  839. // Unbind event handlers.
  840. element.removeEventListener(Dragger._inputEvents.start, this._onStart, listenerOptions);
  841. element.removeEventListener('dragstart', Dragger._preventDefault, false);
  842. element.removeEventListener(Dragger._touchEvents.start, Dragger._preventDefault, true);
  843. // Reset styles.
  844. for (var prop in this._cssProps) {
  845. element.style[prop] = this._cssProps[prop];
  846. delete this._cssProps[prop];
  847. }
  848. // Reset data.
  849. this._element = null;
  850. // Mark as destroyed.
  851. this._isDestroyed = true;
  852. };
  853. var dt = 1000 / 60;
  854. var raf = (
  855. window.requestAnimationFrame ||
  856. window.webkitRequestAnimationFrame ||
  857. window.mozRequestAnimationFrame ||
  858. window.msRequestAnimationFrame ||
  859. function (callback) {
  860. return this.setTimeout(function () {
  861. callback(Date.now());
  862. }, dt);
  863. }
  864. ).bind(window);
  865. /**
  866. * A ticker system for handling DOM reads and writes in an efficient way.
  867. *
  868. * @class
  869. */
  870. function Ticker(numLanes) {
  871. this._nextStep = null;
  872. this._lanes = [];
  873. this._stepQueue = [];
  874. this._stepCallbacks = {};
  875. this._step = this._step.bind(this);
  876. for (var i = 0; i < numLanes; i++) {
  877. this._lanes.push(new TickerLane());
  878. }
  879. }
  880. Ticker.prototype._step = function (time) {
  881. var lanes = this._lanes;
  882. var stepQueue = this._stepQueue;
  883. var stepCallbacks = this._stepCallbacks;
  884. var i, j, id, laneQueue, laneCallbacks, laneIndices;
  885. this._nextStep = null;
  886. for (i = 0; i < lanes.length; i++) {
  887. laneQueue = lanes[i].queue;
  888. laneCallbacks = lanes[i].callbacks;
  889. laneIndices = lanes[i].indices;
  890. for (j = 0; j < laneQueue.length; j++) {
  891. id = laneQueue[j];
  892. if (!id) continue;
  893. stepQueue.push(id);
  894. stepCallbacks[id] = laneCallbacks[id];
  895. delete laneCallbacks[id];
  896. delete laneIndices[id];
  897. }
  898. laneQueue.length = 0;
  899. }
  900. for (i = 0; i < stepQueue.length; i++) {
  901. id = stepQueue[i];
  902. if (stepCallbacks[id]) stepCallbacks[id](time);
  903. delete stepCallbacks[id];
  904. }
  905. stepQueue.length = 0;
  906. };
  907. Ticker.prototype.add = function (laneIndex, id, callback) {
  908. this._lanes[laneIndex].add(id, callback);
  909. if (!this._nextStep) this._nextStep = raf(this._step);
  910. };
  911. Ticker.prototype.remove = function (laneIndex, id) {
  912. this._lanes[laneIndex].remove(id);
  913. };
  914. /**
  915. * A lane for ticker.
  916. *
  917. * @class
  918. */
  919. function TickerLane() {
  920. this.queue = [];
  921. this.indices = {};
  922. this.callbacks = {};
  923. }
  924. TickerLane.prototype.add = function (id, callback) {
  925. var index = this.indices[id];
  926. if (index !== undefined) this.queue[index] = undefined;
  927. this.queue.push(id);
  928. this.callbacks[id] = callback;
  929. this.indices[id] = this.queue.length - 1;
  930. };
  931. TickerLane.prototype.remove = function (id) {
  932. var index = this.indices[id];
  933. if (index === undefined) return;
  934. this.queue[index] = undefined;
  935. delete this.callbacks[id];
  936. delete this.indices[id];
  937. };
  938. var LAYOUT_READ = 'layoutRead';
  939. var LAYOUT_WRITE = 'layoutWrite';
  940. var VISIBILITY_READ = 'visibilityRead';
  941. var VISIBILITY_WRITE = 'visibilityWrite';
  942. var DRAG_START_READ = 'dragStartRead';
  943. var DRAG_START_WRITE = 'dragStartWrite';
  944. var DRAG_MOVE_READ = 'dragMoveRead';
  945. var DRAG_MOVE_WRITE = 'dragMoveWrite';
  946. var DRAG_SCROLL_READ = 'dragScrollRead';
  947. var DRAG_SCROLL_WRITE = 'dragScrollWrite';
  948. var DRAG_SORT_READ = 'dragSortRead';
  949. var PLACEHOLDER_LAYOUT_READ = 'placeholderLayoutRead';
  950. var PLACEHOLDER_LAYOUT_WRITE = 'placeholderLayoutWrite';
  951. var PLACEHOLDER_RESIZE_WRITE = 'placeholderResizeWrite';
  952. var AUTO_SCROLL_READ = 'autoScrollRead';
  953. var AUTO_SCROLL_WRITE = 'autoScrollWrite';
  954. var DEBOUNCE_READ = 'debounceRead';
  955. var LANE_READ = 0;
  956. var LANE_READ_TAIL = 1;
  957. var LANE_WRITE = 2;
  958. var ticker = new Ticker(3);
  959. function addLayoutTick(itemId, read, write) {
  960. ticker.add(LANE_READ, LAYOUT_READ + itemId, read);
  961. ticker.add(LANE_WRITE, LAYOUT_WRITE + itemId, write);
  962. }
  963. function cancelLayoutTick(itemId) {
  964. ticker.remove(LANE_READ, LAYOUT_READ + itemId);
  965. ticker.remove(LANE_WRITE, LAYOUT_WRITE + itemId);
  966. }
  967. function addVisibilityTick(itemId, read, write) {
  968. ticker.add(LANE_READ, VISIBILITY_READ + itemId, read);
  969. ticker.add(LANE_WRITE, VISIBILITY_WRITE + itemId, write);
  970. }
  971. function cancelVisibilityTick(itemId) {
  972. ticker.remove(LANE_READ, VISIBILITY_READ + itemId);
  973. ticker.remove(LANE_WRITE, VISIBILITY_WRITE + itemId);
  974. }
  975. function addDragStartTick(itemId, read, write) {
  976. ticker.add(LANE_READ, DRAG_START_READ + itemId, read);
  977. ticker.add(LANE_WRITE, DRAG_START_WRITE + itemId, write);
  978. }
  979. function cancelDragStartTick(itemId) {
  980. ticker.remove(LANE_READ, DRAG_START_READ + itemId);
  981. ticker.remove(LANE_WRITE, DRAG_START_WRITE + itemId);
  982. }
  983. function addDragMoveTick(itemId, read, write) {
  984. ticker.add(LANE_READ, DRAG_MOVE_READ + itemId, read);
  985. ticker.add(LANE_WRITE, DRAG_MOVE_WRITE + itemId, write);
  986. }
  987. function cancelDragMoveTick(itemId) {
  988. ticker.remove(LANE_READ, DRAG_MOVE_READ + itemId);
  989. ticker.remove(LANE_WRITE, DRAG_MOVE_WRITE + itemId);
  990. }
  991. function addDragScrollTick(itemId, read, write) {
  992. ticker.add(LANE_READ, DRAG_SCROLL_READ + itemId, read);
  993. ticker.add(LANE_WRITE, DRAG_SCROLL_WRITE + itemId, write);
  994. }
  995. function cancelDragScrollTick(itemId) {
  996. ticker.remove(LANE_READ, DRAG_SCROLL_READ + itemId);
  997. ticker.remove(LANE_WRITE, DRAG_SCROLL_WRITE + itemId);
  998. }
  999. function addDragSortTick(itemId, read) {
  1000. ticker.add(LANE_READ_TAIL, DRAG_SORT_READ + itemId, read);
  1001. }
  1002. function cancelDragSortTick(itemId) {
  1003. ticker.remove(LANE_READ_TAIL, DRAG_SORT_READ + itemId);
  1004. }
  1005. function addPlaceholderLayoutTick(itemId, read, write) {
  1006. ticker.add(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId, read);
  1007. ticker.add(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId, write);
  1008. }
  1009. function cancelPlaceholderLayoutTick(itemId) {
  1010. ticker.remove(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId);
  1011. ticker.remove(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId);
  1012. }
  1013. function addPlaceholderResizeTick(itemId, write) {
  1014. ticker.add(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId, write);
  1015. }
  1016. function cancelPlaceholderResizeTick(itemId) {
  1017. ticker.remove(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId);
  1018. }
  1019. function addAutoScrollTick(read, write) {
  1020. ticker.add(LANE_READ, AUTO_SCROLL_READ, read);
  1021. ticker.add(LANE_WRITE, AUTO_SCROLL_WRITE, write);
  1022. }
  1023. function cancelAutoScrollTick() {
  1024. ticker.remove(LANE_READ, AUTO_SCROLL_READ);
  1025. ticker.remove(LANE_WRITE, AUTO_SCROLL_WRITE);
  1026. }
  1027. function addDebounceTick(debounceId, read) {
  1028. ticker.add(LANE_READ, DEBOUNCE_READ + debounceId, read);
  1029. }
  1030. function cancelDebounceTick(debounceId) {
  1031. ticker.remove(LANE_READ, DEBOUNCE_READ + debounceId);
  1032. }
  1033. var AXIS_X = 1;
  1034. var AXIS_Y = 2;
  1035. var FORWARD = 4;
  1036. var BACKWARD = 8;
  1037. var LEFT = AXIS_X | BACKWARD;
  1038. var RIGHT = AXIS_X | FORWARD;
  1039. var UP = AXIS_Y | BACKWARD;
  1040. var DOWN = AXIS_Y | FORWARD;
  1041. var functionType = 'function';
  1042. /**
  1043. * Check if a value is a function.
  1044. *
  1045. * @param {*} val
  1046. * @returns {Boolean}
  1047. */
  1048. function isFunction(val) {
  1049. return typeof val === functionType;
  1050. }
  1051. var isWeakMapSupported = typeof WeakMap === 'function';
  1052. var cache$1 = isWeakMapSupported ? new WeakMap() : null;
  1053. var cacheInterval = 3000;
  1054. var cacheTimer;
  1055. var canClearCache = true;
  1056. var clearCache = function () {
  1057. if (canClearCache) {
  1058. cacheTimer = window.clearInterval(cacheTimer);
  1059. cache$1 = isWeakMapSupported ? new WeakMap() : null;
  1060. } else {
  1061. canClearCache = true;
  1062. }
  1063. };
  1064. /**
  1065. * Returns the computed value of an element's style property as a string.
  1066. *
  1067. * @param {HTMLElement} element
  1068. * @param {String} style
  1069. * @returns {String}
  1070. */
  1071. function getStyle(element, style) {
  1072. var styles = cache$1 && cache$1.get(element);
  1073. if (!styles) {
  1074. styles = window.getComputedStyle(element, null);
  1075. if (cache$1) cache$1.set(element, styles);
  1076. }
  1077. if (cache$1) {
  1078. if (!cacheTimer) {
  1079. cacheTimer = window.setInterval(clearCache, cacheInterval);
  1080. } else {
  1081. canClearCache = false;
  1082. }
  1083. }
  1084. return styles.getPropertyValue(style);
  1085. }
  1086. /**
  1087. * Returns the computed value of an element's style property transformed into
  1088. * a float value.
  1089. *
  1090. * @param {HTMLElement} el
  1091. * @param {String} style
  1092. * @returns {Number}
  1093. */
  1094. function getStyleAsFloat(el, style) {
  1095. return parseFloat(getStyle(el, style)) || 0;
  1096. }
  1097. var DOC_ELEM = document.documentElement;
  1098. var BODY = document.body;
  1099. var THRESHOLD_DATA = { value: 0, offset: 0 };
  1100. /**
  1101. * @param {HTMLElement|Window} element
  1102. * @returns {HTMLElement|Window}
  1103. */
  1104. function getScrollElement(element) {
  1105. if (element === window || element === DOC_ELEM || element === BODY) {
  1106. return window;
  1107. } else {
  1108. return element;
  1109. }
  1110. }
  1111. /**
  1112. * @param {HTMLElement|Window} element
  1113. * @returns {Number}
  1114. */
  1115. function getScrollLeft(element) {
  1116. return element === window ? element.pageXOffset : element.scrollLeft;
  1117. }
  1118. /**
  1119. * @param {HTMLElement|Window} element
  1120. * @returns {Number}
  1121. */
  1122. function getScrollTop(element) {
  1123. return element === window ? element.pageYOffset : element.scrollTop;
  1124. }
  1125. /**
  1126. * @param {HTMLElement|Window} element
  1127. * @returns {Number}
  1128. */
  1129. function getScrollLeftMax(element) {
  1130. if (element === window) {
  1131. return DOC_ELEM.scrollWidth - DOC_ELEM.clientWidth;
  1132. } else {
  1133. return element.scrollWidth - element.clientWidth;
  1134. }
  1135. }
  1136. /**
  1137. * @param {HTMLElement|Window} element
  1138. * @returns {Number}
  1139. */
  1140. function getScrollTopMax(element) {
  1141. if (element === window) {
  1142. return DOC_ELEM.scrollHeight - DOC_ELEM.clientHeight;
  1143. } else {
  1144. return element.scrollHeight - element.clientHeight;
  1145. }
  1146. }
  1147. /**
  1148. * Get window's or element's client rectangle data relative to the element's
  1149. * content dimensions (includes inner size + padding, excludes scrollbars,
  1150. * borders and margins).
  1151. *
  1152. * @param {HTMLElement|Window} element
  1153. * @returns {Rectangle}
  1154. */
  1155. function getContentRect(element, result) {
  1156. result = result || {};
  1157. if (element === window) {
  1158. result.width = DOC_ELEM.clientWidth;
  1159. result.height = DOC_ELEM.clientHeight;
  1160. result.left = 0;
  1161. result.right = result.width;
  1162. result.top = 0;
  1163. result.bottom = result.height;
  1164. } else {
  1165. var bcr = element.getBoundingClientRect();
  1166. var borderLeft = element.clientLeft || getStyleAsFloat(element, 'border-left-width');
  1167. var borderTop = element.clientTop || getStyleAsFloat(element, 'border-top-width');
  1168. result.width = element.clientWidth;
  1169. result.height = element.clientHeight;
  1170. result.left = bcr.left + borderLeft;
  1171. result.right = result.left + result.width;
  1172. result.top = bcr.top + borderTop;
  1173. result.bottom = result.top + result.height;
  1174. }
  1175. return result;
  1176. }
  1177. /**
  1178. * @param {Item} item
  1179. * @returns {Object}
  1180. */
  1181. function getItemAutoScrollSettings(item) {
  1182. return item._drag._getGrid()._settings.dragAutoScroll;
  1183. }
  1184. /**
  1185. * @param {Item} item
  1186. */
  1187. function prepareItemScrollSync(item) {
  1188. if (!item._drag) return;
  1189. item._drag._prepareScroll();
  1190. }
  1191. /**
  1192. * @param {Item} item
  1193. */
  1194. function applyItemScrollSync(item) {
  1195. if (!item._drag || !item._isActive) return;
  1196. var drag = item._drag;
  1197. drag._scrollDiffX = drag._scrollDiffY = 0;
  1198. item._setTranslate(drag._left, drag._top);
  1199. }
  1200. /**
  1201. * Compute threshold value and edge offset.
  1202. *
  1203. * @param {Number} threshold
  1204. * @param {Number} safeZone
  1205. * @param {Number} itemSize
  1206. * @param {Number} targetSize
  1207. * @returns {Object}
  1208. */
  1209. function computeThreshold(threshold, safeZone, itemSize, targetSize) {
  1210. THRESHOLD_DATA.value = Math.min(targetSize / 2, threshold);
  1211. THRESHOLD_DATA.offset =
  1212. Math.max(0, itemSize + THRESHOLD_DATA.value * 2 + targetSize * safeZone - targetSize) / 2;
  1213. return THRESHOLD_DATA;
  1214. }
  1215. function ScrollRequest() {
  1216. this.reset();
  1217. }
  1218. ScrollRequest.prototype.reset = function () {
  1219. if (this.isActive) this.onStop();
  1220. this.item = null;
  1221. this.element = null;
  1222. this.isActive = false;
  1223. this.isEnding = false;
  1224. this.direction = null;
  1225. this.value = null;
  1226. this.maxValue = 0;
  1227. this.threshold = 0;
  1228. this.distance = 0;
  1229. this.speed = 0;
  1230. this.duration = 0;
  1231. this.action = null;
  1232. };
  1233. ScrollRequest.prototype.hasReachedEnd = function () {
  1234. return FORWARD & this.direction ? this.value >= this.maxValue : this.value <= 0;
  1235. };
  1236. ScrollRequest.prototype.computeCurrentScrollValue = function () {
  1237. if (this.value === null) {
  1238. return AXIS_X & this.direction ? getScrollLeft(this.element) : getScrollTop(this.element);
  1239. }
  1240. return Math.max(0, Math.min(this.value, this.maxValue));
  1241. };
  1242. ScrollRequest.prototype.computeNextScrollValue = function (deltaTime) {
  1243. var delta = this.speed * (deltaTime / 1000);
  1244. var nextValue = FORWARD & this.direction ? this.value + delta : this.value - delta;
  1245. return Math.max(0, Math.min(nextValue, this.maxValue));
  1246. };
  1247. ScrollRequest.prototype.computeSpeed = (function () {
  1248. var data = {
  1249. direction: null,
  1250. threshold: 0,
  1251. distance: 0,
  1252. value: 0,
  1253. maxValue: 0,
  1254. deltaTime: 0,
  1255. duration: 0,
  1256. isEnding: false,
  1257. };
  1258. return function (deltaTime) {
  1259. var item = this.item;
  1260. var speed = getItemAutoScrollSettings(item).speed;
  1261. if (isFunction(speed)) {
  1262. data.direction = this.direction;
  1263. data.threshold = this.threshold;
  1264. data.distance = this.distance;
  1265. data.value = this.value;
  1266. data.maxValue = this.maxValue;
  1267. data.duration = this.duration;
  1268. data.speed = this.speed;
  1269. data.deltaTime = deltaTime;
  1270. data.isEnding = this.isEnding;
  1271. return speed(item, this.element, data);
  1272. } else {
  1273. return speed;
  1274. }
  1275. };
  1276. })();
  1277. ScrollRequest.prototype.tick = function (deltaTime) {
  1278. if (!this.isActive) {
  1279. this.isActive = true;
  1280. this.onStart();
  1281. }
  1282. this.value = this.computeCurrentScrollValue();
  1283. this.speed = this.computeSpeed(deltaTime);
  1284. this.value = this.computeNextScrollValue(deltaTime);
  1285. this.duration += deltaTime;
  1286. return this.value;
  1287. };
  1288. ScrollRequest.prototype.onStart = function () {
  1289. var item = this.item;
  1290. var onStart = getItemAutoScrollSettings(item).onStart;
  1291. if (isFunction(onStart)) onStart(item, this.element, this.direction);
  1292. };
  1293. ScrollRequest.prototype.onStop = function () {
  1294. var item = this.item;
  1295. var onStop = getItemAutoScrollSettings(item).onStop;
  1296. if (isFunction(onStop)) onStop(item, this.element, this.direction);
  1297. // Manually nudge sort to happen. There's a good chance that the item is still
  1298. // after the scroll stops which means that the next sort will be triggered
  1299. // only after the item is moved or it's parent scrolled.
  1300. if (item._drag) item._drag.sort();
  1301. };
  1302. function ScrollAction() {
  1303. this.element = null;
  1304. this.requestX = null;
  1305. this.requestY = null;
  1306. this.scrollLeft = 0;
  1307. this.scrollTop = 0;
  1308. }
  1309. ScrollAction.prototype.reset = function () {
  1310. if (this.requestX) this.requestX.action = null;
  1311. if (this.requestY) this.requestY.action = null;
  1312. this.element = null;
  1313. this.requestX = null;
  1314. this.requestY = null;
  1315. this.scrollLeft = 0;
  1316. this.scrollTop = 0;
  1317. };
  1318. ScrollAction.prototype.addRequest = function (request) {
  1319. if (AXIS_X & request.direction) {
  1320. this.removeRequest(this.requestX);
  1321. this.requestX = request;
  1322. } else {
  1323. this.removeRequest(this.requestY);
  1324. this.requestY = request;
  1325. }
  1326. request.action = this;
  1327. };
  1328. ScrollAction.prototype.removeRequest = function (request) {
  1329. if (!request) return;
  1330. if (this.requestX === request) {
  1331. this.requestX = null;
  1332. request.action = null;
  1333. } else if (this.requestY === request) {
  1334. this.requestY = null;
  1335. request.action = null;
  1336. }
  1337. };
  1338. ScrollAction.prototype.computeScrollValues = function () {
  1339. this.scrollLeft = this.requestX ? this.requestX.value : getScrollLeft(this.element);
  1340. this.scrollTop = this.requestY ? this.requestY.value : getScrollTop(this.element);
  1341. };
  1342. ScrollAction.prototype.scroll = function () {
  1343. var element = this.element;
  1344. if (!element) return;
  1345. if (element.scrollTo) {
  1346. element.scrollTo(this.scrollLeft, this.scrollTop);
  1347. } else {
  1348. element.scrollLeft = this.scrollLeft;
  1349. element.scrollTop = this.scrollTop;
  1350. }
  1351. };
  1352. function Pool(createItem, releaseItem) {
  1353. this.pool = [];
  1354. this.createItem = createItem;
  1355. this.releaseItem = releaseItem;
  1356. }
  1357. Pool.prototype.pick = function () {
  1358. return this.pool.pop() || this.createItem();
  1359. };
  1360. Pool.prototype.release = function (item) {
  1361. this.releaseItem(item);
  1362. if (this.pool.indexOf(item) !== -1) return;
  1363. this.pool.push(item);
  1364. };
  1365. Pool.prototype.reset = function () {
  1366. this.pool.length = 0;
  1367. };
  1368. /**
  1369. * Check if two rectangles are overlapping.
  1370. *
  1371. * @param {Object} a
  1372. * @param {Object} b
  1373. * @returns {Number}
  1374. */
  1375. function isOverlapping(a, b) {
  1376. return !(
  1377. a.left + a.width <= b.left ||
  1378. b.left + b.width <= a.left ||
  1379. a.top + a.height <= b.top ||
  1380. b.top + b.height <= a.top
  1381. );
  1382. }
  1383. /**
  1384. * Calculate intersection area between two rectangle.
  1385. *
  1386. * @param {Object} a
  1387. * @param {Object} b
  1388. * @returns {Number}
  1389. */
  1390. function getIntersectionArea(a, b) {
  1391. if (!isOverlapping(a, b)) return 0;
  1392. var width = Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left);
  1393. var height = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
  1394. return width * height;
  1395. }
  1396. /**
  1397. * Calculate how many percent the intersection area of two rectangles is from
  1398. * the maximum potential intersection area between the rectangles.
  1399. *
  1400. * @param {Object} a
  1401. * @param {Object} b
  1402. * @returns {Number}
  1403. */
  1404. function getIntersectionScore(a, b) {
  1405. var area = getIntersectionArea(a, b);
  1406. if (!area) return 0;
  1407. var maxArea = Math.min(a.width, b.width) * Math.min(a.height, b.height);
  1408. return (area / maxArea) * 100;
  1409. }
  1410. var RECT_1 = {
  1411. width: 0,
  1412. height: 0,
  1413. left: 0,
  1414. right: 0,
  1415. top: 0,
  1416. bottom: 0,
  1417. };
  1418. var RECT_2 = {
  1419. width: 0,
  1420. height: 0,
  1421. left: 0,
  1422. right: 0,
  1423. top: 0,
  1424. bottom: 0,
  1425. };
  1426. function AutoScroller() {
  1427. this._isDestroyed = false;
  1428. this._isTicking = false;
  1429. this._tickTime = 0;
  1430. this._tickDeltaTime = 0;
  1431. this._items = [];
  1432. this._actions = [];
  1433. this._requests = {};
  1434. this._requests[AXIS_X] = {};
  1435. this._requests[AXIS_Y] = {};
  1436. this._requestOverlapCheck = {};
  1437. this._dragPositions = {};
  1438. this._dragDirections = {};
  1439. this._overlapCheckInterval = 150;
  1440. this._requestPool = new Pool(
  1441. function () {
  1442. return new ScrollRequest();
  1443. },
  1444. function (request) {
  1445. request.reset();
  1446. }
  1447. );
  1448. this._actionPool = new Pool(
  1449. function () {
  1450. return new ScrollAction();
  1451. },
  1452. function (action) {
  1453. action.reset();
  1454. }
  1455. );
  1456. this._readTick = this._readTick.bind(this);
  1457. this._writeTick = this._writeTick.bind(this);
  1458. }
  1459. AutoScroller.AXIS_X = AXIS_X;
  1460. AutoScroller.AXIS_Y = AXIS_Y;
  1461. AutoScroller.FORWARD = FORWARD;
  1462. AutoScroller.BACKWARD = BACKWARD;
  1463. AutoScroller.LEFT = LEFT;
  1464. AutoScroller.RIGHT = RIGHT;
  1465. AutoScroller.UP = UP;
  1466. AutoScroller.DOWN = DOWN;
  1467. AutoScroller.smoothSpeed = function (maxSpeed, acceleration, deceleration) {
  1468. return function (item, element, data) {
  1469. var targetSpeed = 0;
  1470. if (!data.isEnding) {
  1471. if (data.threshold > 0) {
  1472. var factor = data.threshold - Math.max(0, data.distance);
  1473. targetSpeed = (maxSpeed / data.threshold) * factor;
  1474. } else {
  1475. targetSpeed = maxSpeed;
  1476. }
  1477. }
  1478. var currentSpeed = data.speed;
  1479. var nextSpeed = targetSpeed;
  1480. if (currentSpeed === targetSpeed) {
  1481. return nextSpeed;
  1482. }
  1483. if (currentSpeed < targetSpeed) {
  1484. nextSpeed = currentSpeed + acceleration * (data.deltaTime / 1000);
  1485. return Math.min(targetSpeed, nextSpeed);
  1486. } else {
  1487. nextSpeed = currentSpeed - deceleration * (data.deltaTime / 1000);
  1488. return Math.max(targetSpeed, nextSpeed);
  1489. }
  1490. };
  1491. };
  1492. AutoScroller.pointerHandle = function (pointerSize) {
  1493. var rect = { left: 0, top: 0, width: 0, height: 0 };
  1494. var size = pointerSize || 1;
  1495. return function (item, x, y, w, h, pX, pY) {
  1496. rect.left = pX - size * 0.5;
  1497. rect.top = pY - size * 0.5;
  1498. rect.width = size;
  1499. rect.height = size;
  1500. return rect;
  1501. };
  1502. };
  1503. AutoScroller.prototype._readTick = function (time) {
  1504. if (this._isDestroyed) return;
  1505. if (time && this._tickTime) {
  1506. this._tickDeltaTime = time - this._tickTime;
  1507. this._tickTime = time;
  1508. this._updateRequests();
  1509. this._updateActions();
  1510. } else {
  1511. this._tickTime = time;
  1512. this._tickDeltaTime = 0;
  1513. }
  1514. };
  1515. AutoScroller.prototype._writeTick = function () {
  1516. if (this._isDestroyed) return;
  1517. this._applyActions();
  1518. addAutoScrollTick(this._readTick, this._writeTick);
  1519. };
  1520. AutoScroller.prototype._startTicking = function () {
  1521. this._isTicking = true;
  1522. addAutoScrollTick(this._readTick, this._writeTick);
  1523. };
  1524. AutoScroller.prototype._stopTicking = function () {
  1525. this._isTicking = false;
  1526. this._tickTime = 0;
  1527. this._tickDeltaTime = 0;
  1528. cancelAutoScrollTick();
  1529. };
  1530. AutoScroller.prototype._getItemHandleRect = function (item, handle, rect) {
  1531. var itemDrag = item._drag;
  1532. if (handle) {
  1533. var ev = itemDrag._dragMoveEvent || itemDrag._dragStartEvent;
  1534. var data = handle(
  1535. item,
  1536. itemDrag._clientX,
  1537. itemDrag._clientY,
  1538. item._width,
  1539. item._height,
  1540. ev.clientX,
  1541. ev.clientY
  1542. );
  1543. rect.left = data.left;
  1544. rect.top = data.top;
  1545. rect.width = data.width;
  1546. rect.height = data.height;
  1547. } else {
  1548. rect.left = itemDrag._clientX;
  1549. rect.top = itemDrag._clientY;
  1550. rect.width = item._width;
  1551. rect.height = item._height;
  1552. }
  1553. rect.right = rect.left + rect.width;
  1554. rect.bottom = rect.top + rect.height;
  1555. return rect;
  1556. };
  1557. AutoScroller.prototype._requestItemScroll = function (
  1558. item,
  1559. axis,
  1560. element,
  1561. direction,
  1562. threshold,
  1563. distance,
  1564. maxValue
  1565. ) {
  1566. var reqMap = this._requests[axis];
  1567. var request = reqMap[item._id];
  1568. if (request) {
  1569. if (request.element !== element || request.direction !== direction) {
  1570. request.reset();
  1571. }
  1572. } else {
  1573. request = this._requestPool.pick();
  1574. }
  1575. request.item = item;
  1576. request.element = element;
  1577. request.direction = direction;
  1578. request.threshold = threshold;
  1579. request.distance = distance;
  1580. request.maxValue = maxValue;
  1581. reqMap[item._id] = request;
  1582. };
  1583. AutoScroller.prototype._cancelItemScroll = function (item, axis) {
  1584. var reqMap = this._requests[axis];
  1585. var request = reqMap[item._id];
  1586. if (!request) return;
  1587. if (request.action) request.action.removeRequest(request);
  1588. this._requestPool.release(request);
  1589. delete reqMap[item._id];
  1590. };
  1591. AutoScroller.prototype._checkItemOverlap = function (item, checkX, checkY) {
  1592. var settings = getItemAutoScrollSettings(item);
  1593. var targets = isFunction(settings.targets) ? settings.targets(item) : settings.targets;
  1594. var threshold = settings.threshold;
  1595. var safeZone = settings.safeZone;
  1596. if (!targets || !targets.length) {
  1597. checkX && this._cancelItemScroll(item, AXIS_X);
  1598. checkY && this._cancelItemScroll(item, AXIS_Y);
  1599. return;
  1600. }
  1601. var dragDirections = this._dragDirections[item._id];
  1602. var dragDirectionX = dragDirections[0];
  1603. var dragDirectionY = dragDirections[1];
  1604. if (!dragDirectionX && !dragDirectionY) {
  1605. checkX && this._cancelItemScroll(item, AXIS_X);
  1606. checkY && this._cancelItemScroll(item, AXIS_Y);
  1607. return;
  1608. }
  1609. var itemRect = this._getItemHandleRect(item, settings.handle, RECT_1);
  1610. var testRect = RECT_2;
  1611. var target = null;
  1612. var testElement = null;
  1613. var testAxisX = true;
  1614. var testAxisY = true;
  1615. var testScore = 0;
  1616. var testPriority = 0;
  1617. var testThreshold = null;
  1618. var testDirection = null;
  1619. var testDistance = 0;
  1620. var testMaxScrollX = 0;
  1621. var testMaxScrollY = 0;
  1622. var xElement = null;
  1623. var xPriority = -Infinity;
  1624. var xThreshold = 0;
  1625. var xScore = 0;
  1626. var xDirection = null;
  1627. var xDistance = 0;
  1628. var xMaxScroll = 0;
  1629. var yElement = null;
  1630. var yPriority = -Infinity;
  1631. var yThreshold = 0;
  1632. var yScore = 0;
  1633. var yDirection = null;
  1634. var yDistance = 0;
  1635. var yMaxScroll = 0;
  1636. for (var i = 0; i < targets.length; i++) {
  1637. target = targets[i];
  1638. testAxisX = checkX && dragDirectionX && target.axis !== AXIS_Y;
  1639. testAxisY = checkY && dragDirectionY && target.axis !== AXIS_X;
  1640. testPriority = target.priority || 0;
  1641. // Ignore this item if it's x-axis and y-axis priority is lower than
  1642. // the currently matching item's.
  1643. if ((!testAxisX || testPriority < xPriority) && (!testAxisY || testPriority < yPriority)) {
  1644. continue;
  1645. }
  1646. testElement = getScrollElement(target.element || target);
  1647. testMaxScrollX = testAxisX ? getScrollLeftMax(testElement) : -1;
  1648. testMaxScrollY = testAxisY ? getScrollTopMax(testElement) : -1;
  1649. // Ignore this item if there is no possibility to scroll.
  1650. if (!testMaxScrollX && !testMaxScrollY) continue;
  1651. testRect = getContentRect(testElement, testRect);
  1652. testScore = getIntersectionScore(itemRect, testRect);
  1653. // Ignore this item if it's not overlapping at all with the dragged item.
  1654. if (testScore <= 0) continue;
  1655. // Test x-axis.
  1656. if (
  1657. testAxisX &&
  1658. testPriority >= xPriority &&
  1659. testMaxScrollX > 0 &&
  1660. (testPriority > xPriority || testScore > xScore)
  1661. ) {
  1662. testDirection = null;
  1663. testThreshold = computeThreshold(
  1664. typeof target.threshold === 'number' ? target.threshold : threshold,
  1665. safeZone,
  1666. itemRect.width,
  1667. testRect.width
  1668. );
  1669. if (dragDirectionX === RIGHT) {
  1670. testDistance = testRect.right + testThreshold.offset - itemRect.right;
  1671. if (testDistance <= testThreshold.value && getScrollLeft(testElement) < testMaxScrollX) {
  1672. testDirection = RIGHT;
  1673. }
  1674. } else if (dragDirectionX === LEFT) {
  1675. testDistance = itemRect.left - (testRect.left - testThreshold.offset);
  1676. if (testDistance <= testThreshold.value && getScrollLeft(testElement) > 0) {
  1677. testDirection = LEFT;
  1678. }
  1679. }
  1680. if (testDirection !== null) {
  1681. xElement = testElement;
  1682. xPriority = testPriority;
  1683. xThreshold = testThreshold.value;
  1684. xScore = testScore;
  1685. xDirection = testDirection;
  1686. xDistance = testDistance;
  1687. xMaxScroll = testMaxScrollX;
  1688. }
  1689. }
  1690. // Test y-axis.
  1691. if (
  1692. testAxisY &&
  1693. testPriority >= yPriority &&
  1694. testMaxScrollY > 0 &&
  1695. (testPriority > yPriority || testScore > yScore)
  1696. ) {
  1697. testDirection = null;
  1698. testThreshold = computeThreshold(
  1699. typeof target.threshold === 'number' ? target.threshold : threshold,
  1700. safeZone,
  1701. itemRect.height,
  1702. testRect.height
  1703. );
  1704. if (dragDirectionY === DOWN) {
  1705. testDistance = testRect.bottom + testThreshold.offset - itemRect.bottom;
  1706. if (testDistance <= testThreshold.value && getScrollTop(testElement) < testMaxScrollY) {
  1707. testDirection = DOWN;
  1708. }
  1709. } else if (dragDirectionY === UP) {
  1710. testDistance = itemRect.top - (testRect.top - testThreshold.offset);
  1711. if (testDistance <= testThreshold.value && getScrollTop(testElement) > 0) {
  1712. testDirection = UP;
  1713. }
  1714. }
  1715. if (testDirection !== null) {
  1716. yElement = testElement;
  1717. yPriority = testPriority;
  1718. yThreshold = testThreshold.value;
  1719. yScore = testScore;
  1720. yDirection = testDirection;
  1721. yDistance = testDistance;
  1722. yMaxScroll = testMaxScrollY;
  1723. }
  1724. }
  1725. }
  1726. // Request or cancel x-axis scroll.
  1727. if (checkX) {
  1728. if (xElement) {
  1729. this._requestItemScroll(
  1730. item,
  1731. AXIS_X,
  1732. xElement,
  1733. xDirection,
  1734. xThreshold,
  1735. xDistance,
  1736. xMaxScroll
  1737. );
  1738. } else {
  1739. this._cancelItemScroll(item, AXIS_X);
  1740. }
  1741. }
  1742. // Request or cancel y-axis scroll.
  1743. if (checkY) {
  1744. if (yElement) {
  1745. this._requestItemScroll(
  1746. item,
  1747. AXIS_Y,
  1748. yElement,
  1749. yDirection,
  1750. yThreshold,
  1751. yDistance,
  1752. yMaxScroll
  1753. );
  1754. } else {
  1755. this._cancelItemScroll(item, AXIS_Y);
  1756. }
  1757. }
  1758. };
  1759. AutoScroller.prototype._updateScrollRequest = function (scrollRequest) {
  1760. var item = scrollRequest.item;
  1761. var settings = getItemAutoScrollSettings(item);
  1762. var targets = isFunction(settings.targets) ? settings.targets(item) : settings.targets;
  1763. var targetCount = (targets && targets.length) || 0;
  1764. var threshold = settings.threshold;
  1765. var safeZone = settings.safeZone;
  1766. var itemRect = this._getItemHandleRect(item, settings.handle, RECT_1);
  1767. var testRect = RECT_2;
  1768. var target = null;
  1769. var testElement = null;
  1770. var testIsAxisX = false;
  1771. var testScore = null;
  1772. var testThreshold = null;
  1773. var testDistance = null;
  1774. var testScroll = null;
  1775. var testMaxScroll = null;
  1776. var hasReachedEnd = null;
  1777. for (var i = 0; i < targetCount; i++) {
  1778. target = targets[i];
  1779. // Make sure we have a matching element.
  1780. testElement = getScrollElement(target.element || target);
  1781. if (testElement !== scrollRequest.element) continue;
  1782. // Make sure we have a matching axis.
  1783. testIsAxisX = !!(AXIS_X & scrollRequest.direction);
  1784. if (testIsAxisX) {
  1785. if (target.axis === AXIS_Y) continue;
  1786. } else {
  1787. if (target.axis === AXIS_X) continue;
  1788. }
  1789. // Stop scrolling if there is no room to scroll anymore.
  1790. testMaxScroll = testIsAxisX ? getScrollLeftMax(testElement) : getScrollTopMax(testElement);
  1791. if (testMaxScroll <= 0) {
  1792. break;
  1793. }
  1794. testRect = getContentRect(testElement, testRect);
  1795. testScore = getIntersectionScore(itemRect, testRect);
  1796. // Stop scrolling if dragged item is not overlapping with the scroll
  1797. // element anymore.
  1798. if (testScore <= 0) {
  1799. break;
  1800. }
  1801. // Compute threshold and edge offset.
  1802. testThreshold = computeThreshold(
  1803. typeof target.threshold === 'number' ? target.threshold : threshold,
  1804. safeZone,
  1805. testIsAxisX ? itemRect.width : itemRect.height,
  1806. testIsAxisX ? testRect.width : testRect.height
  1807. );
  1808. // Compute distance (based on current direction).
  1809. if (scrollRequest.direction === LEFT) {
  1810. testDistance = itemRect.left - (testRect.left - testThreshold.offset);
  1811. } else if (scrollRequest.direction === RIGHT) {
  1812. testDistance = testRect.right + testThreshold.offset - itemRect.right;
  1813. } else if (scrollRequest.direction === UP) {
  1814. testDistance = itemRect.top - (testRect.top - testThreshold.offset);
  1815. } else {
  1816. testDistance = testRect.bottom + testThreshold.offset - itemRect.bottom;
  1817. }
  1818. // Stop scrolling if threshold is not exceeded.
  1819. if (testDistance > testThreshold.value) {
  1820. break;
  1821. }
  1822. // Stop scrolling if we have reached the end of the scroll value.
  1823. testScroll = testIsAxisX ? getScrollLeft(testElement) : getScrollTop(testElement);
  1824. hasReachedEnd =
  1825. FORWARD & scrollRequest.direction ? testScroll >= testMaxScroll : testScroll <= 0;
  1826. if (hasReachedEnd) {
  1827. break;
  1828. }
  1829. // Scrolling can continue, let's update the values.
  1830. scrollRequest.maxValue = testMaxScroll;
  1831. scrollRequest.threshold = testThreshold.value;
  1832. scrollRequest.distance = testDistance;
  1833. scrollRequest.isEnding = false;
  1834. return true;
  1835. }
  1836. // Before we end the request, let's see if we need to stop the scrolling
  1837. // smoothly or immediately.
  1838. if (settings.smoothStop === true && scrollRequest.speed > 0) {
  1839. if (hasReachedEnd === null) hasReachedEnd = scrollRequest.hasReachedEnd();
  1840. scrollRequest.isEnding = hasReachedEnd ? false : true;
  1841. } else {
  1842. scrollRequest.isEnding = false;
  1843. }
  1844. return scrollRequest.isEnding;
  1845. };
  1846. AutoScroller.prototype._updateRequests = function () {
  1847. var items = this._items;
  1848. var requestsX = this._requests[AXIS_X];
  1849. var requestsY = this._requests[AXIS_Y];
  1850. var item, reqX, reqY, checkTime, needsCheck, checkX, checkY;
  1851. for (var i = 0; i < items.length; i++) {
  1852. item = items[i];
  1853. checkTime = this._requestOverlapCheck[item._id];
  1854. needsCheck = checkTime > 0 && this._tickTime - checkTime > this._overlapCheckInterval;
  1855. checkX = true;
  1856. reqX = requestsX[item._id];
  1857. if (reqX && reqX.isActive) {
  1858. checkX = !this._updateScrollRequest(reqX);
  1859. if (checkX) {
  1860. needsCheck = true;
  1861. this._cancelItemScroll(item, AXIS_X);
  1862. }
  1863. }
  1864. checkY = true;
  1865. reqY = requestsY[item._id];
  1866. if (reqY && reqY.isActive) {
  1867. checkY = !this._updateScrollRequest(reqY);
  1868. if (checkY) {
  1869. needsCheck = true;
  1870. this._cancelItemScroll(item, AXIS_Y);
  1871. }
  1872. }
  1873. if (needsCheck) {
  1874. this._requestOverlapCheck[item._id] = 0;
  1875. this._checkItemOverlap(item, checkX, checkY);
  1876. }
  1877. }
  1878. };
  1879. AutoScroller.prototype._requestAction = function (request, axis) {
  1880. var actions = this._actions;
  1881. var isAxisX = axis === AXIS_X;
  1882. var action = null;
  1883. for (var i = 0; i < actions.length; i++) {
  1884. action = actions[i];
  1885. // If the action's request does not match the request's -> skip.
  1886. if (request.element !== action.element) {
  1887. action = null;
  1888. continue;
  1889. }
  1890. // If the request and action share the same element, but the request slot
  1891. // for the requested axis is already reserved let's ignore and cancel this
  1892. // request.
  1893. if (isAxisX ? action.requestX : action.requestY) {
  1894. this._cancelItemScroll(request.item, axis);
  1895. return;
  1896. }
  1897. // Seems like we have found our action, let's break the loop.
  1898. break;
  1899. }
  1900. if (!action) action = this._actionPool.pick();
  1901. action.element = request.element;
  1902. action.addRequest(request);
  1903. request.tick(this._tickDeltaTime);
  1904. actions.push(action);
  1905. };
  1906. AutoScroller.prototype._updateActions = function () {
  1907. var items = this._items;
  1908. var requests = this._requests;
  1909. var actions = this._actions;
  1910. var itemId;
  1911. var reqX;
  1912. var reqY;
  1913. var i;
  1914. // Generate actions.
  1915. for (i = 0; i < items.length; i++) {
  1916. itemId = items[i]._id;
  1917. reqX = requests[AXIS_X][itemId];
  1918. reqY = requests[AXIS_Y][itemId];
  1919. if (reqX) this._requestAction(reqX, AXIS_X);
  1920. if (reqY) this._requestAction(reqY, AXIS_Y);
  1921. }
  1922. // Compute actions' scroll values.
  1923. for (i = 0; i < actions.length; i++) {
  1924. actions[i].computeScrollValues();
  1925. }
  1926. };
  1927. AutoScroller.prototype._applyActions = function () {
  1928. var actions = this._actions;
  1929. var items = this._items;
  1930. var i;
  1931. // No actions -> no scrolling.
  1932. if (!actions.length) return;
  1933. // Scroll all the required elements.
  1934. for (i = 0; i < actions.length; i++) {
  1935. actions[i].scroll();
  1936. this._actionPool.release(actions[i]);
  1937. }
  1938. // Reset actions.
  1939. actions.length = 0;
  1940. // Sync the item position immediately after all the auto-scrolling business is
  1941. // finished. Without this procedure the items will jitter during auto-scroll
  1942. // (in some cases at least) since the drag scroll handler is async (bound to
  1943. // raf tick). Note that this procedure should not emit any dragScroll events,
  1944. // because otherwise they would be emitted twice for the same event.
  1945. for (i = 0; i < items.length; i++) prepareItemScrollSync(items[i]);
  1946. for (i = 0; i < items.length; i++) applyItemScrollSync(items[i]);
  1947. };
  1948. AutoScroller.prototype._updateDragDirection = function (item) {
  1949. var dragPositions = this._dragPositions[item._id];
  1950. var dragDirections = this._dragDirections[item._id];
  1951. var x1 = item._drag._left;
  1952. var y1 = item._drag._top;
  1953. if (dragPositions.length) {
  1954. var x2 = dragPositions[0];
  1955. var y2 = dragPositions[1];
  1956. dragDirections[0] = x1 > x2 ? RIGHT : x1 < x2 ? LEFT : dragDirections[0] || 0;
  1957. dragDirections[1] = y1 > y2 ? DOWN : y1 < y2 ? UP : dragDirections[1] || 0;
  1958. }
  1959. dragPositions[0] = x1;
  1960. dragPositions[1] = y1;
  1961. };
  1962. AutoScroller.prototype.addItem = function (item) {
  1963. if (this._isDestroyed) return;
  1964. var index = this._items.indexOf(item);
  1965. if (index === -1) {
  1966. this._items.push(item);
  1967. this._requestOverlapCheck[item._id] = this._tickTime;
  1968. this._dragDirections[item._id] = [0, 0];
  1969. this._dragPositions[item._id] = [];
  1970. if (!this._isTicking) this._startTicking();
  1971. }
  1972. };
  1973. AutoScroller.prototype.updateItem = function (item) {
  1974. if (this._isDestroyed) return;
  1975. // Make sure the item still exists in the auto-scroller.
  1976. if (!this._dragDirections[item._id]) return;
  1977. this._updateDragDirection(item);
  1978. if (!this._requestOverlapCheck[item._id]) {
  1979. this._requestOverlapCheck[item._id] = this._tickTime;
  1980. }
  1981. };
  1982. AutoScroller.prototype.removeItem = function (item) {
  1983. if (this._isDestroyed) return;
  1984. var index = this._items.indexOf(item);
  1985. if (index === -1) return;
  1986. var itemId = item._id;
  1987. var reqX = this._requests[AXIS_X][itemId];
  1988. if (reqX) {
  1989. this._cancelItemScroll(item, AXIS_X);
  1990. delete this._requests[AXIS_X][itemId];
  1991. }
  1992. var reqY = this._requests[AXIS_Y][itemId];
  1993. if (reqY) {
  1994. this._cancelItemScroll(item, AXIS_Y);
  1995. delete this._requests[AXIS_Y][itemId];
  1996. }
  1997. delete this._requestOverlapCheck[itemId];
  1998. delete this._dragPositions[itemId];
  1999. delete this._dragDirections[itemId];
  2000. this._items.splice(index, 1);
  2001. if (this._isTicking && !this._items.length) {
  2002. this._stopTicking();
  2003. }
  2004. };
  2005. AutoScroller.prototype.isItemScrollingX = function (item) {
  2006. var reqX = this._requests[AXIS_X][item._id];
  2007. return !!(reqX && reqX.isActive);
  2008. };
  2009. AutoScroller.prototype.isItemScrollingY = function (item) {
  2010. var reqY = this._requests[AXIS_Y][item._id];
  2011. return !!(reqY && reqY.isActive);
  2012. };
  2013. AutoScroller.prototype.isItemScrolling = function (item) {
  2014. return this.isItemScrollingX(item) || this.isItemScrollingY(item);
  2015. };
  2016. AutoScroller.prototype.destroy = function () {
  2017. if (this._isDestroyed) return;
  2018. var items = this._items.slice(0);
  2019. for (var i = 0; i < items.length; i++) {
  2020. this.removeItem(items[i]);
  2021. }
  2022. this._actions.length = 0;
  2023. this._requestPool.reset();
  2024. this._actionPool.reset();
  2025. this._isDestroyed = true;
  2026. };
  2027. var ElProto = window.Element.prototype;
  2028. var matchesFn =
  2029. ElProto.matches ||
  2030. ElProto.matchesSelector ||
  2031. ElProto.webkitMatchesSelector ||
  2032. ElProto.mozMatchesSelector ||
  2033. ElProto.msMatchesSelector ||
  2034. ElProto.oMatchesSelector ||
  2035. function () {
  2036. return false;
  2037. };
  2038. /**
  2039. * Check if element matches a CSS selector.
  2040. *
  2041. * @param {Element} el
  2042. * @param {String} selector
  2043. * @returns {Boolean}
  2044. */
  2045. function elementMatches(el, selector) {
  2046. return matchesFn.call(el, selector);
  2047. }
  2048. /**
  2049. * Add class to an element.
  2050. *
  2051. * @param {HTMLElement} element
  2052. * @param {String} className
  2053. */
  2054. function addClass(element, className) {
  2055. if (!className) return;
  2056. if (element.classList) {
  2057. element.classList.add(className);
  2058. } else {
  2059. if (!elementMatches(element, '.' + className)) {
  2060. element.className += ' ' + className;
  2061. }
  2062. }
  2063. }
  2064. var tempArray = [];
  2065. var numberType = 'number';
  2066. /**
  2067. * Insert an item or an array of items to array to a specified index. Mutates
  2068. * the array. The index can be negative in which case the items will be added
  2069. * to the end of the array.
  2070. *
  2071. * @param {Array} array
  2072. * @param {*} items
  2073. * @param {Number} [index=-1]
  2074. */
  2075. function arrayInsert(array, items, index) {
  2076. var startIndex = typeof index === numberType ? index : -1;
  2077. if (startIndex < 0) startIndex = array.length - startIndex + 1;
  2078. array.splice.apply(array, tempArray.concat(startIndex, 0, items));
  2079. tempArray.length = 0;
  2080. }
  2081. /**
  2082. * Normalize array index. Basically this function makes sure that the provided
  2083. * array index is within the bounds of the provided array and also transforms
  2084. * negative index to the matching positive index. The third (optional) argument
  2085. * allows you to define offset for array's length in case you are adding items
  2086. * to the array or removing items from the array.
  2087. *
  2088. * @param {Array} array
  2089. * @param {Number} index
  2090. * @param {Number} [sizeOffset]
  2091. */
  2092. function normalizeArrayIndex(array, index, sizeOffset) {
  2093. var maxIndex = Math.max(0, array.length - 1 + (sizeOffset || 0));
  2094. return index > maxIndex ? maxIndex : index < 0 ? Math.max(maxIndex + index + 1, 0) : index;
  2095. }
  2096. /**
  2097. * Move array item to another index.
  2098. *
  2099. * @param {Array} array
  2100. * @param {Number} fromIndex
  2101. * - Index (positive or negative) of the item that will be moved.
  2102. * @param {Number} toIndex
  2103. * - Index (positive or negative) where the item should be moved to.
  2104. */
  2105. function arrayMove(array, fromIndex, toIndex) {
  2106. // Make sure the array has two or more items.
  2107. if (array.length < 2) return;
  2108. // Normalize the indices.
  2109. var from = normalizeArrayIndex(array, fromIndex);
  2110. var to = normalizeArrayIndex(array, toIndex);
  2111. // Add target item to the new position.
  2112. if (from !== to) {
  2113. array.splice(to, 0, array.splice(from, 1)[0]);
  2114. }
  2115. }
  2116. /**
  2117. * Swap array items.
  2118. *
  2119. * @param {Array} array
  2120. * @param {Number} index
  2121. * - Index (positive or negative) of the item that will be swapped.
  2122. * @param {Number} withIndex
  2123. * - Index (positive or negative) of the other item that will be swapped.
  2124. */
  2125. function arraySwap(array, index, withIndex) {
  2126. // Make sure the array has two or more items.
  2127. if (array.length < 2) return;
  2128. // Normalize the indices.
  2129. var indexA = normalizeArrayIndex(array, index);
  2130. var indexB = normalizeArrayIndex(array, withIndex);
  2131. var temp;
  2132. // Swap the items.
  2133. if (indexA !== indexB) {
  2134. temp = array[indexA];
  2135. array[indexA] = array[indexB];
  2136. array[indexB] = temp;
  2137. }
  2138. }
  2139. var transformProp = getPrefixedPropName(document.documentElement.style, 'transform') || 'transform';
  2140. var styleNameRegEx = /([A-Z])/g;
  2141. var prefixRegex = /^(webkit-|moz-|ms-|o-)/;
  2142. var msPrefixRegex = /^(-m-s-)/;
  2143. /**
  2144. * Transforms a camel case style property to kebab case style property. Handles
  2145. * vendor prefixed properties elegantly as well, e.g. "WebkitTransform" and
  2146. * "webkitTransform" are both transformed into "-webkit-transform".
  2147. *
  2148. * @param {String} property
  2149. * @returns {String}
  2150. */
  2151. function getStyleName(property) {
  2152. // Initial slicing, turns "fooBarProp" into "foo-bar-prop".
  2153. var styleName = property.replace(styleNameRegEx, '-$1').toLowerCase();
  2154. // Handle properties that start with "webkit", "moz", "ms" or "o" prefix (we
  2155. // need to add an extra '-' to the beginnig).
  2156. styleName = styleName.replace(prefixRegex, '-$1');
  2157. // Handle properties that start with "MS" prefix (we need to transform the
  2158. // "-m-s-" into "-ms-").
  2159. styleName = styleName.replace(msPrefixRegex, '-ms-');
  2160. return styleName;
  2161. }
  2162. var transformStyle = getStyleName(transformProp);
  2163. var transformNone = 'none';
  2164. var displayInline = 'inline';
  2165. var displayNone = 'none';
  2166. var displayStyle = 'display';
  2167. /**
  2168. * Returns true if element is transformed, false if not. In practice the
  2169. * element's display value must be anything else than "none" or "inline" as
  2170. * well as have a valid transform value applied in order to be counted as a
  2171. * transformed element.
  2172. *
  2173. * Borrowed from Mezr (v0.6.1):
  2174. * https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L661
  2175. *
  2176. * @param {HTMLElement} element
  2177. * @returns {Boolean}
  2178. */
  2179. function isTransformed(element) {
  2180. var transform = getStyle(element, transformStyle);
  2181. if (!transform || transform === transformNone) return false;
  2182. var display = getStyle(element, displayStyle);
  2183. if (display === displayInline || display === displayNone) return false;
  2184. return true;
  2185. }
  2186. /**
  2187. * Returns an absolute positioned element's containing block, which is
  2188. * considered to be the closest ancestor element that the target element's
  2189. * positioning is relative to. Disclaimer: this only works as intended for
  2190. * absolute positioned elements.
  2191. *
  2192. * @param {HTMLElement} element
  2193. * @returns {(Document|Element)}
  2194. */
  2195. function getContainingBlock(element) {
  2196. // As long as the containing block is an element, static and not
  2197. // transformed, try to get the element's parent element and fallback to
  2198. // document. https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L339
  2199. var doc = document;
  2200. var res = element || doc;
  2201. while (res && res !== doc && getStyle(res, 'position') === 'static' && !isTransformed(res)) {
  2202. res = res.parentElement || doc;
  2203. }
  2204. return res;
  2205. }
  2206. var offsetA = {};
  2207. var offsetB = {};
  2208. var offsetDiff = {};
  2209. /**
  2210. * Returns the element's document offset, which in practice means the vertical
  2211. * and horizontal distance between the element's northwest corner and the
  2212. * document's northwest corner. Note that this function always returns the same
  2213. * object so be sure to read the data from it instead using it as a reference.
  2214. *
  2215. * @param {(Document|Element|Window)} element
  2216. * @param {Object} [offsetData]
  2217. * - Optional data object where the offset data will be inserted to. If not
  2218. * provided a new object will be created for the return data.
  2219. * @returns {Object}
  2220. */
  2221. function getOffset(element, offsetData) {
  2222. var offset = offsetData || {};
  2223. var rect;
  2224. // Set up return data.
  2225. offset.left = 0;
  2226. offset.top = 0;
  2227. // Document's offsets are always 0.
  2228. if (element === document) return offset;
  2229. // Add viewport scroll left/top to the respective offsets.
  2230. offset.left = window.pageXOffset || 0;
  2231. offset.top = window.pageYOffset || 0;
  2232. // Window's offsets are the viewport scroll left/top values.
  2233. if (element.self === window.self) return offset;
  2234. // Add element's client rects to the offsets.
  2235. rect = element.getBoundingClientRect();
  2236. offset.left += rect.left;
  2237. offset.top += rect.top;
  2238. // Exclude element's borders from the offset.
  2239. offset.left += getStyleAsFloat(element, 'border-left-width');
  2240. offset.top += getStyleAsFloat(element, 'border-top-width');
  2241. return offset;
  2242. }
  2243. /**
  2244. * Calculate the offset difference two elements.
  2245. *
  2246. * @param {HTMLElement} elemA
  2247. * @param {HTMLElement} elemB
  2248. * @param {Boolean} [compareContainingBlocks=false]
  2249. * - When this is set to true the containing blocks of the provided elements
  2250. * will be used for calculating the difference. Otherwise the provided
  2251. * elements will be compared directly.
  2252. * @returns {Object}
  2253. */
  2254. function getOffsetDiff(elemA, elemB, compareContainingBlocks) {
  2255. offsetDiff.left = 0;
  2256. offsetDiff.top = 0;
  2257. // If elements are same let's return early.
  2258. if (elemA === elemB) return offsetDiff;
  2259. // Compare containing blocks if necessary.
  2260. if (compareContainingBlocks) {
  2261. elemA = getContainingBlock(elemA);
  2262. elemB = getContainingBlock(elemB);
  2263. // If containing blocks are identical, let's return early.
  2264. if (elemA === elemB) return offsetDiff;
  2265. }
  2266. // Finally, let's calculate the offset diff.
  2267. getOffset(elemA, offsetA);
  2268. getOffset(elemB, offsetB);
  2269. offsetDiff.left = offsetB.left - offsetA.left;
  2270. offsetDiff.top = offsetB.top - offsetA.top;
  2271. return offsetDiff;
  2272. }
  2273. /**
  2274. * Check if overflow style value is scrollable.
  2275. *
  2276. * @param {String} value
  2277. * @returns {Boolean}
  2278. */
  2279. function isScrollableOverflow(value) {
  2280. return value === 'auto' || value === 'scroll' || value === 'overlay';
  2281. }
  2282. /**
  2283. * Check if an element is scrollable.
  2284. *
  2285. * @param {HTMLElement} element
  2286. * @returns {Boolean}
  2287. */
  2288. function isScrollable(element) {
  2289. return (
  2290. isScrollableOverflow(getStyle(element, 'overflow')) ||
  2291. isScrollableOverflow(getStyle(element, 'overflow-x')) ||
  2292. isScrollableOverflow(getStyle(element, 'overflow-y'))
  2293. );
  2294. }
  2295. /**
  2296. * Collect element's ancestors that are potentially scrollable elements. The
  2297. * provided element is also also included in the check, meaning that if it is
  2298. * scrollable it is added to the result array.
  2299. *
  2300. * @param {HTMLElement} element
  2301. * @param {Array} [result]
  2302. * @returns {Array}
  2303. */
  2304. function getScrollableAncestors(element, result) {
  2305. result = result || [];
  2306. // Find scroll parents.
  2307. while (element && element !== document) {
  2308. // If element is inside ShadowDOM let's get it's host node from the real
  2309. // DOM and continue looping.
  2310. if (element.getRootNode && element instanceof DocumentFragment) {
  2311. element = element.getRootNode().host;
  2312. continue;
  2313. }
  2314. // If element is scrollable let's add it to the scrollable list.
  2315. if (isScrollable(element)) {
  2316. result.push(element);
  2317. }
  2318. element = element.parentNode;
  2319. }
  2320. // Always add window to the results.
  2321. result.push(window);
  2322. return result;
  2323. }
  2324. var translateValue = {};
  2325. var transformNone$1 = 'none';
  2326. var rxMat3d = /^matrix3d/;
  2327. var rxMatTx = /([^,]*,){4}/;
  2328. var rxMat3dTx = /([^,]*,){12}/;
  2329. var rxNextItem = /[^,]*,/;
  2330. /**
  2331. * Returns the element's computed translateX and translateY values as a floats.
  2332. * The returned object is always the same object and updated every time this
  2333. * function is called.
  2334. *
  2335. * @param {HTMLElement} element
  2336. * @returns {Object}
  2337. */
  2338. function getTranslate(element) {
  2339. translateValue.x = 0;
  2340. translateValue.y = 0;
  2341. var transform = getStyle(element, transformStyle);
  2342. if (!transform || transform === transformNone$1) {
  2343. return translateValue;
  2344. }
  2345. // Transform style can be in either matrix3d(...) or matrix(...).
  2346. var isMat3d = rxMat3d.test(transform);
  2347. var tX = transform.replace(isMat3d ? rxMat3dTx : rxMatTx, '');
  2348. var tY = tX.replace(rxNextItem, '');
  2349. translateValue.x = parseFloat(tX) || 0;
  2350. translateValue.y = parseFloat(tY) || 0;
  2351. return translateValue;
  2352. }
  2353. /**
  2354. * Remove class from an element.
  2355. *
  2356. * @param {HTMLElement} element
  2357. * @param {String} className
  2358. */
  2359. function removeClass(element, className) {
  2360. if (!className) return;
  2361. if (element.classList) {
  2362. element.classList.remove(className);
  2363. } else {
  2364. if (elementMatches(element, '.' + className)) {
  2365. element.className = (' ' + element.className + ' ')
  2366. .replace(' ' + className + ' ', ' ')
  2367. .trim();
  2368. }
  2369. }
  2370. }
  2371. var IS_IOS =
  2372. /^(iPad|iPhone|iPod)/.test(window.navigator.platform) ||
  2373. (/^Mac/.test(window.navigator.platform) && window.navigator.maxTouchPoints > 1);
  2374. var START_PREDICATE_INACTIVE = 0;
  2375. var START_PREDICATE_PENDING = 1;
  2376. var START_PREDICATE_RESOLVED = 2;
  2377. var SCROLL_LISTENER_OPTIONS = hasPassiveEvents() ? { passive: true } : false;
  2378. /**
  2379. * Bind touch interaction to an item.
  2380. *
  2381. * @class
  2382. * @param {Item} item
  2383. */
  2384. function ItemDrag(item) {
  2385. var element = item._element;
  2386. var grid = item.getGrid();
  2387. var settings = grid._settings;
  2388. this._item = item;
  2389. this._gridId = grid._id;
  2390. this._isDestroyed = false;
  2391. this._isMigrating = false;
  2392. // Start predicate data.
  2393. this._startPredicate = isFunction(settings.dragStartPredicate)
  2394. ? settings.dragStartPredicate
  2395. : ItemDrag.defaultStartPredicate;
  2396. this._startPredicateState = START_PREDICATE_INACTIVE;
  2397. this._startPredicateResult = undefined;
  2398. // Data for drag sort predicate heuristics.
  2399. this._isSortNeeded = false;
  2400. this._sortTimer = undefined;
  2401. this._blockedSortIndex = null;
  2402. this._sortX1 = 0;
  2403. this._sortX2 = 0;
  2404. this._sortY1 = 0;
  2405. this._sortY2 = 0;
  2406. // Setup item's initial drag data.
  2407. this._reset();
  2408. // Bind the methods that needs binding.
  2409. this._preStartCheck = this._preStartCheck.bind(this);
  2410. this._preEndCheck = this._preEndCheck.bind(this);
  2411. this._onScroll = this._onScroll.bind(this);
  2412. this._prepareStart = this._prepareStart.bind(this);
  2413. this._applyStart = this._applyStart.bind(this);
  2414. this._prepareMove = this._prepareMove.bind(this);
  2415. this._applyMove = this._applyMove.bind(this);
  2416. this._prepareScroll = this._prepareScroll.bind(this);
  2417. this._applyScroll = this._applyScroll.bind(this);
  2418. this._handleSort = this._handleSort.bind(this);
  2419. this._handleSortDelayed = this._handleSortDelayed.bind(this);
  2420. // Get drag handle element.
  2421. this._handle = (settings.dragHandle && element.querySelector(settings.dragHandle)) || element;
  2422. // Init dragger.
  2423. this._dragger = new Dragger(this._handle, settings.dragCssProps);
  2424. this._dragger.on('start', this._preStartCheck);
  2425. this._dragger.on('move', this._preStartCheck);
  2426. this._dragger.on('cancel', this._preEndCheck);
  2427. this._dragger.on('end', this._preEndCheck);
  2428. }
  2429. /**
  2430. * Public properties
  2431. * *****************
  2432. */
  2433. /**
  2434. * @public
  2435. * @static
  2436. * @type {AutoScroller}
  2437. */
  2438. ItemDrag.autoScroller = new AutoScroller();
  2439. /**
  2440. * Public static methods
  2441. * *********************
  2442. */
  2443. /**
  2444. * Default drag start predicate handler that handles anchor elements
  2445. * gracefully. The return value of this function defines if the drag is
  2446. * started, rejected or pending. When true is returned the dragging is started
  2447. * and when false is returned the dragging is rejected. If nothing is returned
  2448. * the predicate will be called again on the next drag movement.
  2449. *
  2450. * @public
  2451. * @static
  2452. * @param {Item} item
  2453. * @param {Object} event
  2454. * @param {Object} [options]
  2455. * - An optional options object which can be used to pass the predicate
  2456. * it's options manually. By default the predicate retrieves the options
  2457. * from the grid's settings.
  2458. * @returns {(Boolean|undefined)}
  2459. */
  2460. ItemDrag.defaultStartPredicate = function (item, event, options) {
  2461. var drag = item._drag;
  2462. // Make sure left button is pressed on mouse.
  2463. if (event.isFirst && event.srcEvent.button) {
  2464. return false;
  2465. }
  2466. // If the start event is trusted, non-cancelable and it's default action has
  2467. // not been prevented it is in most cases a sign that the gesture would be
  2468. // cancelled anyways right after it has started (e.g. starting drag while
  2469. // the page is scrolling).
  2470. if (
  2471. !IS_IOS &&
  2472. event.isFirst &&
  2473. event.srcEvent.isTrusted === true &&
  2474. event.srcEvent.defaultPrevented === false &&
  2475. event.srcEvent.cancelable === false
  2476. ) {
  2477. return false;
  2478. }
  2479. // Final event logic. At this stage return value does not matter anymore,
  2480. // the predicate is either resolved or it's not and there's nothing to do
  2481. // about it. Here we just reset data and if the item element is a link
  2482. // we follow it (if there has only been slight movement).
  2483. if (event.isFinal) {
  2484. drag._finishStartPredicate(event);
  2485. return;
  2486. }
  2487. // Setup predicate data from options if not already set.
  2488. var predicate = drag._startPredicateData;
  2489. if (!predicate) {
  2490. var config = options || drag._getGrid()._settings.dragStartPredicate || {};
  2491. drag._startPredicateData = predicate = {
  2492. distance: Math.max(config.distance, 0) || 0,
  2493. delay: Math.max(config.delay, 0) || 0,
  2494. };
  2495. }
  2496. // If delay is defined let's keep track of the latest event and initiate
  2497. // delay if it has not been done yet.
  2498. if (predicate.delay) {
  2499. predicate.event = event;
  2500. if (!predicate.delayTimer) {
  2501. predicate.delayTimer = window.setTimeout(function () {
  2502. predicate.delay = 0;
  2503. if (drag._resolveStartPredicate(predicate.event)) {
  2504. drag._forceResolveStartPredicate(predicate.event);
  2505. drag._resetStartPredicate();
  2506. }
  2507. }, predicate.delay);
  2508. }
  2509. }
  2510. return drag._resolveStartPredicate(event);
  2511. };
  2512. /**
  2513. * Default drag sort predicate.
  2514. *
  2515. * @public
  2516. * @static
  2517. * @param {Item} item
  2518. * @param {Object} [options]
  2519. * @param {Number} [options.threshold=50]
  2520. * @param {String} [options.action='move']
  2521. * @returns {?Object}
  2522. * - Returns `null` if no valid index was found. Otherwise returns drag sort
  2523. * command.
  2524. */
  2525. ItemDrag.defaultSortPredicate = (function () {
  2526. var itemRect = {};
  2527. var targetRect = {};
  2528. var returnData = {};
  2529. var gridsArray = [];
  2530. var minThreshold = 1;
  2531. var maxThreshold = 100;
  2532. function getTargetGrid(item, rootGrid, threshold) {
  2533. var target = null;
  2534. var dragSort = rootGrid._settings.dragSort;
  2535. var bestScore = -1;
  2536. var gridScore;
  2537. var grids;
  2538. var grid;
  2539. var container;
  2540. var containerRect;
  2541. var left;
  2542. var top;
  2543. var right;
  2544. var bottom;
  2545. var i;
  2546. // Get potential target grids.
  2547. if (dragSort === true) {
  2548. gridsArray[0] = rootGrid;
  2549. grids = gridsArray;
  2550. } else if (isFunction(dragSort)) {
  2551. grids = dragSort.call(rootGrid, item);
  2552. }
  2553. // Return immediately if there are no grids.
  2554. if (!grids || !Array.isArray(grids) || !grids.length) {
  2555. return target;
  2556. }
  2557. // Loop through the grids and get the best match.
  2558. for (i = 0; i < grids.length; i++) {
  2559. grid = grids[i];
  2560. // Filter out all destroyed grids.
  2561. if (grid._isDestroyed) continue;
  2562. // Compute the grid's client rect an clamp the initial boundaries to
  2563. // viewport dimensions.
  2564. grid._updateBoundingRect();
  2565. left = Math.max(0, grid._left);
  2566. top = Math.max(0, grid._top);
  2567. right = Math.min(window.innerWidth, grid._right);
  2568. bottom = Math.min(window.innerHeight, grid._bottom);
  2569. // The grid might be inside one or more elements that clip it's visibility
  2570. // (e.g overflow scroll/hidden) so we want to find out the visible portion
  2571. // of the grid in the viewport and use that in our calculations.
  2572. container = grid._element.parentNode;
  2573. while (
  2574. container &&
  2575. container !== document &&
  2576. container !== document.documentElement &&
  2577. container !== document.body
  2578. ) {
  2579. if (container.getRootNode && container instanceof DocumentFragment) {
  2580. container = container.getRootNode().host;
  2581. continue;
  2582. }
  2583. if (getStyle(container, 'overflow') !== 'visible') {
  2584. containerRect = container.getBoundingClientRect();
  2585. left = Math.max(left, containerRect.left);
  2586. top = Math.max(top, containerRect.top);
  2587. right = Math.min(right, containerRect.right);
  2588. bottom = Math.min(bottom, containerRect.bottom);
  2589. }
  2590. if (getStyle(container, 'position') === 'fixed') {
  2591. break;
  2592. }
  2593. container = container.parentNode;
  2594. }
  2595. // No need to go further if target rect does not have visible area.
  2596. if (left >= right || top >= bottom) continue;
  2597. // Check how much dragged element overlaps the container element.
  2598. targetRect.left = left;
  2599. targetRect.top = top;
  2600. targetRect.width = right - left;
  2601. targetRect.height = bottom - top;
  2602. gridScore = getIntersectionScore(itemRect, targetRect);
  2603. // Check if this grid is the best match so far.
  2604. if (gridScore > threshold && gridScore > bestScore) {
  2605. bestScore = gridScore;
  2606. target = grid;
  2607. }
  2608. }
  2609. // Always reset grids array.
  2610. gridsArray.length = 0;
  2611. return target;
  2612. }
  2613. return function (item, options) {
  2614. var drag = item._drag;
  2615. var rootGrid = drag._getGrid();
  2616. // Get drag sort predicate settings.
  2617. var sortThreshold = options && typeof options.threshold === 'number' ? options.threshold : 50;
  2618. var sortAction = options && options.action === ACTION_SWAP ? ACTION_SWAP : ACTION_MOVE;
  2619. var migrateAction =
  2620. options && options.migrateAction === ACTION_SWAP ? ACTION_SWAP : ACTION_MOVE;
  2621. // Sort threshold must be a positive number capped to a max value of 100. If
  2622. // that's not the case this function will not work correctly. So let's clamp
  2623. // the threshold just in case.
  2624. sortThreshold = Math.min(Math.max(sortThreshold, minThreshold), maxThreshold);
  2625. // Populate item rect data.
  2626. itemRect.width = item._width;
  2627. itemRect.height = item._height;
  2628. itemRect.left = drag._clientX;
  2629. itemRect.top = drag._clientY;
  2630. // Calculate the target grid.
  2631. var grid = getTargetGrid(item, rootGrid, sortThreshold);
  2632. // Return early if we found no grid container element that overlaps the
  2633. // dragged item enough.
  2634. if (!grid) return null;
  2635. var isMigration = item.getGrid() !== grid;
  2636. var gridOffsetLeft = 0;
  2637. var gridOffsetTop = 0;
  2638. var matchScore = 0;
  2639. var matchIndex = -1;
  2640. var hasValidTargets = false;
  2641. var target;
  2642. var score;
  2643. var i;
  2644. // If item is moved within it's originating grid adjust item's left and
  2645. // top props. Otherwise if item is moved to/within another grid get the
  2646. // container element's offset (from the element's content edge).
  2647. if (grid === rootGrid) {
  2648. itemRect.left = drag._gridX + item._marginLeft;
  2649. itemRect.top = drag._gridY + item._marginTop;
  2650. } else {
  2651. grid._updateBorders(1, 0, 1, 0);
  2652. gridOffsetLeft = grid._left + grid._borderLeft;
  2653. gridOffsetTop = grid._top + grid._borderTop;
  2654. }
  2655. // Loop through the target grid items and try to find the best match.
  2656. for (i = 0; i < grid._items.length; i++) {
  2657. target = grid._items[i];
  2658. // If the target item is not active or the target item is the dragged
  2659. // item let's skip to the next item.
  2660. if (!target._isActive || target === item) {
  2661. continue;
  2662. }
  2663. // Mark the grid as having valid target items.
  2664. hasValidTargets = true;
  2665. // Calculate the target's overlap score with the dragged item.
  2666. targetRect.width = target._width;
  2667. targetRect.height = target._height;
  2668. targetRect.left = target._left + target._marginLeft + gridOffsetLeft;
  2669. targetRect.top = target._top + target._marginTop + gridOffsetTop;
  2670. score = getIntersectionScore(itemRect, targetRect);
  2671. // Update best match index and score if the target's overlap score with
  2672. // the dragged item is higher than the current best match score.
  2673. if (score > matchScore) {
  2674. matchIndex = i;
  2675. matchScore = score;
  2676. }
  2677. }
  2678. // If there is no valid match and the dragged item is being moved into
  2679. // another grid we need to do some guess work here. If there simply are no
  2680. // valid targets (which means that the dragged item will be the only active
  2681. // item in the new grid) we can just add it as the first item. If we have
  2682. // valid items in the new grid and the dragged item is overlapping one or
  2683. // more of the items in the new grid let's make an exception with the
  2684. // threshold and just pick the item which the dragged item is overlapping
  2685. // most. However, if the dragged item is not overlapping any of the valid
  2686. // items in the new grid let's position it as the last item in the grid.
  2687. if (isMigration && matchScore < sortThreshold) {
  2688. matchIndex = hasValidTargets ? matchIndex : 0;
  2689. matchScore = sortThreshold;
  2690. }
  2691. // Check if the best match overlaps enough to justify a placement switch.
  2692. if (matchScore >= sortThreshold) {
  2693. returnData.grid = grid;
  2694. returnData.index = matchIndex;
  2695. returnData.action = isMigration ? migrateAction : sortAction;
  2696. return returnData;
  2697. }
  2698. return null;
  2699. };
  2700. })();
  2701. /**
  2702. * Public prototype methods
  2703. * ************************
  2704. */
  2705. /**
  2706. * Abort dragging and reset drag data.
  2707. *
  2708. * @public
  2709. */
  2710. ItemDrag.prototype.stop = function () {
  2711. if (!this._isActive) return;
  2712. // If the item is being dropped into another grid, finish it up and return
  2713. // immediately.
  2714. if (this._isMigrating) {
  2715. this._finishMigration();
  2716. return;
  2717. }
  2718. // Stop auto-scroll.
  2719. ItemDrag.autoScroller.removeItem(this._item);
  2720. // Cancel queued ticks.
  2721. var itemId = this._item._id;
  2722. cancelDragStartTick(itemId);
  2723. cancelDragMoveTick(itemId);
  2724. cancelDragScrollTick(itemId);
  2725. // Cancel sort procedure.
  2726. this._cancelSort();
  2727. if (this._isStarted) {
  2728. // Remove scroll listeners.
  2729. this._unbindScrollListeners();
  2730. var element = item._element;
  2731. var grid = this._getGrid();
  2732. var draggingClass = grid._settings.itemDraggingClass;
  2733. // Append item element to the container if it's not it's child. Also make
  2734. // sure the translate values are adjusted to account for the DOM shift.
  2735. if (element.parentNode !== grid._element) {
  2736. grid._element.appendChild(element);
  2737. item._setTranslate(this._gridX, this._gridY);
  2738. // We need to do forced reflow to make sure the dragging class is removed
  2739. // gracefully.
  2740. // eslint-disable-next-line
  2741. if (draggingClass) element.clientWidth;
  2742. }
  2743. // Remove dragging class.
  2744. removeClass(element, draggingClass);
  2745. }
  2746. // Reset drag data.
  2747. this._reset();
  2748. };
  2749. /**
  2750. * Manually trigger drag sort. This is only needed for special edge cases where
  2751. * e.g. you have disabled sort and want to trigger a sort right after enabling
  2752. * it (and don't want to wait for the next move/scroll event).
  2753. *
  2754. * @private
  2755. * @param {Boolean} [force=false]
  2756. */
  2757. ItemDrag.prototype.sort = function (force) {
  2758. var item = this._item;
  2759. if (this._isActive && item._isActive && this._dragMoveEvent) {
  2760. if (force === true) {
  2761. this._handleSort();
  2762. } else {
  2763. addDragSortTick(item._id, this._handleSort);
  2764. }
  2765. }
  2766. };
  2767. /**
  2768. * Destroy instance.
  2769. *
  2770. * @public
  2771. */
  2772. ItemDrag.prototype.destroy = function () {
  2773. if (this._isDestroyed) return;
  2774. this.stop();
  2775. this._dragger.destroy();
  2776. ItemDrag.autoScroller.removeItem(this._item);
  2777. this._isDestroyed = true;
  2778. };
  2779. /**
  2780. * Private prototype methods
  2781. * *************************
  2782. */
  2783. /**
  2784. * Get Grid instance.
  2785. *
  2786. * @private
  2787. * @returns {?Grid}
  2788. */
  2789. ItemDrag.prototype._getGrid = function () {
  2790. return GRID_INSTANCES[this._gridId] || null;
  2791. };
  2792. /**
  2793. * Setup/reset drag data.
  2794. *
  2795. * @private
  2796. */
  2797. ItemDrag.prototype._reset = function () {
  2798. this._isActive = false;
  2799. this._isStarted = false;
  2800. // The dragged item's container element.
  2801. this._container = null;
  2802. // The dragged item's containing block.
  2803. this._containingBlock = null;
  2804. // Drag/scroll event data.
  2805. this._dragStartEvent = null;
  2806. this._dragMoveEvent = null;
  2807. this._dragPrevMoveEvent = null;
  2808. this._scrollEvent = null;
  2809. // All the elements which need to be listened for scroll events during
  2810. // dragging.
  2811. this._scrollers = [];
  2812. // The current translateX/translateY position.
  2813. this._left = 0;
  2814. this._top = 0;
  2815. // Dragged element's current position within the grid.
  2816. this._gridX = 0;
  2817. this._gridY = 0;
  2818. // Dragged element's current offset from window's northwest corner. Does
  2819. // not account for element's margins.
  2820. this._clientX = 0;
  2821. this._clientY = 0;
  2822. // Keep track of the clientX/Y diff for scrolling.
  2823. this._scrollDiffX = 0;
  2824. this._scrollDiffY = 0;
  2825. // Keep track of the clientX/Y diff for moving.
  2826. this._moveDiffX = 0;
  2827. this._moveDiffY = 0;
  2828. // Offset difference between the dragged element's temporary drag
  2829. // container and it's original container.
  2830. this._containerDiffX = 0;
  2831. this._containerDiffY = 0;
  2832. };
  2833. /**
  2834. * Bind drag scroll handlers to all scrollable ancestor elements of the
  2835. * dragged element and the drag container element.
  2836. *
  2837. * @private
  2838. */
  2839. ItemDrag.prototype._bindScrollListeners = function () {
  2840. var gridContainer = this._getGrid()._element;
  2841. var dragContainer = this._container;
  2842. var scrollers = this._scrollers;
  2843. var gridScrollers;
  2844. var i;
  2845. // Get dragged element's scrolling parents.
  2846. scrollers.length = 0;
  2847. getScrollableAncestors(this._item._element.parentNode, scrollers);
  2848. // If drag container is defined and it's not the same element as grid
  2849. // container then we need to add the grid container and it's scroll parents
  2850. // to the elements which are going to be listener for scroll events.
  2851. if (dragContainer !== gridContainer) {
  2852. gridScrollers = [];
  2853. getScrollableAncestors(gridContainer, gridScrollers);
  2854. for (i = 0; i < gridScrollers.length; i++) {
  2855. if (scrollers.indexOf(gridScrollers[i]) < 0) {
  2856. scrollers.push(gridScrollers[i]);
  2857. }
  2858. }
  2859. }
  2860. // Bind scroll listeners.
  2861. for (i = 0; i < scrollers.length; i++) {
  2862. scrollers[i].addEventListener('scroll', this._onScroll, SCROLL_LISTENER_OPTIONS);
  2863. }
  2864. };
  2865. /**
  2866. * Unbind currently bound drag scroll handlers from all scrollable ancestor
  2867. * elements of the dragged element and the drag container element.
  2868. *
  2869. * @private
  2870. */
  2871. ItemDrag.prototype._unbindScrollListeners = function () {
  2872. var scrollers = this._scrollers;
  2873. var i;
  2874. for (i = 0; i < scrollers.length; i++) {
  2875. scrollers[i].removeEventListener('scroll', this._onScroll, SCROLL_LISTENER_OPTIONS);
  2876. }
  2877. scrollers.length = 0;
  2878. };
  2879. /**
  2880. * Unbind currently bound drag scroll handlers from all scrollable ancestor
  2881. * elements of the dragged element and the drag container element.
  2882. *
  2883. * @private
  2884. * @param {Object} event
  2885. * @returns {Boolean}
  2886. */
  2887. ItemDrag.prototype._resolveStartPredicate = function (event) {
  2888. var predicate = this._startPredicateData;
  2889. if (event.distance < predicate.distance || predicate.delay) return;
  2890. this._resetStartPredicate();
  2891. return true;
  2892. };
  2893. /**
  2894. * Forcefully resolve drag start predicate.
  2895. *
  2896. * @private
  2897. * @param {Object} event
  2898. */
  2899. ItemDrag.prototype._forceResolveStartPredicate = function (event) {
  2900. if (!this._isDestroyed && this._startPredicateState === START_PREDICATE_PENDING) {
  2901. this._startPredicateState = START_PREDICATE_RESOLVED;
  2902. this._onStart(event);
  2903. }
  2904. };
  2905. /**
  2906. * Finalize start predicate.
  2907. *
  2908. * @private
  2909. * @param {Object} event
  2910. */
  2911. ItemDrag.prototype._finishStartPredicate = function (event) {
  2912. var element = this._item._element;
  2913. // Check if this is a click (very subjective heuristics).
  2914. var isClick = Math.abs(event.deltaX) < 2 && Math.abs(event.deltaY) < 2 && event.deltaTime < 200;
  2915. // Reset predicate.
  2916. this._resetStartPredicate();
  2917. // If the gesture can be interpreted as click let's try to open the element's
  2918. // href url (if it is an anchor element).
  2919. if (isClick) openAnchorHref(element);
  2920. };
  2921. /**
  2922. * Reset drag sort heuristics.
  2923. *
  2924. * @private
  2925. * @param {Number} x
  2926. * @param {Number} y
  2927. */
  2928. ItemDrag.prototype._resetHeuristics = function (x, y) {
  2929. this._blockedSortIndex = null;
  2930. this._sortX1 = this._sortX2 = x;
  2931. this._sortY1 = this._sortY2 = y;
  2932. };
  2933. /**
  2934. * Run heuristics and return true if overlap check can be performed, and false
  2935. * if it can not.
  2936. *
  2937. * @private
  2938. * @param {Number} x
  2939. * @param {Number} y
  2940. * @returns {Boolean}
  2941. */
  2942. ItemDrag.prototype._checkHeuristics = function (x, y) {
  2943. var settings = this._getGrid()._settings.dragSortHeuristics;
  2944. var minDist = settings.minDragDistance;
  2945. // Skip heuristics if not needed.
  2946. if (minDist <= 0) {
  2947. this._blockedSortIndex = null;
  2948. return true;
  2949. }
  2950. var diffX = x - this._sortX2;
  2951. var diffY = y - this._sortY2;
  2952. // If we can't do proper bounce back check make sure that the blocked index
  2953. // is not set.
  2954. var canCheckBounceBack = minDist > 3 && settings.minBounceBackAngle > 0;
  2955. if (!canCheckBounceBack) {
  2956. this._blockedSortIndex = null;
  2957. }
  2958. if (Math.abs(diffX) > minDist || Math.abs(diffY) > minDist) {
  2959. // Reset blocked index if angle changed enough. This check requires a
  2960. // minimum value of 3 for minDragDistance to function properly.
  2961. if (canCheckBounceBack) {
  2962. var angle = Math.atan2(diffX, diffY);
  2963. var prevAngle = Math.atan2(this._sortX2 - this._sortX1, this._sortY2 - this._sortY1);
  2964. var deltaAngle = Math.atan2(Math.sin(angle - prevAngle), Math.cos(angle - prevAngle));
  2965. if (Math.abs(deltaAngle) > settings.minBounceBackAngle) {
  2966. this._blockedSortIndex = null;
  2967. }
  2968. }
  2969. // Update points.
  2970. this._sortX1 = this._sortX2;
  2971. this._sortY1 = this._sortY2;
  2972. this._sortX2 = x;
  2973. this._sortY2 = y;
  2974. return true;
  2975. }
  2976. return false;
  2977. };
  2978. /**
  2979. * Reset for default drag start predicate function.
  2980. *
  2981. * @private
  2982. */
  2983. ItemDrag.prototype._resetStartPredicate = function () {
  2984. var predicate = this._startPredicateData;
  2985. if (predicate) {
  2986. if (predicate.delayTimer) {
  2987. predicate.delayTimer = window.clearTimeout(predicate.delayTimer);
  2988. }
  2989. this._startPredicateData = null;
  2990. }
  2991. };
  2992. /**
  2993. * Handle the sorting procedure. Manage drag sort heuristics/interval and
  2994. * check overlap when necessary.
  2995. *
  2996. * @private
  2997. */
  2998. ItemDrag.prototype._handleSort = function () {
  2999. if (!this._isActive) return;
  3000. var settings = this._getGrid()._settings;
  3001. // No sorting when drag sort is disabled. Also, account for the scenario where
  3002. // dragSort is temporarily disabled during drag procedure so we need to reset
  3003. // sort timer heuristics state too.
  3004. if (
  3005. !settings.dragSort ||
  3006. (!settings.dragAutoScroll.sortDuringScroll && ItemDrag.autoScroller.isItemScrolling(this._item))
  3007. ) {
  3008. this._sortX1 = this._sortX2 = this._gridX;
  3009. this._sortY1 = this._sortY2 = this._gridY;
  3010. // We set this to true intentionally so that overlap check would be
  3011. // triggered as soon as possible after sort becomes enabled again.
  3012. this._isSortNeeded = true;
  3013. if (this._sortTimer !== undefined) {
  3014. this._sortTimer = window.clearTimeout(this._sortTimer);
  3015. }
  3016. return;
  3017. }
  3018. // If sorting is enabled we always need to run the heuristics check to keep
  3019. // the tracked coordinates updated. We also allow an exception when the sort
  3020. // timer is finished because the heuristics are intended to prevent overlap
  3021. // checks based on the dragged element's immediate movement and a delayed
  3022. // overlap check is valid if it comes through, because it was valid when it
  3023. // was invoked.
  3024. var shouldSort = this._checkHeuristics(this._gridX, this._gridY);
  3025. if (!this._isSortNeeded && !shouldSort) return;
  3026. var sortInterval = settings.dragSortHeuristics.sortInterval;
  3027. if (sortInterval <= 0 || this._isSortNeeded) {
  3028. this._isSortNeeded = false;
  3029. if (this._sortTimer !== undefined) {
  3030. this._sortTimer = window.clearTimeout(this._sortTimer);
  3031. }
  3032. this._checkOverlap();
  3033. } else if (this._sortTimer === undefined) {
  3034. this._sortTimer = window.setTimeout(this._handleSortDelayed, sortInterval);
  3035. }
  3036. };
  3037. /**
  3038. * Delayed sort handler.
  3039. *
  3040. * @private
  3041. */
  3042. ItemDrag.prototype._handleSortDelayed = function () {
  3043. this._isSortNeeded = true;
  3044. this._sortTimer = undefined;
  3045. addDragSortTick(this._item._id, this._handleSort);
  3046. };
  3047. /**
  3048. * Cancel and reset sort procedure.
  3049. *
  3050. * @private
  3051. */
  3052. ItemDrag.prototype._cancelSort = function () {
  3053. this._isSortNeeded = false;
  3054. if (this._sortTimer !== undefined) {
  3055. this._sortTimer = window.clearTimeout(this._sortTimer);
  3056. }
  3057. cancelDragSortTick(this._item._id);
  3058. };
  3059. /**
  3060. * Handle the ending of the drag procedure for sorting.
  3061. *
  3062. * @private
  3063. */
  3064. ItemDrag.prototype._finishSort = function () {
  3065. var isSortEnabled = this._getGrid()._settings.dragSort;
  3066. var needsFinalCheck = isSortEnabled && (this._isSortNeeded || this._sortTimer !== undefined);
  3067. this._cancelSort();
  3068. if (needsFinalCheck) this._checkOverlap();
  3069. };
  3070. /**
  3071. * Check (during drag) if an item is overlapping other items and based on
  3072. * the configuration layout the items.
  3073. *
  3074. * @private
  3075. */
  3076. ItemDrag.prototype._checkOverlap = function () {
  3077. if (!this._isActive) return;
  3078. var item = this._item;
  3079. var settings = this._getGrid()._settings;
  3080. var result;
  3081. var currentGrid;
  3082. var currentIndex;
  3083. var targetGrid;
  3084. var targetIndex;
  3085. var targetItem;
  3086. var sortAction;
  3087. var isMigration;
  3088. // Get overlap check result.
  3089. if (isFunction(settings.dragSortPredicate)) {
  3090. result = settings.dragSortPredicate(item, this._dragMoveEvent);
  3091. } else {
  3092. result = ItemDrag.defaultSortPredicate(item, settings.dragSortPredicate);
  3093. }
  3094. // Let's make sure the result object has a valid index before going further.
  3095. if (!result || typeof result.index !== 'number') return;
  3096. sortAction = result.action === ACTION_SWAP ? ACTION_SWAP : ACTION_MOVE;
  3097. currentGrid = item.getGrid();
  3098. targetGrid = result.grid || currentGrid;
  3099. isMigration = currentGrid !== targetGrid;
  3100. currentIndex = currentGrid._items.indexOf(item);
  3101. targetIndex = normalizeArrayIndex(
  3102. targetGrid._items,
  3103. result.index,
  3104. isMigration && sortAction === ACTION_MOVE ? 1 : 0
  3105. );
  3106. // Prevent position bounce.
  3107. if (!isMigration && targetIndex === this._blockedSortIndex) {
  3108. return;
  3109. }
  3110. // If the item was moved within it's current grid.
  3111. if (!isMigration) {
  3112. // Make sure the target index is not the current index.
  3113. if (currentIndex !== targetIndex) {
  3114. this._blockedSortIndex = currentIndex;
  3115. // Do the sort.
  3116. (sortAction === ACTION_SWAP ? arraySwap : arrayMove)(
  3117. currentGrid._items,
  3118. currentIndex,
  3119. targetIndex
  3120. );
  3121. // Emit move event.
  3122. if (currentGrid._hasListeners(EVENT_MOVE)) {
  3123. currentGrid._emit(EVENT_MOVE, {
  3124. item: item,
  3125. fromIndex: currentIndex,
  3126. toIndex: targetIndex,
  3127. action: sortAction,
  3128. });
  3129. }
  3130. // Layout the grid.
  3131. currentGrid.layout();
  3132. }
  3133. }
  3134. // If the item was moved to another grid.
  3135. else {
  3136. this._blockedSortIndex = null;
  3137. // Let's fetch the target item when it's still in it's original index.
  3138. targetItem = targetGrid._items[targetIndex];
  3139. // Emit beforeSend event.
  3140. if (currentGrid._hasListeners(EVENT_BEFORE_SEND)) {
  3141. currentGrid._emit(EVENT_BEFORE_SEND, {
  3142. item: item,
  3143. fromGrid: currentGrid,
  3144. fromIndex: currentIndex,
  3145. toGrid: targetGrid,
  3146. toIndex: targetIndex,
  3147. });
  3148. }
  3149. // Emit beforeReceive event.
  3150. if (targetGrid._hasListeners(EVENT_BEFORE_RECEIVE)) {
  3151. targetGrid._emit(EVENT_BEFORE_RECEIVE, {
  3152. item: item,
  3153. fromGrid: currentGrid,
  3154. fromIndex: currentIndex,
  3155. toGrid: targetGrid,
  3156. toIndex: targetIndex,
  3157. });
  3158. }
  3159. // Update item's grid id reference.
  3160. item._gridId = targetGrid._id;
  3161. // Update drag instance's migrating indicator.
  3162. this._isMigrating = item._gridId !== this._gridId;
  3163. // Move item instance from current grid to target grid.
  3164. currentGrid._items.splice(currentIndex, 1);
  3165. arrayInsert(targetGrid._items, item, targetIndex);
  3166. // Reset sort data.
  3167. item._sortData = null;
  3168. // Emit send event.
  3169. if (currentGrid._hasListeners(EVENT_SEND)) {
  3170. currentGrid._emit(EVENT_SEND, {
  3171. item: item,
  3172. fromGrid: currentGrid,
  3173. fromIndex: currentIndex,
  3174. toGrid: targetGrid,
  3175. toIndex: targetIndex,
  3176. });
  3177. }
  3178. // Emit receive event.
  3179. if (targetGrid._hasListeners(EVENT_RECEIVE)) {
  3180. targetGrid._emit(EVENT_RECEIVE, {
  3181. item: item,
  3182. fromGrid: currentGrid,
  3183. fromIndex: currentIndex,
  3184. toGrid: targetGrid,
  3185. toIndex: targetIndex,
  3186. });
  3187. }
  3188. // If the sort action is "swap" let's respect it and send the target item
  3189. // (if it exists) from the target grid to the originating grid. This process
  3190. // is done on purpose after the dragged item placed within the target grid
  3191. // so that we can keep this implementation as simple as possible utilizing
  3192. // the existing API.
  3193. if (sortAction === ACTION_SWAP && targetItem && targetItem.isActive()) {
  3194. // Sanity check to make sure that the target item is still part of the
  3195. // target grid. It could have been manipulated in the event handlers.
  3196. if (targetGrid._items.indexOf(targetItem) > -1) {
  3197. targetGrid.send(targetItem, currentGrid, currentIndex, {
  3198. appendTo: this._container || document.body,
  3199. layoutSender: false,
  3200. layoutReceiver: false,
  3201. });
  3202. }
  3203. }
  3204. // Layout both grids.
  3205. currentGrid.layout();
  3206. targetGrid.layout();
  3207. }
  3208. };
  3209. /**
  3210. * If item is dragged into another grid, finish the migration process
  3211. * gracefully.
  3212. *
  3213. * @private
  3214. */
  3215. ItemDrag.prototype._finishMigration = function () {
  3216. var item = this._item;
  3217. var release = item._dragRelease;
  3218. var element = item._element;
  3219. var isActive = item._isActive;
  3220. var targetGrid = item.getGrid();
  3221. var targetGridElement = targetGrid._element;
  3222. var targetSettings = targetGrid._settings;
  3223. var targetContainer = targetSettings.dragContainer || targetGridElement;
  3224. var currentSettings = this._getGrid()._settings;
  3225. var currentContainer = element.parentNode;
  3226. var currentVisClass = isActive
  3227. ? currentSettings.itemVisibleClass
  3228. : currentSettings.itemHiddenClass;
  3229. var nextVisClass = isActive ? targetSettings.itemVisibleClass : targetSettings.itemHiddenClass;
  3230. var translate;
  3231. var offsetDiff;
  3232. // Destroy current drag. Note that we need to set the migrating flag to
  3233. // false first, because otherwise we create an infinite loop between this
  3234. // and the drag.stop() method.
  3235. this._isMigrating = false;
  3236. this.destroy();
  3237. // Update item class.
  3238. if (currentSettings.itemClass !== targetSettings.itemClass) {
  3239. removeClass(element, currentSettings.itemClass);
  3240. addClass(element, targetSettings.itemClass);
  3241. }
  3242. // Update visibility class.
  3243. if (currentVisClass !== nextVisClass) {
  3244. removeClass(element, currentVisClass);
  3245. addClass(element, nextVisClass);
  3246. }
  3247. // Move the item inside the target container if it's different than the
  3248. // current container.
  3249. if (targetContainer !== currentContainer) {
  3250. targetContainer.appendChild(element);
  3251. offsetDiff = getOffsetDiff(currentContainer, targetContainer, true);
  3252. translate = getTranslate(element);
  3253. translate.x -= offsetDiff.left;
  3254. translate.y -= offsetDiff.top;
  3255. }
  3256. // Update item's cached dimensions.
  3257. item._refreshDimensions();
  3258. // Calculate the offset difference between target's drag container (if any)
  3259. // and actual grid container element. We save it later for the release
  3260. // process.
  3261. offsetDiff = getOffsetDiff(targetContainer, targetGridElement, true);
  3262. release._containerDiffX = offsetDiff.left;
  3263. release._containerDiffY = offsetDiff.top;
  3264. // Recreate item's drag handler.
  3265. item._drag = targetSettings.dragEnabled ? new ItemDrag(item) : null;
  3266. // Adjust the position of the item element if it was moved from a container
  3267. // to another.
  3268. if (targetContainer !== currentContainer) {
  3269. item._setTranslate(translate.x, translate.y);
  3270. }
  3271. // Update child element's styles to reflect the current visibility state.
  3272. item._visibility.setStyles(isActive ? targetSettings.visibleStyles : targetSettings.hiddenStyles);
  3273. // Start the release.
  3274. release.start();
  3275. };
  3276. /**
  3277. * Drag pre-start handler.
  3278. *
  3279. * @private
  3280. * @param {Object} event
  3281. */
  3282. ItemDrag.prototype._preStartCheck = function (event) {
  3283. // Let's activate drag start predicate state.
  3284. if (this._startPredicateState === START_PREDICATE_INACTIVE) {
  3285. this._startPredicateState = START_PREDICATE_PENDING;
  3286. }
  3287. // If predicate is pending try to resolve it.
  3288. if (this._startPredicateState === START_PREDICATE_PENDING) {
  3289. this._startPredicateResult = this._startPredicate(this._item, event);
  3290. if (this._startPredicateResult === true) {
  3291. this._startPredicateState = START_PREDICATE_RESOLVED;
  3292. this._onStart(event);
  3293. } else if (this._startPredicateResult === false) {
  3294. this._resetStartPredicate(event);
  3295. this._dragger._reset();
  3296. this._startPredicateState = START_PREDICATE_INACTIVE;
  3297. }
  3298. }
  3299. // Otherwise if predicate is resolved and drag is active, move the item.
  3300. else if (this._startPredicateState === START_PREDICATE_RESOLVED && this._isActive) {
  3301. this._onMove(event);
  3302. }
  3303. };
  3304. /**
  3305. * Drag pre-end handler.
  3306. *
  3307. * @private
  3308. * @param {Object} event
  3309. */
  3310. ItemDrag.prototype._preEndCheck = function (event) {
  3311. var isResolved = this._startPredicateState === START_PREDICATE_RESOLVED;
  3312. // Do final predicate check to allow user to unbind stuff for the current
  3313. // drag procedure within the predicate callback. The return value of this
  3314. // check will have no effect to the state of the predicate.
  3315. this._startPredicate(this._item, event);
  3316. this._startPredicateState = START_PREDICATE_INACTIVE;
  3317. if (!isResolved || !this._isActive) return;
  3318. if (this._isStarted) {
  3319. this._onEnd(event);
  3320. } else {
  3321. this.stop();
  3322. }
  3323. };
  3324. /**
  3325. * Drag start handler.
  3326. *
  3327. * @private
  3328. * @param {Object} event
  3329. */
  3330. ItemDrag.prototype._onStart = function (event) {
  3331. var item = this._item;
  3332. if (!item._isActive) return;
  3333. this._isActive = true;
  3334. this._dragStartEvent = event;
  3335. ItemDrag.autoScroller.addItem(item);
  3336. addDragStartTick(item._id, this._prepareStart, this._applyStart);
  3337. };
  3338. /**
  3339. * Prepare item to be dragged.
  3340. *
  3341. * @private
  3342. * ItemDrag.prototype
  3343. */
  3344. ItemDrag.prototype._prepareStart = function () {
  3345. if (!this._isActive) return;
  3346. var item = this._item;
  3347. if (!item._isActive) return;
  3348. var element = item._element;
  3349. var grid = this._getGrid();
  3350. var settings = grid._settings;
  3351. var gridContainer = grid._element;
  3352. var dragContainer = settings.dragContainer || gridContainer;
  3353. var containingBlock = getContainingBlock(dragContainer);
  3354. var translate = getTranslate(element);
  3355. var elementRect = element.getBoundingClientRect();
  3356. var hasDragContainer = dragContainer !== gridContainer;
  3357. this._container = dragContainer;
  3358. this._containingBlock = containingBlock;
  3359. this._clientX = elementRect.left;
  3360. this._clientY = elementRect.top;
  3361. this._left = this._gridX = translate.x;
  3362. this._top = this._gridY = translate.y;
  3363. this._scrollDiffX = this._scrollDiffY = 0;
  3364. this._moveDiffX = this._moveDiffY = 0;
  3365. this._resetHeuristics(this._gridX, this._gridY);
  3366. // If a specific drag container is set and it is different from the
  3367. // grid's container element we store the offset between containers.
  3368. if (hasDragContainer) {
  3369. var offsetDiff = getOffsetDiff(containingBlock, gridContainer);
  3370. this._containerDiffX = offsetDiff.left;
  3371. this._containerDiffY = offsetDiff.top;
  3372. }
  3373. };
  3374. /**
  3375. * Start drag for the item.
  3376. *
  3377. * @private
  3378. */
  3379. ItemDrag.prototype._applyStart = function () {
  3380. if (!this._isActive) return;
  3381. var item = this._item;
  3382. if (!item._isActive) return;
  3383. var grid = this._getGrid();
  3384. var element = item._element;
  3385. var release = item._dragRelease;
  3386. var migrate = item._migrate;
  3387. var hasDragContainer = this._container !== grid._element;
  3388. if (item.isPositioning()) {
  3389. item._layout.stop(true, this._left, this._top);
  3390. }
  3391. if (migrate._isActive) {
  3392. this._left -= migrate._containerDiffX;
  3393. this._top -= migrate._containerDiffY;
  3394. this._gridX -= migrate._containerDiffX;
  3395. this._gridY -= migrate._containerDiffY;
  3396. migrate.stop(true, this._left, this._top);
  3397. }
  3398. if (item.isReleasing()) {
  3399. release._reset();
  3400. }
  3401. if (grid._settings.dragPlaceholder.enabled) {
  3402. item._dragPlaceholder.create();
  3403. }
  3404. this._isStarted = true;
  3405. grid._emit(EVENT_DRAG_INIT, item, this._dragStartEvent);
  3406. if (hasDragContainer) {
  3407. // If the dragged element is a child of the drag container all we need to
  3408. // do is setup the relative drag position data.
  3409. if (element.parentNode === this._container) {
  3410. this._gridX -= this._containerDiffX;
  3411. this._gridY -= this._containerDiffY;
  3412. }
  3413. // Otherwise we need to append the element inside the correct container,
  3414. // setup the actual drag position data and adjust the element's translate
  3415. // values to account for the DOM position shift.
  3416. else {
  3417. this._left += this._containerDiffX;
  3418. this._top += this._containerDiffY;
  3419. this._container.appendChild(element);
  3420. item._setTranslate(this._left, this._top);
  3421. }
  3422. }
  3423. addClass(element, grid._settings.itemDraggingClass);
  3424. this._bindScrollListeners();
  3425. grid._emit(EVENT_DRAG_START, item, this._dragStartEvent);
  3426. };
  3427. /**
  3428. * Drag move handler.
  3429. *
  3430. * @private
  3431. * @param {Object} event
  3432. */
  3433. ItemDrag.prototype._onMove = function (event) {
  3434. var item = this._item;
  3435. if (!item._isActive) {
  3436. this.stop();
  3437. return;
  3438. }
  3439. this._dragMoveEvent = event;
  3440. addDragMoveTick(item._id, this._prepareMove, this._applyMove);
  3441. addDragSortTick(item._id, this._handleSort);
  3442. };
  3443. /**
  3444. * Prepare dragged item for moving.
  3445. *
  3446. * @private
  3447. */
  3448. ItemDrag.prototype._prepareMove = function () {
  3449. if (!this._isActive) return;
  3450. var item = this._item;
  3451. if (!item._isActive) return;
  3452. var settings = this._getGrid()._settings;
  3453. var axis = settings.dragAxis;
  3454. var nextEvent = this._dragMoveEvent;
  3455. var prevEvent = this._dragPrevMoveEvent || this._dragStartEvent || nextEvent;
  3456. // Update horizontal position data.
  3457. if (axis !== 'y') {
  3458. var moveDiffX = nextEvent.clientX - prevEvent.clientX;
  3459. this._left = this._left - this._moveDiffX + moveDiffX;
  3460. this._gridX = this._gridX - this._moveDiffX + moveDiffX;
  3461. this._clientX = this._clientX - this._moveDiffX + moveDiffX;
  3462. this._moveDiffX = moveDiffX;
  3463. }
  3464. // Update vertical position data.
  3465. if (axis !== 'x') {
  3466. var moveDiffY = nextEvent.clientY - prevEvent.clientY;
  3467. this._top = this._top - this._moveDiffY + moveDiffY;
  3468. this._gridY = this._gridY - this._moveDiffY + moveDiffY;
  3469. this._clientY = this._clientY - this._moveDiffY + moveDiffY;
  3470. this._moveDiffY = moveDiffY;
  3471. }
  3472. this._dragPrevMoveEvent = nextEvent;
  3473. };
  3474. /**
  3475. * Apply movement to dragged item.
  3476. *
  3477. * @private
  3478. */
  3479. ItemDrag.prototype._applyMove = function () {
  3480. if (!this._isActive) return;
  3481. var item = this._item;
  3482. if (!item._isActive) return;
  3483. this._moveDiffX = this._moveDiffY = 0;
  3484. item._setTranslate(this._left, this._top);
  3485. this._getGrid()._emit(EVENT_DRAG_MOVE, item, this._dragMoveEvent);
  3486. ItemDrag.autoScroller.updateItem(item);
  3487. };
  3488. /**
  3489. * Drag scroll handler.
  3490. *
  3491. * @private
  3492. * @param {Object} event
  3493. */
  3494. ItemDrag.prototype._onScroll = function (event) {
  3495. var item = this._item;
  3496. if (!item._isActive) {
  3497. this.stop();
  3498. return;
  3499. }
  3500. this._scrollEvent = event;
  3501. addDragScrollTick(item._id, this._prepareScroll, this._applyScroll);
  3502. addDragSortTick(item._id, this._handleSort);
  3503. };
  3504. /**
  3505. * Prepare dragged item for scrolling.
  3506. *
  3507. * @private
  3508. */
  3509. ItemDrag.prototype._prepareScroll = function () {
  3510. if (!this._isActive) return;
  3511. // If item is not active do nothing.
  3512. var item = this._item;
  3513. if (!item._isActive) return;
  3514. var element = item._element;
  3515. var grid = this._getGrid();
  3516. var gridContainer = grid._element;
  3517. var axis = grid._settings.dragAxis;
  3518. var moveX = axis !== 'y';
  3519. var moveY = axis !== 'x';
  3520. var rect = element.getBoundingClientRect();
  3521. // Update container diff.
  3522. if (this._container !== gridContainer) {
  3523. var offsetDiff = getOffsetDiff(this._containingBlock, gridContainer);
  3524. this._containerDiffX = offsetDiff.left;
  3525. this._containerDiffY = offsetDiff.top;
  3526. }
  3527. // Update horizontal position data.
  3528. if (moveX) {
  3529. var scrollDiffX = this._clientX - this._moveDiffX - this._scrollDiffX - rect.left;
  3530. this._left = this._left - this._scrollDiffX + scrollDiffX;
  3531. this._scrollDiffX = scrollDiffX;
  3532. }
  3533. // Update vertical position data.
  3534. if (moveY) {
  3535. var scrollDiffY = this._clientY - this._moveDiffY - this._scrollDiffY - rect.top;
  3536. this._top = this._top - this._scrollDiffY + scrollDiffY;
  3537. this._scrollDiffY = scrollDiffY;
  3538. }
  3539. // Update grid position.
  3540. this._gridX = this._left - this._containerDiffX;
  3541. this._gridY = this._top - this._containerDiffY;
  3542. };
  3543. /**
  3544. * Apply scroll to dragged item.
  3545. *
  3546. * @private
  3547. */
  3548. ItemDrag.prototype._applyScroll = function () {
  3549. if (!this._isActive) return;
  3550. var item = this._item;
  3551. if (!item._isActive) return;
  3552. this._scrollDiffX = this._scrollDiffY = 0;
  3553. item._setTranslate(this._left, this._top);
  3554. this._getGrid()._emit(EVENT_DRAG_SCROLL, item, this._scrollEvent);
  3555. };
  3556. /**
  3557. * Drag end handler.
  3558. *
  3559. * @private
  3560. * @param {Object} event
  3561. */
  3562. ItemDrag.prototype._onEnd = function (event) {
  3563. var item = this._item;
  3564. var element = item._element;
  3565. var grid = this._getGrid();
  3566. var settings = grid._settings;
  3567. var release = item._dragRelease;
  3568. // If item is not active, reset drag.
  3569. if (!item._isActive) {
  3570. this.stop();
  3571. return;
  3572. }
  3573. // Cancel queued ticks.
  3574. cancelDragStartTick(item._id);
  3575. cancelDragMoveTick(item._id);
  3576. cancelDragScrollTick(item._id);
  3577. // Finish sort procedure (does final overlap check if needed).
  3578. this._finishSort();
  3579. // Remove scroll listeners.
  3580. this._unbindScrollListeners();
  3581. // Setup release data.
  3582. release._containerDiffX = this._containerDiffX;
  3583. release._containerDiffY = this._containerDiffY;
  3584. // Reset drag data.
  3585. this._reset();
  3586. // Remove drag class name from element.
  3587. removeClass(element, settings.itemDraggingClass);
  3588. // Stop auto-scroll.
  3589. ItemDrag.autoScroller.removeItem(item);
  3590. // Emit dragEnd event.
  3591. grid._emit(EVENT_DRAG_END, item, event);
  3592. // Finish up the migration process or start the release process.
  3593. this._isMigrating ? this._finishMigration() : release.start();
  3594. };
  3595. /**
  3596. * Private helpers
  3597. * ***************
  3598. */
  3599. /**
  3600. * Check if an element is an anchor element and open the href url if possible.
  3601. *
  3602. * @param {HTMLElement} element
  3603. */
  3604. function openAnchorHref(element) {
  3605. // Make sure the element is anchor element.
  3606. if (element.tagName.toLowerCase() !== 'a') return;
  3607. // Get href and make sure it exists.
  3608. var href = element.getAttribute('href');
  3609. if (!href) return;
  3610. // Finally let's navigate to the link href.
  3611. var target = element.getAttribute('target');
  3612. if (target && target !== '_self') {
  3613. window.open(href, target);
  3614. } else {
  3615. window.location.href = href;
  3616. }
  3617. }
  3618. /**
  3619. * Get current values of the provided styles definition object or array.
  3620. *
  3621. * @param {HTMLElement} element
  3622. * @param {(Object|Array} styles
  3623. * @return {Object}
  3624. */
  3625. function getCurrentStyles(element, styles) {
  3626. var result = {};
  3627. var prop, i;
  3628. if (Array.isArray(styles)) {
  3629. for (i = 0; i < styles.length; i++) {
  3630. prop = styles[i];
  3631. result[prop] = getStyle(element, getStyleName(prop));
  3632. }
  3633. } else {
  3634. for (prop in styles) {
  3635. result[prop] = getStyle(element, getStyleName(prop));
  3636. }
  3637. }
  3638. return result;
  3639. }
  3640. var unprefixRegEx = /^(webkit|moz|ms|o|Webkit|Moz|MS|O)(?=[A-Z])/;
  3641. var cache$2 = {};
  3642. /**
  3643. * Remove any potential vendor prefixes from a property name.
  3644. *
  3645. * @param {String} prop
  3646. * @returns {String}
  3647. */
  3648. function getUnprefixedPropName(prop) {
  3649. var result = cache$2[prop];
  3650. if (result) return result;
  3651. result = prop.replace(unprefixRegEx, '');
  3652. if (result !== prop) {
  3653. result = result[0].toLowerCase() + result.slice(1);
  3654. }
  3655. cache$2[prop] = result;
  3656. return result;
  3657. }
  3658. var nativeCode = '[native code]';
  3659. /**
  3660. * Check if a value (e.g. a method or constructor) is native code. Good for
  3661. * detecting when a polyfill is used and when not.
  3662. *
  3663. * @param {*} feat
  3664. * @returns {Boolean}
  3665. */
  3666. function isNative(feat) {
  3667. var S = window.Symbol;
  3668. return !!(
  3669. feat &&
  3670. isFunction(S) &&
  3671. isFunction(S.toString) &&
  3672. S(feat).toString().indexOf(nativeCode) > -1
  3673. );
  3674. }
  3675. /**
  3676. * Set inline styles to an element.
  3677. *
  3678. * @param {HTMLElement} element
  3679. * @param {Object} styles
  3680. */
  3681. function setStyles(element, styles) {
  3682. for (var prop in styles) {
  3683. element.style[prop] = styles[prop];
  3684. }
  3685. }
  3686. var HAS_WEB_ANIMATIONS = !!(Element && isFunction(Element.prototype.animate));
  3687. var HAS_NATIVE_WEB_ANIMATIONS = !!(Element && isNative(Element.prototype.animate));
  3688. /**
  3689. * Item animation handler powered by Web Animations API.
  3690. *
  3691. * @class
  3692. * @param {HTMLElement} element
  3693. */
  3694. function Animator(element) {
  3695. this._element = element;
  3696. this._animation = null;
  3697. this._duration = 0;
  3698. this._easing = '';
  3699. this._callback = null;
  3700. this._props = [];
  3701. this._values = [];
  3702. this._isDestroyed = false;
  3703. this._onFinish = this._onFinish.bind(this);
  3704. }
  3705. /**
  3706. * Public prototype methods
  3707. * ************************
  3708. */
  3709. /**
  3710. * Start instance's animation. Automatically stops current animation if it is
  3711. * running.
  3712. *
  3713. * @public
  3714. * @param {Object} propsFrom
  3715. * @param {Object} propsTo
  3716. * @param {Object} [options]
  3717. * @param {Number} [options.duration=300]
  3718. * @param {String} [options.easing='ease']
  3719. * @param {Function} [options.onFinish]
  3720. */
  3721. Animator.prototype.start = function (propsFrom, propsTo, options) {
  3722. if (this._isDestroyed) return;
  3723. var element = this._element;
  3724. var opts = options || {};
  3725. // If we don't have web animations available let's not animate.
  3726. if (!HAS_WEB_ANIMATIONS) {
  3727. setStyles(element, propsTo);
  3728. this._callback = isFunction(opts.onFinish) ? opts.onFinish : null;
  3729. this._onFinish();
  3730. return;
  3731. }
  3732. var animation = this._animation;
  3733. var currentProps = this._props;
  3734. var currentValues = this._values;
  3735. var duration = opts.duration || 300;
  3736. var easing = opts.easing || 'ease';
  3737. var cancelAnimation = false;
  3738. var propName, propCount, propIndex;
  3739. // If we have an existing animation running, let's check if it needs to be
  3740. // cancelled or if it can continue running.
  3741. if (animation) {
  3742. propCount = 0;
  3743. // Cancel animation if duration or easing has changed.
  3744. if (duration !== this._duration || easing !== this._easing) {
  3745. cancelAnimation = true;
  3746. }
  3747. // Check if the requested animation target props and values match with the
  3748. // current props and values.
  3749. if (!cancelAnimation) {
  3750. for (propName in propsTo) {
  3751. ++propCount;
  3752. propIndex = currentProps.indexOf(propName);
  3753. if (propIndex === -1 || propsTo[propName] !== currentValues[propIndex]) {
  3754. cancelAnimation = true;
  3755. break;
  3756. }
  3757. }
  3758. // Check if the target props count matches current props count. This is
  3759. // needed for the edge case scenario where target props contain the same
  3760. // styles as current props, but the current props have some additional
  3761. // props.
  3762. if (propCount !== currentProps.length) {
  3763. cancelAnimation = true;
  3764. }
  3765. }
  3766. }
  3767. // Cancel animation (if required).
  3768. if (cancelAnimation) animation.cancel();
  3769. // Store animation callback.
  3770. this._callback = isFunction(opts.onFinish) ? opts.onFinish : null;
  3771. // If we have a running animation that does not need to be cancelled, let's
  3772. // call it a day here and let it run.
  3773. if (animation && !cancelAnimation) return;
  3774. // Store target props and values to instance.
  3775. currentProps.length = currentValues.length = 0;
  3776. for (propName in propsTo) {
  3777. currentProps.push(propName);
  3778. currentValues.push(propsTo[propName]);
  3779. }
  3780. // Start the animation. We need to provide unprefixed property names to the
  3781. // Web Animations polyfill if it is being used. If we have native Web
  3782. // Animations available we need to provide prefixed properties instead.
  3783. this._duration = duration;
  3784. this._easing = easing;
  3785. this._animation = element.animate(
  3786. [
  3787. createFrame(propsFrom, HAS_NATIVE_WEB_ANIMATIONS),
  3788. createFrame(propsTo, HAS_NATIVE_WEB_ANIMATIONS),
  3789. ],
  3790. {
  3791. duration: duration,
  3792. easing: easing,
  3793. }
  3794. );
  3795. this._animation.onfinish = this._onFinish;
  3796. // Set the end styles. This makes sure that the element stays at the end
  3797. // values after animation is finished.
  3798. setStyles(element, propsTo);
  3799. };
  3800. /**
  3801. * Stop instance's current animation if running.
  3802. *
  3803. * @public
  3804. */
  3805. Animator.prototype.stop = function () {
  3806. if (this._isDestroyed || !this._animation) return;
  3807. this._animation.cancel();
  3808. this._animation = this._callback = null;
  3809. this._props.length = this._values.length = 0;
  3810. };
  3811. /**
  3812. * Read the current values of the element's animated styles from the DOM.
  3813. *
  3814. * @public
  3815. * @return {Object}
  3816. */
  3817. Animator.prototype.getCurrentStyles = function () {
  3818. return getCurrentStyles(element, currentProps);
  3819. };
  3820. /**
  3821. * Check if the item is being animated currently.
  3822. *
  3823. * @public
  3824. * @return {Boolean}
  3825. */
  3826. Animator.prototype.isAnimating = function () {
  3827. return !!this._animation;
  3828. };
  3829. /**
  3830. * Destroy the instance and stop current animation if it is running.
  3831. *
  3832. * @public
  3833. */
  3834. Animator.prototype.destroy = function () {
  3835. if (this._isDestroyed) return;
  3836. this.stop();
  3837. this._element = null;
  3838. this._isDestroyed = true;
  3839. };
  3840. /**
  3841. * Private prototype methods
  3842. * *************************
  3843. */
  3844. /**
  3845. * Animation end handler.
  3846. *
  3847. * @private
  3848. */
  3849. Animator.prototype._onFinish = function () {
  3850. var callback = this._callback;
  3851. this._animation = this._callback = null;
  3852. this._props.length = this._values.length = 0;
  3853. callback && callback();
  3854. };
  3855. /**
  3856. * Private helpers
  3857. * ***************
  3858. */
  3859. function createFrame(props, prefix) {
  3860. var frame = {};
  3861. for (var prop in props) {
  3862. frame[prefix ? prop : getUnprefixedPropName(prop)] = props[prop];
  3863. }
  3864. return frame;
  3865. }
  3866. /**
  3867. * Transform translateX and translateY value into CSS transform style
  3868. * property's value.
  3869. *
  3870. * @param {Number} x
  3871. * @param {Number} y
  3872. * @returns {String}
  3873. */
  3874. function getTranslateString(x, y) {
  3875. return 'translateX(' + x + 'px) translateY(' + y + 'px)';
  3876. }
  3877. /**
  3878. * Drag placeholder.
  3879. *
  3880. * @class
  3881. * @param {Item} item
  3882. */
  3883. function ItemDragPlaceholder(item) {
  3884. this._item = item;
  3885. this._animation = new Animator();
  3886. this._element = null;
  3887. this._className = '';
  3888. this._didMigrate = false;
  3889. this._resetAfterLayout = false;
  3890. this._left = 0;
  3891. this._top = 0;
  3892. this._transX = 0;
  3893. this._transY = 0;
  3894. this._nextTransX = 0;
  3895. this._nextTransY = 0;
  3896. // Bind animation handlers.
  3897. this._setupAnimation = this._setupAnimation.bind(this);
  3898. this._startAnimation = this._startAnimation.bind(this);
  3899. this._updateDimensions = this._updateDimensions.bind(this);
  3900. // Bind event handlers.
  3901. this._onLayoutStart = this._onLayoutStart.bind(this);
  3902. this._onLayoutEnd = this._onLayoutEnd.bind(this);
  3903. this._onReleaseEnd = this._onReleaseEnd.bind(this);
  3904. this._onMigrate = this._onMigrate.bind(this);
  3905. this._onHide = this._onHide.bind(this);
  3906. }
  3907. /**
  3908. * Private prototype methods
  3909. * *************************
  3910. */
  3911. /**
  3912. * Update placeholder's dimensions to match the item's dimensions.
  3913. *
  3914. * @private
  3915. */
  3916. ItemDragPlaceholder.prototype._updateDimensions = function () {
  3917. if (!this.isActive()) return;
  3918. setStyles(this._element, {
  3919. width: this._item._width + 'px',
  3920. height: this._item._height + 'px',
  3921. });
  3922. };
  3923. /**
  3924. * Move placeholder to a new position.
  3925. *
  3926. * @private
  3927. * @param {Item[]} items
  3928. * @param {Boolean} isInstant
  3929. */
  3930. ItemDragPlaceholder.prototype._onLayoutStart = function (items, isInstant) {
  3931. var item = this._item;
  3932. // If the item is not part of the layout anymore reset placeholder.
  3933. if (items.indexOf(item) === -1) {
  3934. this.reset();
  3935. return;
  3936. }
  3937. var nextLeft = item._left;
  3938. var nextTop = item._top;
  3939. var currentLeft = this._left;
  3940. var currentTop = this._top;
  3941. // Keep track of item layout position.
  3942. this._left = nextLeft;
  3943. this._top = nextTop;
  3944. // If item's position did not change, and the item did not migrate and the
  3945. // layout is not instant and we can safely skip layout.
  3946. if (!isInstant && !this._didMigrate && currentLeft === nextLeft && currentTop === nextTop) {
  3947. return;
  3948. }
  3949. // Slots data is calculated with item margins added to them so we need to add
  3950. // item's left and top margin to the slot data to get the placeholder's
  3951. // next position.
  3952. var nextX = nextLeft + item._marginLeft;
  3953. var nextY = nextTop + item._marginTop;
  3954. // Just snap to new position without any animations if no animation is
  3955. // required or if placeholder moves between grids.
  3956. var grid = item.getGrid();
  3957. var animEnabled = !isInstant && grid._settings.layoutDuration > 0;
  3958. if (!animEnabled || this._didMigrate) {
  3959. // Cancel potential (queued) layout tick.
  3960. cancelPlaceholderLayoutTick(item._id);
  3961. // Snap placeholder to correct position.
  3962. this._element.style[transformProp] = getTranslateString(nextX, nextY);
  3963. this._animation.stop();
  3964. // Move placeholder inside correct container after migration.
  3965. if (this._didMigrate) {
  3966. grid.getElement().appendChild(this._element);
  3967. this._didMigrate = false;
  3968. }
  3969. return;
  3970. }
  3971. // Start the placeholder's layout animation in the next tick. We do this to
  3972. // avoid layout thrashing.
  3973. this._nextTransX = nextX;
  3974. this._nextTransY = nextY;
  3975. addPlaceholderLayoutTick(item._id, this._setupAnimation, this._startAnimation);
  3976. };
  3977. /**
  3978. * Prepare placeholder for layout animation.
  3979. *
  3980. * @private
  3981. */
  3982. ItemDragPlaceholder.prototype._setupAnimation = function () {
  3983. if (!this.isActive()) return;
  3984. var translate = getTranslate(this._element);
  3985. this._transX = translate.x;
  3986. this._transY = translate.y;
  3987. };
  3988. /**
  3989. * Start layout animation.
  3990. *
  3991. * @private
  3992. */
  3993. ItemDragPlaceholder.prototype._startAnimation = function () {
  3994. if (!this.isActive()) return;
  3995. var animation = this._animation;
  3996. var currentX = this._transX;
  3997. var currentY = this._transY;
  3998. var nextX = this._nextTransX;
  3999. var nextY = this._nextTransY;
  4000. // If placeholder is already in correct position let's just stop animation
  4001. // and be done with it.
  4002. if (currentX === nextX && currentY === nextY) {
  4003. if (animation.isAnimating()) {
  4004. this._element.style[transformProp] = getTranslateString(nextX, nextY);
  4005. animation.stop();
  4006. }
  4007. return;
  4008. }
  4009. // Otherwise let's start the animation.
  4010. var settings = this._item.getGrid()._settings;
  4011. var currentStyles = {};
  4012. var targetStyles = {};
  4013. currentStyles[transformProp] = getTranslateString(currentX, currentY);
  4014. targetStyles[transformProp] = getTranslateString(nextX, nextY);
  4015. animation.start(currentStyles, targetStyles, {
  4016. duration: settings.layoutDuration,
  4017. easing: settings.layoutEasing,
  4018. onFinish: this._onLayoutEnd,
  4019. });
  4020. };
  4021. /**
  4022. * Layout end handler.
  4023. *
  4024. * @private
  4025. */
  4026. ItemDragPlaceholder.prototype._onLayoutEnd = function () {
  4027. if (this._resetAfterLayout) {
  4028. this.reset();
  4029. }
  4030. };
  4031. /**
  4032. * Drag end handler. This handler is called when dragReleaseEnd event is
  4033. * emitted and receives the event data as it's argument.
  4034. *
  4035. * @private
  4036. * @param {Item} item
  4037. */
  4038. ItemDragPlaceholder.prototype._onReleaseEnd = function (item) {
  4039. if (item._id === this._item._id) {
  4040. // If the placeholder is not animating anymore we can safely reset it.
  4041. if (!this._animation.isAnimating()) {
  4042. this.reset();
  4043. return;
  4044. }
  4045. // If the placeholder item is still animating here, let's wait for it to
  4046. // finish it's animation.
  4047. this._resetAfterLayout = true;
  4048. }
  4049. };
  4050. /**
  4051. * Migration start handler. This handler is called when beforeSend event is
  4052. * emitted and receives the event data as it's argument.
  4053. *
  4054. * @private
  4055. * @param {Object} data
  4056. * @param {Item} data.item
  4057. * @param {Grid} data.fromGrid
  4058. * @param {Number} data.fromIndex
  4059. * @param {Grid} data.toGrid
  4060. * @param {Number} data.toIndex
  4061. */
  4062. ItemDragPlaceholder.prototype._onMigrate = function (data) {
  4063. // Make sure we have a matching item.
  4064. if (data.item !== this._item) return;
  4065. var grid = this._item.getGrid();
  4066. var nextGrid = data.toGrid;
  4067. // Unbind listeners from current grid.
  4068. grid.off(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
  4069. grid.off(EVENT_LAYOUT_START, this._onLayoutStart);
  4070. grid.off(EVENT_BEFORE_SEND, this._onMigrate);
  4071. grid.off(EVENT_HIDE_START, this._onHide);
  4072. // Bind listeners to the next grid.
  4073. nextGrid.on(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
  4074. nextGrid.on(EVENT_LAYOUT_START, this._onLayoutStart);
  4075. nextGrid.on(EVENT_BEFORE_SEND, this._onMigrate);
  4076. nextGrid.on(EVENT_HIDE_START, this._onHide);
  4077. // Mark the item as migrated.
  4078. this._didMigrate = true;
  4079. };
  4080. /**
  4081. * Reset placeholder if the associated item is hidden.
  4082. *
  4083. * @private
  4084. * @param {Item[]} items
  4085. */
  4086. ItemDragPlaceholder.prototype._onHide = function (items) {
  4087. if (items.indexOf(this._item) > -1) this.reset();
  4088. };
  4089. /**
  4090. * Public prototype methods
  4091. * ************************
  4092. */
  4093. /**
  4094. * Create placeholder. Note that this method only writes to DOM and does not
  4095. * read anything from DOM so it should not cause any additional layout
  4096. * thrashing when it's called at the end of the drag start procedure.
  4097. *
  4098. * @public
  4099. */
  4100. ItemDragPlaceholder.prototype.create = function () {
  4101. // If we already have placeholder set up we can skip the initiation logic.
  4102. if (this.isActive()) {
  4103. this._resetAfterLayout = false;
  4104. return;
  4105. }
  4106. var item = this._item;
  4107. var grid = item.getGrid();
  4108. var settings = grid._settings;
  4109. var animation = this._animation;
  4110. // Keep track of layout position.
  4111. this._left = item._left;
  4112. this._top = item._top;
  4113. // Create placeholder element.
  4114. var element;
  4115. if (isFunction(settings.dragPlaceholder.createElement)) {
  4116. element = settings.dragPlaceholder.createElement(item);
  4117. } else {
  4118. element = document.createElement('div');
  4119. }
  4120. this._element = element;
  4121. // Update element to animation instance.
  4122. animation._element = element;
  4123. // Add placeholder class to the placeholder element.
  4124. this._className = settings.itemPlaceholderClass || '';
  4125. if (this._className) {
  4126. addClass(element, this._className);
  4127. }
  4128. // Set initial styles.
  4129. setStyles(element, {
  4130. position: 'absolute',
  4131. left: '0px',
  4132. top: '0px',
  4133. width: item._width + 'px',
  4134. height: item._height + 'px',
  4135. });
  4136. // Set initial position.
  4137. element.style[transformProp] = getTranslateString(
  4138. item._left + item._marginLeft,
  4139. item._top + item._marginTop
  4140. );
  4141. // Bind event listeners.
  4142. grid.on(EVENT_LAYOUT_START, this._onLayoutStart);
  4143. grid.on(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
  4144. grid.on(EVENT_BEFORE_SEND, this._onMigrate);
  4145. grid.on(EVENT_HIDE_START, this._onHide);
  4146. // onCreate hook.
  4147. if (isFunction(settings.dragPlaceholder.onCreate)) {
  4148. settings.dragPlaceholder.onCreate(item, element);
  4149. }
  4150. // Insert the placeholder element to the grid.
  4151. grid.getElement().appendChild(element);
  4152. };
  4153. /**
  4154. * Reset placeholder data.
  4155. *
  4156. * @public
  4157. */
  4158. ItemDragPlaceholder.prototype.reset = function () {
  4159. if (!this.isActive()) return;
  4160. var element = this._element;
  4161. var item = this._item;
  4162. var grid = item.getGrid();
  4163. var settings = grid._settings;
  4164. var animation = this._animation;
  4165. // Reset flag.
  4166. this._resetAfterLayout = false;
  4167. // Cancel potential (queued) layout tick.
  4168. cancelPlaceholderLayoutTick(item._id);
  4169. cancelPlaceholderResizeTick(item._id);
  4170. // Reset animation instance.
  4171. animation.stop();
  4172. animation._element = null;
  4173. // Unbind event listeners.
  4174. grid.off(EVENT_DRAG_RELEASE_END, this._onReleaseEnd);
  4175. grid.off(EVENT_LAYOUT_START, this._onLayoutStart);
  4176. grid.off(EVENT_BEFORE_SEND, this._onMigrate);
  4177. grid.off(EVENT_HIDE_START, this._onHide);
  4178. // Remove placeholder class from the placeholder element.
  4179. if (this._className) {
  4180. removeClass(element, this._className);
  4181. this._className = '';
  4182. }
  4183. // Remove element.
  4184. element.parentNode.removeChild(element);
  4185. this._element = null;
  4186. // onRemove hook. Note that here we use the current grid's onRemove callback
  4187. // so if the item has migrated during drag the onRemove method will not be
  4188. // the originating grid's method.
  4189. if (isFunction(settings.dragPlaceholder.onRemove)) {
  4190. settings.dragPlaceholder.onRemove(item, element);
  4191. }
  4192. };
  4193. /**
  4194. * Check if placeholder is currently active (visible).
  4195. *
  4196. * @public
  4197. * @returns {Boolean}
  4198. */
  4199. ItemDragPlaceholder.prototype.isActive = function () {
  4200. return !!this._element;
  4201. };
  4202. /**
  4203. * Get placeholder element.
  4204. *
  4205. * @public
  4206. * @returns {?HTMLElement}
  4207. */
  4208. ItemDragPlaceholder.prototype.getElement = function () {
  4209. return this._element;
  4210. };
  4211. /**
  4212. * Update placeholder's dimensions to match the item's dimensions. Note that
  4213. * the updating is done asynchronously in the next tick to avoid layout
  4214. * thrashing.
  4215. *
  4216. * @public
  4217. */
  4218. ItemDragPlaceholder.prototype.updateDimensions = function () {
  4219. if (!this.isActive()) return;
  4220. addPlaceholderResizeTick(this._item._id, this._updateDimensions);
  4221. };
  4222. /**
  4223. * Destroy placeholder instance.
  4224. *
  4225. * @public
  4226. */
  4227. ItemDragPlaceholder.prototype.destroy = function () {
  4228. this.reset();
  4229. this._animation.destroy();
  4230. this._item = this._animation = null;
  4231. };
  4232. /**
  4233. * The release process handler constructor. Although this might seem as proper
  4234. * fit for the drag process this needs to be separated into it's own logic
  4235. * because there might be a scenario where drag is disabled, but the release
  4236. * process still needs to be implemented (dragging from a grid to another).
  4237. *
  4238. * @class
  4239. * @param {Item} item
  4240. */
  4241. function ItemDragRelease(item) {
  4242. this._item = item;
  4243. this._isActive = false;
  4244. this._isDestroyed = false;
  4245. this._isPositioningStarted = false;
  4246. this._containerDiffX = 0;
  4247. this._containerDiffY = 0;
  4248. }
  4249. /**
  4250. * Public prototype methods
  4251. * ************************
  4252. */
  4253. /**
  4254. * Start the release process of an item.
  4255. *
  4256. * @public
  4257. */
  4258. ItemDragRelease.prototype.start = function () {
  4259. if (this._isDestroyed || this._isActive) return;
  4260. var item = this._item;
  4261. var grid = item.getGrid();
  4262. var settings = grid._settings;
  4263. this._isActive = true;
  4264. addClass(item._element, settings.itemReleasingClass);
  4265. if (!settings.dragRelease.useDragContainer) {
  4266. this._placeToGrid();
  4267. }
  4268. grid._emit(EVENT_DRAG_RELEASE_START, item);
  4269. // Let's start layout manually _only_ if there is no unfinished layout in
  4270. // about to finish.
  4271. if (!grid._nextLayoutData) item._layout.start(false);
  4272. };
  4273. /**
  4274. * End the release process of an item. This method can be used to abort an
  4275. * ongoing release process (animation) or finish the release process.
  4276. *
  4277. * @public
  4278. * @param {Boolean} [abort=false]
  4279. * - Should the release be aborted? When true, the release end event won't be
  4280. * emitted. Set to true only when you need to abort the release process
  4281. * while the item is animating to it's position.
  4282. * @param {Number} [left]
  4283. * - The element's current translateX value (optional).
  4284. * @param {Number} [top]
  4285. * - The element's current translateY value (optional).
  4286. */
  4287. ItemDragRelease.prototype.stop = function (abort, left, top) {
  4288. if (this._isDestroyed || !this._isActive) return;
  4289. var item = this._item;
  4290. var grid = item.getGrid();
  4291. if (!abort && (left === undefined || top === undefined)) {
  4292. left = item._left;
  4293. top = item._top;
  4294. }
  4295. var didReparent = this._placeToGrid(left, top);
  4296. this._reset(didReparent);
  4297. if (!abort) grid._emit(EVENT_DRAG_RELEASE_END, item);
  4298. };
  4299. ItemDragRelease.prototype.isJustReleased = function () {
  4300. return this._isActive && this._isPositioningStarted === false;
  4301. };
  4302. /**
  4303. * Destroy instance.
  4304. *
  4305. * @public
  4306. */
  4307. ItemDragRelease.prototype.destroy = function () {
  4308. if (this._isDestroyed) return;
  4309. this.stop(true);
  4310. this._item = null;
  4311. this._isDestroyed = true;
  4312. };
  4313. /**
  4314. * Private prototype methods
  4315. * *************************
  4316. */
  4317. /**
  4318. * Move the element back to the grid container element if it does not exist
  4319. * there already.
  4320. *
  4321. * @private
  4322. * @param {Number} [left]
  4323. * - The element's current translateX value (optional).
  4324. * @param {Number} [top]
  4325. * - The element's current translateY value (optional).
  4326. * @returns {Boolean}
  4327. * - Returns `true` if the element was reparented.
  4328. */
  4329. ItemDragRelease.prototype._placeToGrid = function (left, top) {
  4330. if (this._isDestroyed) return;
  4331. var item = this._item;
  4332. var element = item._element;
  4333. var container = item.getGrid()._element;
  4334. var didReparent = false;
  4335. if (element.parentNode !== container) {
  4336. if (left === undefined || top === undefined) {
  4337. var translate = getTranslate(element);
  4338. left = translate.x - this._containerDiffX;
  4339. top = translate.y - this._containerDiffY;
  4340. }
  4341. container.appendChild(element);
  4342. item._setTranslate(left, top);
  4343. didReparent = true;
  4344. }
  4345. this._containerDiffX = 0;
  4346. this._containerDiffY = 0;
  4347. return didReparent;
  4348. };
  4349. /**
  4350. * Reset data and remove releasing class.
  4351. *
  4352. * @private
  4353. * @param {Boolean} [needsReflow]
  4354. */
  4355. ItemDragRelease.prototype._reset = function (needsReflow) {
  4356. if (this._isDestroyed) return;
  4357. var item = this._item;
  4358. var releasingClass = item.getGrid()._settings.itemReleasingClass;
  4359. this._isActive = false;
  4360. this._isPositioningStarted = false;
  4361. this._containerDiffX = 0;
  4362. this._containerDiffY = 0;
  4363. // If the element was just reparented we need to do a forced reflow to remove
  4364. // the class gracefully.
  4365. if (releasingClass) {
  4366. // eslint-disable-next-line
  4367. if (needsReflow) item._element.clientWidth;
  4368. removeClass(item._element, releasingClass);
  4369. }
  4370. };
  4371. var MIN_ANIMATION_DISTANCE = 2;
  4372. /**
  4373. * Layout manager for Item instance, handles the positioning of an item.
  4374. *
  4375. * @class
  4376. * @param {Item} item
  4377. */
  4378. function ItemLayout(item) {
  4379. var element = item._element;
  4380. var elementStyle = element.style;
  4381. this._item = item;
  4382. this._isActive = false;
  4383. this._isDestroyed = false;
  4384. this._isInterrupted = false;
  4385. this._currentStyles = {};
  4386. this._targetStyles = {};
  4387. this._nextLeft = 0;
  4388. this._nextTop = 0;
  4389. this._offsetLeft = 0;
  4390. this._offsetTop = 0;
  4391. this._skipNextAnimation = false;
  4392. this._animOptions = {
  4393. onFinish: this._finish.bind(this),
  4394. duration: 0,
  4395. easing: 0,
  4396. };
  4397. // Set element's initial position styles.
  4398. elementStyle.left = '0px';
  4399. elementStyle.top = '0px';
  4400. item._setTranslate(0, 0);
  4401. this._animation = new Animator(element);
  4402. this._queue = 'layout-' + item._id;
  4403. // Bind animation handlers and finish method.
  4404. this._setupAnimation = this._setupAnimation.bind(this);
  4405. this._startAnimation = this._startAnimation.bind(this);
  4406. }
  4407. /**
  4408. * Public prototype methods
  4409. * ************************
  4410. */
  4411. /**
  4412. * Start item layout based on it's current data.
  4413. *
  4414. * @public
  4415. * @param {Boolean} instant
  4416. * @param {Function} [onFinish]
  4417. */
  4418. ItemLayout.prototype.start = function (instant, onFinish) {
  4419. if (this._isDestroyed) return;
  4420. var item = this._item;
  4421. var release = item._dragRelease;
  4422. var gridSettings = item.getGrid()._settings;
  4423. var isPositioning = this._isActive;
  4424. var isJustReleased = release.isJustReleased();
  4425. var animDuration = isJustReleased
  4426. ? gridSettings.dragRelease.duration
  4427. : gridSettings.layoutDuration;
  4428. var animEasing = isJustReleased ? gridSettings.dragRelease.easing : gridSettings.layoutEasing;
  4429. var animEnabled = !instant && !this._skipNextAnimation && animDuration > 0;
  4430. // If the item is currently positioning cancel potential queued layout tick
  4431. // and process current layout callback queue with interrupted flag on.
  4432. if (isPositioning) {
  4433. cancelLayoutTick(item._id);
  4434. item._emitter.burst(this._queue, true, item);
  4435. }
  4436. // Mark release positioning as started.
  4437. if (isJustReleased) release._isPositioningStarted = true;
  4438. // Push the callback to the callback queue.
  4439. if (isFunction(onFinish)) {
  4440. item._emitter.on(this._queue, onFinish);
  4441. }
  4442. // Reset animation skipping flag.
  4443. this._skipNextAnimation = false;
  4444. // If no animations are needed, easy peasy!
  4445. if (!animEnabled) {
  4446. this._updateOffsets();
  4447. item._setTranslate(this._nextLeft, this._nextTop);
  4448. this._animation.stop();
  4449. this._finish();
  4450. return;
  4451. }
  4452. // Let's make sure an ongoing animation's callback is cancelled before going
  4453. // further. Without this there's a chance that the animation will finish
  4454. // before the next tick and mess up our logic.
  4455. if (this._animation.isAnimating()) {
  4456. this._animation._animation.onfinish = null;
  4457. }
  4458. // Kick off animation to be started in the next tick.
  4459. this._isActive = true;
  4460. this._animOptions.easing = animEasing;
  4461. this._animOptions.duration = animDuration;
  4462. this._isInterrupted = isPositioning;
  4463. addLayoutTick(item._id, this._setupAnimation, this._startAnimation);
  4464. };
  4465. /**
  4466. * Stop item's position animation if it is currently animating.
  4467. *
  4468. * @public
  4469. * @param {Boolean} processCallbackQueue
  4470. * @param {Number} [left]
  4471. * @param {Number} [top]
  4472. */
  4473. ItemLayout.prototype.stop = function (processCallbackQueue, left, top) {
  4474. if (this._isDestroyed || !this._isActive) return;
  4475. var item = this._item;
  4476. // Cancel animation init.
  4477. cancelLayoutTick(item._id);
  4478. // Stop animation.
  4479. if (this._animation.isAnimating()) {
  4480. if (left === undefined || top === undefined) {
  4481. var translate = getTranslate(item._element);
  4482. left = translate.x;
  4483. top = translate.y;
  4484. }
  4485. item._setTranslate(left, top);
  4486. this._animation.stop();
  4487. }
  4488. // Remove positioning class.
  4489. removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
  4490. // Reset active state.
  4491. this._isActive = false;
  4492. // Process callback queue if needed.
  4493. if (processCallbackQueue) {
  4494. item._emitter.burst(this._queue, true, item);
  4495. }
  4496. };
  4497. /**
  4498. * Destroy the instance and stop current animation if it is running.
  4499. *
  4500. * @public
  4501. */
  4502. ItemLayout.prototype.destroy = function () {
  4503. if (this._isDestroyed) return;
  4504. var elementStyle = this._item._element.style;
  4505. this.stop(true, 0, 0);
  4506. this._item._emitter.clear(this._queue);
  4507. this._animation.destroy();
  4508. elementStyle[transformProp] = '';
  4509. elementStyle.left = '';
  4510. elementStyle.top = '';
  4511. this._item = null;
  4512. this._currentStyles = null;
  4513. this._targetStyles = null;
  4514. this._animOptions = null;
  4515. this._isDestroyed = true;
  4516. };
  4517. /**
  4518. * Private prototype methods
  4519. * *************************
  4520. */
  4521. /**
  4522. * Calculate and update item's current layout offset data.
  4523. *
  4524. * @private
  4525. */
  4526. ItemLayout.prototype._updateOffsets = function () {
  4527. if (this._isDestroyed) return;
  4528. var item = this._item;
  4529. var migrate = item._migrate;
  4530. var release = item._dragRelease;
  4531. this._offsetLeft = release._isActive
  4532. ? release._containerDiffX
  4533. : migrate._isActive
  4534. ? migrate._containerDiffX
  4535. : 0;
  4536. this._offsetTop = release._isActive
  4537. ? release._containerDiffY
  4538. : migrate._isActive
  4539. ? migrate._containerDiffY
  4540. : 0;
  4541. this._nextLeft = this._item._left + this._offsetLeft;
  4542. this._nextTop = this._item._top + this._offsetTop;
  4543. };
  4544. /**
  4545. * Finish item layout procedure.
  4546. *
  4547. * @private
  4548. */
  4549. ItemLayout.prototype._finish = function () {
  4550. if (this._isDestroyed) return;
  4551. var item = this._item;
  4552. var migrate = item._migrate;
  4553. var release = item._dragRelease;
  4554. // Update internal translate values.
  4555. item._tX = this._nextLeft;
  4556. item._tY = this._nextTop;
  4557. // Mark the item as inactive and remove positioning classes.
  4558. if (this._isActive) {
  4559. this._isActive = false;
  4560. removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
  4561. }
  4562. // Finish up release and migration.
  4563. if (release._isActive) release.stop();
  4564. if (migrate._isActive) migrate.stop();
  4565. // Process the callback queue.
  4566. item._emitter.burst(this._queue, false, item);
  4567. };
  4568. /**
  4569. * Prepare item for layout animation.
  4570. *
  4571. * @private
  4572. */
  4573. ItemLayout.prototype._setupAnimation = function () {
  4574. var item = this._item;
  4575. if (item._tX === undefined || item._tY === undefined) {
  4576. var translate = getTranslate(item._element);
  4577. item._tX = translate.x;
  4578. item._tY = translate.y;
  4579. }
  4580. };
  4581. /**
  4582. * Start layout animation.
  4583. *
  4584. * @private
  4585. */
  4586. ItemLayout.prototype._startAnimation = function () {
  4587. var item = this._item;
  4588. var settings = item.getGrid()._settings;
  4589. var isInstant = this._animOptions.duration <= 0;
  4590. // Let's update the offset data and target styles.
  4591. this._updateOffsets();
  4592. var xDiff = Math.abs(item._left - (item._tX - this._offsetLeft));
  4593. var yDiff = Math.abs(item._top - (item._tY - this._offsetTop));
  4594. // If there is no need for animation or if the item is already in correct
  4595. // position (or near it) let's finish the process early.
  4596. if (isInstant || (xDiff < MIN_ANIMATION_DISTANCE && yDiff < MIN_ANIMATION_DISTANCE)) {
  4597. if (xDiff || yDiff || this._isInterrupted) {
  4598. item._setTranslate(this._nextLeft, this._nextTop);
  4599. }
  4600. this._animation.stop();
  4601. this._finish();
  4602. return;
  4603. }
  4604. // Set item's positioning class if needed.
  4605. if (!this._isInterrupted) {
  4606. addClass(item._element, settings.itemPositioningClass);
  4607. }
  4608. // Get current/next styles for animation.
  4609. this._currentStyles[transformProp] = getTranslateString(item._tX, item._tY);
  4610. this._targetStyles[transformProp] = getTranslateString(this._nextLeft, this._nextTop);
  4611. // Set internal translation values to undefined for the duration of the
  4612. // animation since they will be changing on each animation frame for the
  4613. // duration of the animation and tracking them would mean reading the DOM on
  4614. // each frame, which is pretty darn expensive.
  4615. item._tX = item._tY = undefined;
  4616. // Start animation.
  4617. this._animation.start(this._currentStyles, this._targetStyles, this._animOptions);
  4618. };
  4619. /**
  4620. * The migrate process handler constructor.
  4621. *
  4622. * @class
  4623. * @param {Item} item
  4624. */
  4625. function ItemMigrate(item) {
  4626. // Private props.
  4627. this._item = item;
  4628. this._isActive = false;
  4629. this._isDestroyed = false;
  4630. this._container = false;
  4631. this._containerDiffX = 0;
  4632. this._containerDiffY = 0;
  4633. }
  4634. /**
  4635. * Public prototype methods
  4636. * ************************
  4637. */
  4638. /**
  4639. * Start the migrate process of an item.
  4640. *
  4641. * @public
  4642. * @param {Grid} targetGrid
  4643. * @param {(HTMLElement|Number|Item)} position
  4644. * @param {HTMLElement} [container]
  4645. */
  4646. ItemMigrate.prototype.start = function (targetGrid, position, container) {
  4647. if (this._isDestroyed) return;
  4648. var item = this._item;
  4649. var element = item._element;
  4650. var isActive = item.isActive();
  4651. var isVisible = item.isVisible();
  4652. var grid = item.getGrid();
  4653. var settings = grid._settings;
  4654. var targetSettings = targetGrid._settings;
  4655. var targetElement = targetGrid._element;
  4656. var targetItems = targetGrid._items;
  4657. var currentIndex = grid._items.indexOf(item);
  4658. var targetContainer = container || document.body;
  4659. var targetIndex;
  4660. var targetItem;
  4661. var currentContainer;
  4662. var offsetDiff;
  4663. var containerDiff;
  4664. var translate;
  4665. var translateX;
  4666. var translateY;
  4667. var currentVisClass;
  4668. var nextVisClass;
  4669. // Get target index.
  4670. if (typeof position === 'number') {
  4671. targetIndex = normalizeArrayIndex(targetItems, position, 1);
  4672. } else {
  4673. targetItem = targetGrid.getItem(position);
  4674. if (!targetItem) return;
  4675. targetIndex = targetItems.indexOf(targetItem);
  4676. }
  4677. // Get current translateX and translateY values if needed.
  4678. if (item.isPositioning() || this._isActive || item.isReleasing()) {
  4679. translate = getTranslate(element);
  4680. translateX = translate.x;
  4681. translateY = translate.y;
  4682. }
  4683. // Abort current positioning.
  4684. if (item.isPositioning()) {
  4685. item._layout.stop(true, translateX, translateY);
  4686. }
  4687. // Abort current migration.
  4688. if (this._isActive) {
  4689. translateX -= this._containerDiffX;
  4690. translateY -= this._containerDiffY;
  4691. this.stop(true, translateX, translateY);
  4692. }
  4693. // Abort current release.
  4694. if (item.isReleasing()) {
  4695. translateX -= item._dragRelease._containerDiffX;
  4696. translateY -= item._dragRelease._containerDiffY;
  4697. item._dragRelease.stop(true, translateX, translateY);
  4698. }
  4699. // Stop current visibility animation.
  4700. item._visibility.stop(true);
  4701. // Destroy current drag.
  4702. if (item._drag) item._drag.destroy();
  4703. // Emit beforeSend event.
  4704. if (grid._hasListeners(EVENT_BEFORE_SEND)) {
  4705. grid._emit(EVENT_BEFORE_SEND, {
  4706. item: item,
  4707. fromGrid: grid,
  4708. fromIndex: currentIndex,
  4709. toGrid: targetGrid,
  4710. toIndex: targetIndex,
  4711. });
  4712. }
  4713. // Emit beforeReceive event.
  4714. if (targetGrid._hasListeners(EVENT_BEFORE_RECEIVE)) {
  4715. targetGrid._emit(EVENT_BEFORE_RECEIVE, {
  4716. item: item,
  4717. fromGrid: grid,
  4718. fromIndex: currentIndex,
  4719. toGrid: targetGrid,
  4720. toIndex: targetIndex,
  4721. });
  4722. }
  4723. // Update item class.
  4724. if (settings.itemClass !== targetSettings.itemClass) {
  4725. removeClass(element, settings.itemClass);
  4726. addClass(element, targetSettings.itemClass);
  4727. }
  4728. // Update visibility class.
  4729. currentVisClass = isVisible ? settings.itemVisibleClass : settings.itemHiddenClass;
  4730. nextVisClass = isVisible ? targetSettings.itemVisibleClass : targetSettings.itemHiddenClass;
  4731. if (currentVisClass !== nextVisClass) {
  4732. removeClass(element, currentVisClass);
  4733. addClass(element, nextVisClass);
  4734. }
  4735. // Move item instance from current grid to target grid.
  4736. grid._items.splice(currentIndex, 1);
  4737. arrayInsert(targetItems, item, targetIndex);
  4738. // Update item's grid id reference.
  4739. item._gridId = targetGrid._id;
  4740. // If item is active we need to move the item inside the target container for
  4741. // the duration of the (potential) animation if it's different than the
  4742. // current container.
  4743. if (isActive) {
  4744. currentContainer = element.parentNode;
  4745. if (targetContainer !== currentContainer) {
  4746. targetContainer.appendChild(element);
  4747. offsetDiff = getOffsetDiff(targetContainer, currentContainer, true);
  4748. if (!translate) {
  4749. translate = getTranslate(element);
  4750. translateX = translate.x;
  4751. translateY = translate.y;
  4752. }
  4753. item._setTranslate(translateX + offsetDiff.left, translateY + offsetDiff.top);
  4754. }
  4755. }
  4756. // If item is not active let's just append it to the target grid's element.
  4757. else {
  4758. targetElement.appendChild(element);
  4759. }
  4760. // Update child element's styles to reflect the current visibility state.
  4761. item._visibility.setStyles(
  4762. isVisible ? targetSettings.visibleStyles : targetSettings.hiddenStyles
  4763. );
  4764. // Get offset diff for the migration data, if the item is active.
  4765. if (isActive) {
  4766. containerDiff = getOffsetDiff(targetContainer, targetElement, true);
  4767. }
  4768. // Update item's cached dimensions.
  4769. item._refreshDimensions();
  4770. // Reset item's sort data.
  4771. item._sortData = null;
  4772. // Create new drag handler.
  4773. item._drag = targetSettings.dragEnabled ? new ItemDrag(item) : null;
  4774. // Setup migration data.
  4775. if (isActive) {
  4776. this._isActive = true;
  4777. this._container = targetContainer;
  4778. this._containerDiffX = containerDiff.left;
  4779. this._containerDiffY = containerDiff.top;
  4780. } else {
  4781. this._isActive = false;
  4782. this._container = null;
  4783. this._containerDiffX = 0;
  4784. this._containerDiffY = 0;
  4785. }
  4786. // Emit send event.
  4787. if (grid._hasListeners(EVENT_SEND)) {
  4788. grid._emit(EVENT_SEND, {
  4789. item: item,
  4790. fromGrid: grid,
  4791. fromIndex: currentIndex,
  4792. toGrid: targetGrid,
  4793. toIndex: targetIndex,
  4794. });
  4795. }
  4796. // Emit receive event.
  4797. if (targetGrid._hasListeners(EVENT_RECEIVE)) {
  4798. targetGrid._emit(EVENT_RECEIVE, {
  4799. item: item,
  4800. fromGrid: grid,
  4801. fromIndex: currentIndex,
  4802. toGrid: targetGrid,
  4803. toIndex: targetIndex,
  4804. });
  4805. }
  4806. };
  4807. /**
  4808. * End the migrate process of an item. This method can be used to abort an
  4809. * ongoing migrate process (animation) or finish the migrate process.
  4810. *
  4811. * @public
  4812. * @param {Boolean} [abort=false]
  4813. * - Should the migration be aborted?
  4814. * @param {Number} [left]
  4815. * - The element's current translateX value (optional).
  4816. * @param {Number} [top]
  4817. * - The element's current translateY value (optional).
  4818. */
  4819. ItemMigrate.prototype.stop = function (abort, left, top) {
  4820. if (this._isDestroyed || !this._isActive) return;
  4821. var item = this._item;
  4822. var element = item._element;
  4823. var grid = item.getGrid();
  4824. var gridElement = grid._element;
  4825. var translate;
  4826. if (this._container !== gridElement) {
  4827. if (left === undefined || top === undefined) {
  4828. if (abort) {
  4829. translate = getTranslate(element);
  4830. left = translate.x - this._containerDiffX;
  4831. top = translate.y - this._containerDiffY;
  4832. } else {
  4833. left = item._left;
  4834. top = item._top;
  4835. }
  4836. }
  4837. gridElement.appendChild(element);
  4838. item._setTranslate(left, top);
  4839. }
  4840. this._isActive = false;
  4841. this._container = null;
  4842. this._containerDiffX = 0;
  4843. this._containerDiffY = 0;
  4844. };
  4845. /**
  4846. * Destroy instance.
  4847. *
  4848. * @public
  4849. */
  4850. ItemMigrate.prototype.destroy = function () {
  4851. if (this._isDestroyed) return;
  4852. this.stop(true);
  4853. this._item = null;
  4854. this._isDestroyed = true;
  4855. };
  4856. /**
  4857. * Visibility manager for Item instance, handles visibility of an item.
  4858. *
  4859. * @class
  4860. * @param {Item} item
  4861. */
  4862. function ItemVisibility(item) {
  4863. var isActive = item._isActive;
  4864. var element = item._element;
  4865. var childElement = element.children[0];
  4866. var settings = item.getGrid()._settings;
  4867. if (!childElement) {
  4868. throw new Error('No valid child element found within item element.');
  4869. }
  4870. this._item = item;
  4871. this._isDestroyed = false;
  4872. this._isHidden = !isActive;
  4873. this._isHiding = false;
  4874. this._isShowing = false;
  4875. this._childElement = childElement;
  4876. this._currentStyleProps = [];
  4877. this._animation = new Animator(childElement);
  4878. this._queue = 'visibility-' + item._id;
  4879. this._finishShow = this._finishShow.bind(this);
  4880. this._finishHide = this._finishHide.bind(this);
  4881. element.style.display = isActive ? '' : 'none';
  4882. addClass(element, isActive ? settings.itemVisibleClass : settings.itemHiddenClass);
  4883. this.setStyles(isActive ? settings.visibleStyles : settings.hiddenStyles);
  4884. }
  4885. /**
  4886. * Public prototype methods
  4887. * ************************
  4888. */
  4889. /**
  4890. * Show item.
  4891. *
  4892. * @public
  4893. * @param {Boolean} instant
  4894. * @param {Function} [onFinish]
  4895. */
  4896. ItemVisibility.prototype.show = function (instant, onFinish) {
  4897. if (this._isDestroyed) return;
  4898. var item = this._item;
  4899. var element = item._element;
  4900. var callback = isFunction(onFinish) ? onFinish : null;
  4901. var grid = item.getGrid();
  4902. var settings = grid._settings;
  4903. // If item is visible call the callback and be done with it.
  4904. if (!this._isShowing && !this._isHidden) {
  4905. callback && callback(false, item);
  4906. return;
  4907. }
  4908. // If item is showing and does not need to be shown instantly, let's just
  4909. // push callback to the callback queue and be done with it.
  4910. if (this._isShowing && !instant) {
  4911. callback && item._emitter.on(this._queue, callback);
  4912. return;
  4913. }
  4914. // If the item is hiding or hidden process the current visibility callback
  4915. // queue with the interrupted flag active, update classes and set display
  4916. // to block if necessary.
  4917. if (!this._isShowing) {
  4918. item._emitter.burst(this._queue, true, item);
  4919. removeClass(element, settings.itemHiddenClass);
  4920. addClass(element, settings.itemVisibleClass);
  4921. if (!this._isHiding) element.style.display = '';
  4922. }
  4923. // Push callback to the callback queue.
  4924. callback && item._emitter.on(this._queue, callback);
  4925. // Update visibility states.
  4926. this._isShowing = true;
  4927. this._isHiding = this._isHidden = false;
  4928. // Finally let's start show animation.
  4929. this._startAnimation(true, instant, this._finishShow);
  4930. };
  4931. /**
  4932. * Hide item.
  4933. *
  4934. * @public
  4935. * @param {Boolean} instant
  4936. * @param {Function} [onFinish]
  4937. */
  4938. ItemVisibility.prototype.hide = function (instant, onFinish) {
  4939. if (this._isDestroyed) return;
  4940. var item = this._item;
  4941. var element = item._element;
  4942. var callback = isFunction(onFinish) ? onFinish : null;
  4943. var grid = item.getGrid();
  4944. var settings = grid._settings;
  4945. // If item is already hidden call the callback and be done with it.
  4946. if (!this._isHiding && this._isHidden) {
  4947. callback && callback(false, item);
  4948. return;
  4949. }
  4950. // If item is hiding and does not need to be hidden instantly, let's just
  4951. // push callback to the callback queue and be done with it.
  4952. if (this._isHiding && !instant) {
  4953. callback && item._emitter.on(this._queue, callback);
  4954. return;
  4955. }
  4956. // If the item is showing or visible process the current visibility callback
  4957. // queue with the interrupted flag active, update classes and set display
  4958. // to block if necessary.
  4959. if (!this._isHiding) {
  4960. item._emitter.burst(this._queue, true, item);
  4961. addClass(element, settings.itemHiddenClass);
  4962. removeClass(element, settings.itemVisibleClass);
  4963. }
  4964. // Push callback to the callback queue.
  4965. callback && item._emitter.on(this._queue, callback);
  4966. // Update visibility states.
  4967. this._isHidden = this._isHiding = true;
  4968. this._isShowing = false;
  4969. // Finally let's start hide animation.
  4970. this._startAnimation(false, instant, this._finishHide);
  4971. };
  4972. /**
  4973. * Stop current hiding/showing process.
  4974. *
  4975. * @public
  4976. * @param {Boolean} processCallbackQueue
  4977. */
  4978. ItemVisibility.prototype.stop = function (processCallbackQueue) {
  4979. if (this._isDestroyed) return;
  4980. if (!this._isHiding && !this._isShowing) return;
  4981. var item = this._item;
  4982. cancelVisibilityTick(item._id);
  4983. this._animation.stop();
  4984. if (processCallbackQueue) {
  4985. item._emitter.burst(this._queue, true, item);
  4986. }
  4987. };
  4988. /**
  4989. * Reset all existing visibility styles and apply new visibility styles to the
  4990. * visibility element. This method should be used to set styles when there is a
  4991. * chance that the current style properties differ from the new ones (basically
  4992. * on init and on migrations).
  4993. *
  4994. * @public
  4995. * @param {Object} styles
  4996. */
  4997. ItemVisibility.prototype.setStyles = function (styles) {
  4998. var childElement = this._childElement;
  4999. var currentStyleProps = this._currentStyleProps;
  5000. this._removeCurrentStyles();
  5001. for (var prop in styles) {
  5002. currentStyleProps.push(prop);
  5003. childElement.style[prop] = styles[prop];
  5004. }
  5005. };
  5006. /**
  5007. * Destroy the instance and stop current animation if it is running.
  5008. *
  5009. * @public
  5010. */
  5011. ItemVisibility.prototype.destroy = function () {
  5012. if (this._isDestroyed) return;
  5013. var item = this._item;
  5014. var element = item._element;
  5015. var grid = item.getGrid();
  5016. var settings = grid._settings;
  5017. this.stop(true);
  5018. item._emitter.clear(this._queue);
  5019. this._animation.destroy();
  5020. this._removeCurrentStyles();
  5021. removeClass(element, settings.itemVisibleClass);
  5022. removeClass(element, settings.itemHiddenClass);
  5023. element.style.display = '';
  5024. // Reset state.
  5025. this._isHiding = this._isShowing = false;
  5026. this._isDestroyed = this._isHidden = true;
  5027. };
  5028. /**
  5029. * Private prototype methods
  5030. * *************************
  5031. */
  5032. /**
  5033. * Start visibility animation.
  5034. *
  5035. * @private
  5036. * @param {Boolean} toVisible
  5037. * @param {Boolean} [instant]
  5038. * @param {Function} [onFinish]
  5039. */
  5040. ItemVisibility.prototype._startAnimation = function (toVisible, instant, onFinish) {
  5041. if (this._isDestroyed) return;
  5042. var item = this._item;
  5043. var animation = this._animation;
  5044. var childElement = this._childElement;
  5045. var settings = item.getGrid()._settings;
  5046. var targetStyles = toVisible ? settings.visibleStyles : settings.hiddenStyles;
  5047. var duration = toVisible ? settings.showDuration : settings.hideDuration;
  5048. var easing = toVisible ? settings.showEasing : settings.hideEasing;
  5049. var isInstant = instant || duration <= 0;
  5050. var currentStyles;
  5051. // No target styles? Let's quit early.
  5052. if (!targetStyles) {
  5053. onFinish && onFinish();
  5054. return;
  5055. }
  5056. // Cancel queued visibility tick.
  5057. cancelVisibilityTick(item._id);
  5058. // If we need to apply the styles instantly without animation.
  5059. if (isInstant) {
  5060. setStyles(childElement, targetStyles);
  5061. animation.stop();
  5062. onFinish && onFinish();
  5063. return;
  5064. }
  5065. // Let's make sure an ongoing animation's callback is cancelled before going
  5066. // further. Without this there's a chance that the animation will finish
  5067. // before the next tick and mess up our logic.
  5068. if (animation.isAnimating()) {
  5069. animation._animation.onfinish = null;
  5070. }
  5071. // Start the animation in the next tick (to avoid layout thrashing).
  5072. addVisibilityTick(
  5073. item._id,
  5074. function () {
  5075. currentStyles = getCurrentStyles(childElement, targetStyles);
  5076. },
  5077. function () {
  5078. animation.start(currentStyles, targetStyles, {
  5079. duration: duration,
  5080. easing: easing,
  5081. onFinish: onFinish,
  5082. });
  5083. }
  5084. );
  5085. };
  5086. /**
  5087. * Finish show procedure.
  5088. *
  5089. * @private
  5090. */
  5091. ItemVisibility.prototype._finishShow = function () {
  5092. if (this._isHidden) return;
  5093. this._isShowing = false;
  5094. this._item._emitter.burst(this._queue, false, this._item);
  5095. };
  5096. /**
  5097. * Finish hide procedure.
  5098. *
  5099. * @private
  5100. */
  5101. ItemVisibility.prototype._finishHide = function () {
  5102. if (!this._isHidden) return;
  5103. var item = this._item;
  5104. this._isHiding = false;
  5105. item._layout.stop(true, 0, 0);
  5106. item._element.style.display = 'none';
  5107. item._emitter.burst(this._queue, false, item);
  5108. };
  5109. /**
  5110. * Remove currently applied visibility related inline style properties.
  5111. *
  5112. * @private
  5113. */
  5114. ItemVisibility.prototype._removeCurrentStyles = function () {
  5115. var childElement = this._childElement;
  5116. var currentStyleProps = this._currentStyleProps;
  5117. for (var i = 0; i < currentStyleProps.length; i++) {
  5118. childElement.style[currentStyleProps[i]] = '';
  5119. }
  5120. currentStyleProps.length = 0;
  5121. };
  5122. var id = 0;
  5123. /**
  5124. * Returns a unique numeric id (increments a base value on every call).
  5125. * @returns {Number}
  5126. */
  5127. function createUid() {
  5128. return ++id;
  5129. }
  5130. /**
  5131. * Creates a new Item instance for a Grid instance.
  5132. *
  5133. * @class
  5134. * @param {Grid} grid
  5135. * @param {HTMLElement} element
  5136. * @param {Boolean} [isActive]
  5137. */
  5138. function Item(grid, element, isActive) {
  5139. var settings = grid._settings;
  5140. // Store item/element pair to a map (for faster item querying by element).
  5141. if (ITEM_ELEMENT_MAP) {
  5142. if (ITEM_ELEMENT_MAP.has(element)) {
  5143. throw new Error('You can only create one Muuri Item per element!');
  5144. } else {
  5145. ITEM_ELEMENT_MAP.set(element, this);
  5146. }
  5147. }
  5148. this._id = createUid();
  5149. this._gridId = grid._id;
  5150. this._element = element;
  5151. this._isDestroyed = false;
  5152. this._left = 0;
  5153. this._top = 0;
  5154. this._width = 0;
  5155. this._height = 0;
  5156. this._marginLeft = 0;
  5157. this._marginRight = 0;
  5158. this._marginTop = 0;
  5159. this._marginBottom = 0;
  5160. this._tX = undefined;
  5161. this._tY = undefined;
  5162. this._sortData = null;
  5163. this._emitter = new Emitter();
  5164. // If the provided item element is not a direct child of the grid container
  5165. // element, append it to the grid container. Note, we are indeed reading the
  5166. // DOM here but it's a property that does not cause reflowing.
  5167. if (element.parentNode !== grid._element) {
  5168. grid._element.appendChild(element);
  5169. }
  5170. // Set item class.
  5171. addClass(element, settings.itemClass);
  5172. // If isActive is not defined, let's try to auto-detect it. Note, we are
  5173. // indeed reading the DOM here but it's a property that does not cause
  5174. // reflowing.
  5175. if (typeof isActive !== 'boolean') {
  5176. isActive = getStyle(element, 'display') !== 'none';
  5177. }
  5178. // Set up active state (defines if the item is considered part of the layout
  5179. // or not).
  5180. this._isActive = isActive;
  5181. // Setup visibility handler.
  5182. this._visibility = new ItemVisibility(this);
  5183. // Set up layout handler.
  5184. this._layout = new ItemLayout(this);
  5185. // Set up migration handler data.
  5186. this._migrate = new ItemMigrate(this);
  5187. // Set up drag handler.
  5188. this._drag = settings.dragEnabled ? new ItemDrag(this) : null;
  5189. // Set up release handler. Note that although this is fully linked to dragging
  5190. // this still needs to be always instantiated to handle migration scenarios
  5191. // correctly.
  5192. this._dragRelease = new ItemDragRelease(this);
  5193. // Set up drag placeholder handler. Note that although this is fully linked to
  5194. // dragging this still needs to be always instantiated to handle migration
  5195. // scenarios correctly.
  5196. this._dragPlaceholder = new ItemDragPlaceholder(this);
  5197. // Note! You must call the following methods before you start using the
  5198. // instance. They are deliberately not called in the end as it would cause
  5199. // potentially a massive amount of reflows if multiple items were instantiated
  5200. // in a loop.
  5201. // this._refreshDimensions();
  5202. // this._refreshSortData();
  5203. }
  5204. /**
  5205. * Public prototype methods
  5206. * ************************
  5207. */
  5208. /**
  5209. * Get the instance grid reference.
  5210. *
  5211. * @public
  5212. * @returns {Grid}
  5213. */
  5214. Item.prototype.getGrid = function () {
  5215. return GRID_INSTANCES[this._gridId];
  5216. };
  5217. /**
  5218. * Get the instance element.
  5219. *
  5220. * @public
  5221. * @returns {HTMLElement}
  5222. */
  5223. Item.prototype.getElement = function () {
  5224. return this._element;
  5225. };
  5226. /**
  5227. * Get instance element's cached width.
  5228. *
  5229. * @public
  5230. * @returns {Number}
  5231. */
  5232. Item.prototype.getWidth = function () {
  5233. return this._width;
  5234. };
  5235. /**
  5236. * Get instance element's cached height.
  5237. *
  5238. * @public
  5239. * @returns {Number}
  5240. */
  5241. Item.prototype.getHeight = function () {
  5242. return this._height;
  5243. };
  5244. /**
  5245. * Get instance element's cached margins.
  5246. *
  5247. * @public
  5248. * @returns {Object}
  5249. * - The returned object contains left, right, top and bottom properties
  5250. * which indicate the item element's cached margins.
  5251. */
  5252. Item.prototype.getMargin = function () {
  5253. return {
  5254. left: this._marginLeft,
  5255. right: this._marginRight,
  5256. top: this._marginTop,
  5257. bottom: this._marginBottom,
  5258. };
  5259. };
  5260. /**
  5261. * Get instance element's cached position.
  5262. *
  5263. * @public
  5264. * @returns {Object}
  5265. * - The returned object contains left and top properties which indicate the
  5266. * item element's cached position in the grid.
  5267. */
  5268. Item.prototype.getPosition = function () {
  5269. return {
  5270. left: this._left,
  5271. top: this._top,
  5272. };
  5273. };
  5274. /**
  5275. * Is the item active?
  5276. *
  5277. * @public
  5278. * @returns {Boolean}
  5279. */
  5280. Item.prototype.isActive = function () {
  5281. return this._isActive;
  5282. };
  5283. /**
  5284. * Is the item visible?
  5285. *
  5286. * @public
  5287. * @returns {Boolean}
  5288. */
  5289. Item.prototype.isVisible = function () {
  5290. return !!this._visibility && !this._visibility._isHidden;
  5291. };
  5292. /**
  5293. * Is the item being animated to visible?
  5294. *
  5295. * @public
  5296. * @returns {Boolean}
  5297. */
  5298. Item.prototype.isShowing = function () {
  5299. return !!(this._visibility && this._visibility._isShowing);
  5300. };
  5301. /**
  5302. * Is the item being animated to hidden?
  5303. *
  5304. * @public
  5305. * @returns {Boolean}
  5306. */
  5307. Item.prototype.isHiding = function () {
  5308. return !!(this._visibility && this._visibility._isHiding);
  5309. };
  5310. /**
  5311. * Is the item positioning?
  5312. *
  5313. * @public
  5314. * @returns {Boolean}
  5315. */
  5316. Item.prototype.isPositioning = function () {
  5317. return !!(this._layout && this._layout._isActive);
  5318. };
  5319. /**
  5320. * Is the item being dragged (or queued for dragging)?
  5321. *
  5322. * @public
  5323. * @returns {Boolean}
  5324. */
  5325. Item.prototype.isDragging = function () {
  5326. return !!(this._drag && this._drag._isActive);
  5327. };
  5328. /**
  5329. * Is the item being released?
  5330. *
  5331. * @public
  5332. * @returns {Boolean}
  5333. */
  5334. Item.prototype.isReleasing = function () {
  5335. return !!(this._dragRelease && this._dragRelease._isActive);
  5336. };
  5337. /**
  5338. * Is the item destroyed?
  5339. *
  5340. * @public
  5341. * @returns {Boolean}
  5342. */
  5343. Item.prototype.isDestroyed = function () {
  5344. return this._isDestroyed;
  5345. };
  5346. /**
  5347. * Private prototype methods
  5348. * *************************
  5349. */
  5350. /**
  5351. * Recalculate item's dimensions.
  5352. *
  5353. * @private
  5354. * @param {Boolean} [force=false]
  5355. */
  5356. Item.prototype._refreshDimensions = function (force) {
  5357. if (this._isDestroyed) return;
  5358. if (force !== true && this._visibility._isHidden) return;
  5359. var element = this._element;
  5360. var dragPlaceholder = this._dragPlaceholder;
  5361. var rect = element.getBoundingClientRect();
  5362. // Calculate width and height.
  5363. this._width = rect.width;
  5364. this._height = rect.height;
  5365. // Calculate margins (ignore negative margins).
  5366. this._marginLeft = Math.max(0, getStyleAsFloat(element, 'margin-left'));
  5367. this._marginRight = Math.max(0, getStyleAsFloat(element, 'margin-right'));
  5368. this._marginTop = Math.max(0, getStyleAsFloat(element, 'margin-top'));
  5369. this._marginBottom = Math.max(0, getStyleAsFloat(element, 'margin-bottom'));
  5370. // Keep drag placeholder's dimensions synced with the item's.
  5371. if (dragPlaceholder) dragPlaceholder.updateDimensions();
  5372. };
  5373. /**
  5374. * Fetch and store item's sort data.
  5375. *
  5376. * @private
  5377. */
  5378. Item.prototype._refreshSortData = function () {
  5379. if (this._isDestroyed) return;
  5380. var data = (this._sortData = {});
  5381. var getters = this.getGrid()._settings.sortData;
  5382. var prop;
  5383. for (prop in getters) {
  5384. data[prop] = getters[prop](this, this._element);
  5385. }
  5386. };
  5387. /**
  5388. * Add item to layout.
  5389. *
  5390. * @private
  5391. */
  5392. Item.prototype._addToLayout = function (left, top) {
  5393. if (this._isActive === true) return;
  5394. this._isActive = true;
  5395. this._left = left || 0;
  5396. this._top = top || 0;
  5397. };
  5398. /**
  5399. * Remove item from layout.
  5400. *
  5401. * @private
  5402. */
  5403. Item.prototype._removeFromLayout = function () {
  5404. if (this._isActive === false) return;
  5405. this._isActive = false;
  5406. this._left = 0;
  5407. this._top = 0;
  5408. };
  5409. /**
  5410. * Check if the layout procedure can be skipped for the item.
  5411. *
  5412. * @private
  5413. * @param {Number} left
  5414. * @param {Number} top
  5415. * @returns {Boolean}
  5416. */
  5417. Item.prototype._canSkipLayout = function (left, top) {
  5418. return (
  5419. this._left === left &&
  5420. this._top === top &&
  5421. !this._migrate._isActive &&
  5422. !this._layout._skipNextAnimation &&
  5423. !this._dragRelease.isJustReleased()
  5424. );
  5425. };
  5426. /**
  5427. * Set the provided left and top arguments as the item element's translate
  5428. * values in the DOM. This method keeps track of the currently applied
  5429. * translate values and skips the update operation if the provided values are
  5430. * identical to the currently applied values. Returns `false` if there was no
  5431. * need for update and `true` if the translate value was updated.
  5432. *
  5433. * @private
  5434. * @param {Number} left
  5435. * @param {Number} top
  5436. * @returns {Boolean}
  5437. */
  5438. Item.prototype._setTranslate = function (left, top) {
  5439. if (this._tX === left && this._tY === top) return false;
  5440. this._tX = left;
  5441. this._tY = top;
  5442. this._element.style[transformProp] = getTranslateString(left, top);
  5443. return true;
  5444. };
  5445. /**
  5446. * Destroy item instance.
  5447. *
  5448. * @private
  5449. * @param {Boolean} [removeElement=false]
  5450. */
  5451. Item.prototype._destroy = function (removeElement) {
  5452. if (this._isDestroyed) return;
  5453. var element = this._element;
  5454. var grid = this.getGrid();
  5455. var settings = grid._settings;
  5456. // Destroy handlers.
  5457. this._dragPlaceholder.destroy();
  5458. this._dragRelease.destroy();
  5459. this._migrate.destroy();
  5460. this._layout.destroy();
  5461. this._visibility.destroy();
  5462. if (this._drag) this._drag.destroy();
  5463. // Destroy emitter.
  5464. this._emitter.destroy();
  5465. // Remove item class.
  5466. removeClass(element, settings.itemClass);
  5467. // Remove element from DOM.
  5468. if (removeElement) element.parentNode.removeChild(element);
  5469. // Remove item/element pair from map.
  5470. if (ITEM_ELEMENT_MAP) ITEM_ELEMENT_MAP.delete(element);
  5471. // Reset state.
  5472. this._isActive = false;
  5473. this._isDestroyed = true;
  5474. };
  5475. function createPackerProcessor(isWorker) {
  5476. var FILL_GAPS = 1;
  5477. var HORIZONTAL = 2;
  5478. var ALIGN_RIGHT = 4;
  5479. var ALIGN_BOTTOM = 8;
  5480. var ROUNDING = 16;
  5481. var EPS = 0.001;
  5482. var MIN_SLOT_SIZE = 0.5;
  5483. // Rounds number first to three decimal precision and then floors the result
  5484. // to two decimal precision.
  5485. // Math.floor(Math.round(number * 1000) / 10) / 100
  5486. function roundNumber(number) {
  5487. return ((((number * 1000 + 0.5) << 0) / 10) << 0) / 100;
  5488. }
  5489. /**
  5490. * @class
  5491. */
  5492. function PackerProcessor() {
  5493. this.currentRects = [];
  5494. this.nextRects = [];
  5495. this.rectTarget = {};
  5496. this.rectStore = [];
  5497. this.slotSizes = [];
  5498. this.rectId = 0;
  5499. this.slotIndex = -1;
  5500. this.slotData = { left: 0, top: 0, width: 0, height: 0 };
  5501. this.sortRectsLeftTop = this.sortRectsLeftTop.bind(this);
  5502. this.sortRectsTopLeft = this.sortRectsTopLeft.bind(this);
  5503. }
  5504. /**
  5505. * Takes a layout object as an argument and computes positions (slots) for the
  5506. * layout items. Also computes the final width and height of the layout. The
  5507. * provided layout object's slots array is mutated as well as the width and
  5508. * height properties.
  5509. *
  5510. * @param {Object} layout
  5511. * @param {Number} layout.width
  5512. * - The start (current) width of the layout in pixels.
  5513. * @param {Number} layout.height
  5514. * - The start (current) height of the layout in pixels.
  5515. * @param {(Item[]|Number[])} layout.items
  5516. * - List of Muuri.Item instances or a list of item dimensions
  5517. * (e.g [ item1Width, item1Height, item2Width, item2Height, ... ]).
  5518. * @param {(Array|Float32Array)} layout.slots
  5519. * - An Array/Float32Array instance which's length should equal to
  5520. * the amount of items times two. The position (width and height) of each
  5521. * item will be written into this array.
  5522. * @param {Number} settings
  5523. * - The layout's settings as bitmasks.
  5524. * @returns {Object}
  5525. */
  5526. PackerProcessor.prototype.computeLayout = function (layout, settings) {
  5527. var items = layout.items;
  5528. var slots = layout.slots;
  5529. var fillGaps = !!(settings & FILL_GAPS);
  5530. var horizontal = !!(settings & HORIZONTAL);
  5531. var alignRight = !!(settings & ALIGN_RIGHT);
  5532. var alignBottom = !!(settings & ALIGN_BOTTOM);
  5533. var rounding = !!(settings & ROUNDING);
  5534. var isPreProcessed = typeof items[0] === 'number';
  5535. var i, bump, item, slotWidth, slotHeight, slot;
  5536. // No need to go further if items do not exist.
  5537. if (!items.length) return layout;
  5538. // Compute slots for the items.
  5539. bump = isPreProcessed ? 2 : 1;
  5540. for (i = 0; i < items.length; i += bump) {
  5541. // If items are pre-processed it means that items array contains only
  5542. // the raw dimensions of the items. Otherwise we assume it is an array
  5543. // of normal Muuri items.
  5544. if (isPreProcessed) {
  5545. slotWidth = items[i];
  5546. slotHeight = items[i + 1];
  5547. } else {
  5548. item = items[i];
  5549. slotWidth = item._width + item._marginLeft + item._marginRight;
  5550. slotHeight = item._height + item._marginTop + item._marginBottom;
  5551. }
  5552. // If rounding is enabled let's round the item's width and height to
  5553. // make the layout algorithm a bit more stable. This has a performance
  5554. // cost so don't use this if not necessary.
  5555. if (rounding) {
  5556. slotWidth = roundNumber(slotWidth);
  5557. slotHeight = roundNumber(slotHeight);
  5558. }
  5559. // Get slot data.
  5560. slot = this.computeNextSlot(layout, slotWidth, slotHeight, fillGaps, horizontal);
  5561. // Update layout width/height.
  5562. if (horizontal) {
  5563. if (slot.left + slot.width > layout.width) {
  5564. layout.width = slot.left + slot.width;
  5565. }
  5566. } else {
  5567. if (slot.top + slot.height > layout.height) {
  5568. layout.height = slot.top + slot.height;
  5569. }
  5570. }
  5571. // Add item slot data to layout slots.
  5572. slots[++this.slotIndex] = slot.left;
  5573. slots[++this.slotIndex] = slot.top;
  5574. // Store the size too (for later usage) if needed.
  5575. if (alignRight || alignBottom) {
  5576. this.slotSizes.push(slot.width, slot.height);
  5577. }
  5578. }
  5579. // If the alignment is set to right we need to adjust the results.
  5580. if (alignRight) {
  5581. for (i = 0; i < slots.length; i += 2) {
  5582. slots[i] = layout.width - (slots[i] + this.slotSizes[i]);
  5583. }
  5584. }
  5585. // If the alignment is set to bottom we need to adjust the results.
  5586. if (alignBottom) {
  5587. for (i = 1; i < slots.length; i += 2) {
  5588. slots[i] = layout.height - (slots[i] + this.slotSizes[i]);
  5589. }
  5590. }
  5591. // Reset stuff.
  5592. this.slotSizes.length = 0;
  5593. this.currentRects.length = 0;
  5594. this.nextRects.length = 0;
  5595. this.rectId = 0;
  5596. this.slotIndex = -1;
  5597. return layout;
  5598. };
  5599. /**
  5600. * Calculate next slot in the layout. Returns a slot object with position and
  5601. * dimensions data. The returned object is reused between calls.
  5602. *
  5603. * @param {Object} layout
  5604. * @param {Number} slotWidth
  5605. * @param {Number} slotHeight
  5606. * @param {Boolean} fillGaps
  5607. * @param {Boolean} horizontal
  5608. * @returns {Object}
  5609. */
  5610. PackerProcessor.prototype.computeNextSlot = function (
  5611. layout,
  5612. slotWidth,
  5613. slotHeight,
  5614. fillGaps,
  5615. horizontal
  5616. ) {
  5617. var slot = this.slotData;
  5618. var currentRects = this.currentRects;
  5619. var nextRects = this.nextRects;
  5620. var ignoreCurrentRects = false;
  5621. var rect;
  5622. var rectId;
  5623. var shards;
  5624. var i;
  5625. var j;
  5626. // Reset new slots.
  5627. nextRects.length = 0;
  5628. // Set item slot initial data.
  5629. slot.left = null;
  5630. slot.top = null;
  5631. slot.width = slotWidth;
  5632. slot.height = slotHeight;
  5633. // Try to find position for the slot from the existing free spaces in the
  5634. // layout.
  5635. for (i = 0; i < currentRects.length; i++) {
  5636. rectId = currentRects[i];
  5637. if (!rectId) continue;
  5638. rect = this.getRect(rectId);
  5639. if (slot.width <= rect.width + EPS && slot.height <= rect.height + EPS) {
  5640. slot.left = rect.left;
  5641. slot.top = rect.top;
  5642. break;
  5643. }
  5644. }
  5645. // If no position was found for the slot let's position the slot to
  5646. // the bottom left (in vertical mode) or top right (in horizontal mode) of
  5647. // the layout.
  5648. if (slot.left === null) {
  5649. if (horizontal) {
  5650. slot.left = layout.width;
  5651. slot.top = 0;
  5652. } else {
  5653. slot.left = 0;
  5654. slot.top = layout.height;
  5655. }
  5656. // If gaps don't need filling let's throw away all the current free spaces
  5657. // (currentRects).
  5658. if (!fillGaps) {
  5659. ignoreCurrentRects = true;
  5660. }
  5661. }
  5662. // In vertical mode, if the slot's bottom overlaps the layout's bottom.
  5663. if (!horizontal && slot.top + slot.height > layout.height + EPS) {
  5664. // If slot is not aligned to the left edge, create a new free space to the
  5665. // left of the slot.
  5666. if (slot.left > MIN_SLOT_SIZE) {
  5667. nextRects.push(this.addRect(0, layout.height, slot.left, Infinity));
  5668. }
  5669. // If slot is not aligned to the right edge, create a new free space to
  5670. // the right of the slot.
  5671. if (slot.left + slot.width < layout.width - MIN_SLOT_SIZE) {
  5672. nextRects.push(
  5673. this.addRect(
  5674. slot.left + slot.width,
  5675. layout.height,
  5676. layout.width - slot.left - slot.width,
  5677. Infinity
  5678. )
  5679. );
  5680. }
  5681. // Update layout height.
  5682. layout.height = slot.top + slot.height;
  5683. }
  5684. // In horizontal mode, if the slot's right overlaps the layout's right edge.
  5685. if (horizontal && slot.left + slot.width > layout.width + EPS) {
  5686. // If slot is not aligned to the top, create a new free space above the
  5687. // slot.
  5688. if (slot.top > MIN_SLOT_SIZE) {
  5689. nextRects.push(this.addRect(layout.width, 0, Infinity, slot.top));
  5690. }
  5691. // If slot is not aligned to the bottom, create a new free space below
  5692. // the slot.
  5693. if (slot.top + slot.height < layout.height - MIN_SLOT_SIZE) {
  5694. nextRects.push(
  5695. this.addRect(
  5696. layout.width,
  5697. slot.top + slot.height,
  5698. Infinity,
  5699. layout.height - slot.top - slot.height
  5700. )
  5701. );
  5702. }
  5703. // Update layout width.
  5704. layout.width = slot.left + slot.width;
  5705. }
  5706. // Clean up the current free spaces making sure none of them overlap with
  5707. // the slot. Split all overlapping free spaces into smaller shards that do
  5708. // not overlap with the slot.
  5709. if (!ignoreCurrentRects) {
  5710. if (fillGaps) i = 0;
  5711. for (; i < currentRects.length; i++) {
  5712. rectId = currentRects[i];
  5713. if (!rectId) continue;
  5714. rect = this.getRect(rectId);
  5715. shards = this.splitRect(rect, slot);
  5716. for (j = 0; j < shards.length; j++) {
  5717. rectId = shards[j];
  5718. rect = this.getRect(rectId);
  5719. // Make sure that the free space is within the boundaries of the
  5720. // layout. This routine is critical to the algorithm as it makes sure
  5721. // that there are no leftover spaces with infinite height/width.
  5722. // It's also essential that we don't compare values absolutely to each
  5723. // other but leave a little headroom (EPSILON) to get rid of false
  5724. // positives.
  5725. if (
  5726. horizontal ? rect.left + EPS < layout.width - EPS : rect.top + EPS < layout.height - EPS
  5727. ) {
  5728. nextRects.push(rectId);
  5729. }
  5730. }
  5731. }
  5732. }
  5733. // Sanitize and sort all the new free spaces that will be used in the next
  5734. // iteration. This procedure is critical to make the bin-packing algorithm
  5735. // work. The free spaces have to be in correct order in the beginning of the
  5736. // next iteration.
  5737. if (nextRects.length > 1) {
  5738. this.purgeRects(nextRects).sort(horizontal ? this.sortRectsLeftTop : this.sortRectsTopLeft);
  5739. }
  5740. // Finally we need to make sure that `this.currentRects` points to
  5741. // `nextRects` array as that is used in the next iteration's beginning when
  5742. // we try to find a space for the next slot.
  5743. this.currentRects = nextRects;
  5744. this.nextRects = currentRects;
  5745. return slot;
  5746. };
  5747. /**
  5748. * Add a new rectangle to the rectangle store. Returns the id of the new
  5749. * rectangle.
  5750. *
  5751. * @param {Number} left
  5752. * @param {Number} top
  5753. * @param {Number} width
  5754. * @param {Number} height
  5755. * @returns {Number}
  5756. */
  5757. PackerProcessor.prototype.addRect = function (left, top, width, height) {
  5758. var rectId = ++this.rectId;
  5759. this.rectStore[rectId] = left || 0;
  5760. this.rectStore[++this.rectId] = top || 0;
  5761. this.rectStore[++this.rectId] = width || 0;
  5762. this.rectStore[++this.rectId] = height || 0;
  5763. return rectId;
  5764. };
  5765. /**
  5766. * Get rectangle data from the rectangle store by id. Optionally you can
  5767. * provide a target object where the rectangle data will be written in. By
  5768. * default an internal object is reused as a target object.
  5769. *
  5770. * @param {Number} id
  5771. * @param {Object} [target]
  5772. * @returns {Object}
  5773. */
  5774. PackerProcessor.prototype.getRect = function (id, target) {
  5775. if (!target) target = this.rectTarget;
  5776. target.left = this.rectStore[id] || 0;
  5777. target.top = this.rectStore[++id] || 0;
  5778. target.width = this.rectStore[++id] || 0;
  5779. target.height = this.rectStore[++id] || 0;
  5780. return target;
  5781. };
  5782. /**
  5783. * Punch a hole into a rectangle and return the shards (1-4).
  5784. *
  5785. * @param {Object} rect
  5786. * @param {Object} hole
  5787. * @returns {Number[]}
  5788. */
  5789. PackerProcessor.prototype.splitRect = (function () {
  5790. var shards = [];
  5791. var width = 0;
  5792. var height = 0;
  5793. return function (rect, hole) {
  5794. // Reset old shards.
  5795. shards.length = 0;
  5796. // If the slot does not overlap with the hole add slot to the return data
  5797. // as is. Note that in this case we are eager to keep the slot as is if
  5798. // possible so we use the EPSILON in favour of that logic.
  5799. if (
  5800. rect.left + rect.width <= hole.left + EPS ||
  5801. hole.left + hole.width <= rect.left + EPS ||
  5802. rect.top + rect.height <= hole.top + EPS ||
  5803. hole.top + hole.height <= rect.top + EPS
  5804. ) {
  5805. shards.push(this.addRect(rect.left, rect.top, rect.width, rect.height));
  5806. return shards;
  5807. }
  5808. // Left split.
  5809. width = hole.left - rect.left;
  5810. if (width >= MIN_SLOT_SIZE) {
  5811. shards.push(this.addRect(rect.left, rect.top, width, rect.height));
  5812. }
  5813. // Right split.
  5814. width = rect.left + rect.width - (hole.left + hole.width);
  5815. if (width >= MIN_SLOT_SIZE) {
  5816. shards.push(this.addRect(hole.left + hole.width, rect.top, width, rect.height));
  5817. }
  5818. // Top split.
  5819. height = hole.top - rect.top;
  5820. if (height >= MIN_SLOT_SIZE) {
  5821. shards.push(this.addRect(rect.left, rect.top, rect.width, height));
  5822. }
  5823. // Bottom split.
  5824. height = rect.top + rect.height - (hole.top + hole.height);
  5825. if (height >= MIN_SLOT_SIZE) {
  5826. shards.push(this.addRect(rect.left, hole.top + hole.height, rect.width, height));
  5827. }
  5828. return shards;
  5829. };
  5830. })();
  5831. /**
  5832. * Check if a rectangle is fully within another rectangle.
  5833. *
  5834. * @param {Object} a
  5835. * @param {Object} b
  5836. * @returns {Boolean}
  5837. */
  5838. PackerProcessor.prototype.isRectAWithinRectB = function (a, b) {
  5839. return (
  5840. a.left + EPS >= b.left &&
  5841. a.top + EPS >= b.top &&
  5842. a.left + a.width - EPS <= b.left + b.width &&
  5843. a.top + a.height - EPS <= b.top + b.height
  5844. );
  5845. };
  5846. /**
  5847. * Loops through an array of rectangle ids and resets all that are fully
  5848. * within another rectangle in the array. Resetting in this case means that
  5849. * the rectangle id value is replaced with zero.
  5850. *
  5851. * @param {Number[]} rectIds
  5852. * @returns {Number[]}
  5853. */
  5854. PackerProcessor.prototype.purgeRects = (function () {
  5855. var rectA = {};
  5856. var rectB = {};
  5857. return function (rectIds) {
  5858. var i = rectIds.length;
  5859. var j;
  5860. while (i--) {
  5861. j = rectIds.length;
  5862. if (!rectIds[i]) continue;
  5863. this.getRect(rectIds[i], rectA);
  5864. while (j--) {
  5865. if (!rectIds[j] || i === j) continue;
  5866. this.getRect(rectIds[j], rectB);
  5867. if (this.isRectAWithinRectB(rectA, rectB)) {
  5868. rectIds[i] = 0;
  5869. break;
  5870. }
  5871. }
  5872. }
  5873. return rectIds;
  5874. };
  5875. })();
  5876. /**
  5877. * Sort rectangles with top-left gravity.
  5878. *
  5879. * @param {Number} aId
  5880. * @param {Number} bId
  5881. * @returns {Number}
  5882. */
  5883. PackerProcessor.prototype.sortRectsTopLeft = (function () {
  5884. var rectA = {};
  5885. var rectB = {};
  5886. return function (aId, bId) {
  5887. this.getRect(aId, rectA);
  5888. this.getRect(bId, rectB);
  5889. return rectA.top < rectB.top && rectA.top + EPS < rectB.top
  5890. ? -1
  5891. : rectA.top > rectB.top && rectA.top - EPS > rectB.top
  5892. ? 1
  5893. : rectA.left < rectB.left && rectA.left + EPS < rectB.left
  5894. ? -1
  5895. : rectA.left > rectB.left && rectA.left - EPS > rectB.left
  5896. ? 1
  5897. : 0;
  5898. };
  5899. })();
  5900. /**
  5901. * Sort rectangles with left-top gravity.
  5902. *
  5903. * @param {Number} aId
  5904. * @param {Number} bId
  5905. * @returns {Number}
  5906. */
  5907. PackerProcessor.prototype.sortRectsLeftTop = (function () {
  5908. var rectA = {};
  5909. var rectB = {};
  5910. return function (aId, bId) {
  5911. this.getRect(aId, rectA);
  5912. this.getRect(bId, rectB);
  5913. return rectA.left < rectB.left && rectA.left + EPS < rectB.left
  5914. ? -1
  5915. : rectA.left > rectB.left && rectA.left - EPS < rectB.left
  5916. ? 1
  5917. : rectA.top < rectB.top && rectA.top + EPS < rectB.top
  5918. ? -1
  5919. : rectA.top > rectB.top && rectA.top - EPS > rectB.top
  5920. ? 1
  5921. : 0;
  5922. };
  5923. })();
  5924. if (isWorker) {
  5925. var PACKET_INDEX_WIDTH = 1;
  5926. var PACKET_INDEX_HEIGHT = 2;
  5927. var PACKET_INDEX_OPTIONS = 3;
  5928. var PACKET_HEADER_SLOTS = 4;
  5929. var processor = new PackerProcessor();
  5930. self.onmessage = function (msg) {
  5931. var data = new Float32Array(msg.data);
  5932. var items = data.subarray(PACKET_HEADER_SLOTS, data.length);
  5933. var slots = new Float32Array(items.length);
  5934. var settings = data[PACKET_INDEX_OPTIONS];
  5935. var layout = {
  5936. items: items,
  5937. slots: slots,
  5938. width: data[PACKET_INDEX_WIDTH],
  5939. height: data[PACKET_INDEX_HEIGHT],
  5940. };
  5941. // Compute the layout (width / height / slots).
  5942. processor.computeLayout(layout, settings);
  5943. // Copy layout data to the return data.
  5944. data[PACKET_INDEX_WIDTH] = layout.width;
  5945. data[PACKET_INDEX_HEIGHT] = layout.height;
  5946. data.set(layout.slots, PACKET_HEADER_SLOTS);
  5947. // Send layout back to the main thread.
  5948. postMessage(data.buffer, [data.buffer]);
  5949. };
  5950. }
  5951. return PackerProcessor;
  5952. }
  5953. var PackerProcessor = createPackerProcessor();
  5954. //
  5955. // WORKER UTILS
  5956. //
  5957. var blobUrl = null;
  5958. var activeWorkers = [];
  5959. function createWorkerProcessors(amount, onmessage) {
  5960. var workers = [];
  5961. if (amount > 0) {
  5962. if (!blobUrl) {
  5963. blobUrl = URL.createObjectURL(
  5964. new Blob(['(' + createPackerProcessor.toString() + ')(true)'], {
  5965. type: 'application/javascript',
  5966. })
  5967. );
  5968. }
  5969. for (var i = 0, worker; i < amount; i++) {
  5970. worker = new Worker(blobUrl);
  5971. if (onmessage) worker.onmessage = onmessage;
  5972. workers.push(worker);
  5973. activeWorkers.push(worker);
  5974. }
  5975. }
  5976. return workers;
  5977. }
  5978. function destroyWorkerProcessors(workers) {
  5979. var worker;
  5980. var index;
  5981. for (var i = 0; i < workers.length; i++) {
  5982. worker = workers[i];
  5983. worker.onmessage = null;
  5984. worker.onerror = null;
  5985. worker.onmessageerror = null;
  5986. worker.terminate();
  5987. index = activeWorkers.indexOf(worker);
  5988. if (index > -1) activeWorkers.splice(index, 1);
  5989. }
  5990. if (blobUrl && !activeWorkers.length) {
  5991. URL.revokeObjectURL(blobUrl);
  5992. blobUrl = null;
  5993. }
  5994. }
  5995. function isWorkerProcessorsSupported() {
  5996. return !!(window.Worker && window.URL && window.Blob);
  5997. }
  5998. var FILL_GAPS = 1;
  5999. var HORIZONTAL = 2;
  6000. var ALIGN_RIGHT = 4;
  6001. var ALIGN_BOTTOM = 8;
  6002. var ROUNDING = 16;
  6003. var PACKET_INDEX_ID = 0;
  6004. var PACKET_INDEX_WIDTH = 1;
  6005. var PACKET_INDEX_HEIGHT = 2;
  6006. var PACKET_INDEX_OPTIONS = 3;
  6007. var PACKET_HEADER_SLOTS = 4;
  6008. /**
  6009. * @class
  6010. * @param {Number} [numWorkers=0]
  6011. * @param {Object} [options]
  6012. * @param {Boolean} [options.fillGaps=false]
  6013. * @param {Boolean} [options.horizontal=false]
  6014. * @param {Boolean} [options.alignRight=false]
  6015. * @param {Boolean} [options.alignBottom=false]
  6016. * @param {Boolean} [options.rounding=false]
  6017. */
  6018. function Packer(numWorkers, options) {
  6019. this._options = 0;
  6020. this._processor = null;
  6021. this._layoutQueue = [];
  6022. this._layouts = {};
  6023. this._layoutCallbacks = {};
  6024. this._layoutWorkers = {};
  6025. this._layoutWorkerData = {};
  6026. this._workers = [];
  6027. this._onWorkerMessage = this._onWorkerMessage.bind(this);
  6028. // Set initial options.
  6029. this.setOptions(options);
  6030. // Init the worker(s) or the processor if workers can't be used.
  6031. numWorkers = typeof numWorkers === 'number' ? Math.max(0, numWorkers) : 0;
  6032. if (numWorkers && isWorkerProcessorsSupported()) {
  6033. try {
  6034. this._workers = createWorkerProcessors(numWorkers, this._onWorkerMessage);
  6035. } catch (e) {
  6036. this._processor = new PackerProcessor();
  6037. }
  6038. } else {
  6039. this._processor = new PackerProcessor();
  6040. }
  6041. }
  6042. Packer.prototype._sendToWorker = function () {
  6043. if (!this._layoutQueue.length || !this._workers.length) return;
  6044. var layoutId = this._layoutQueue.shift();
  6045. var worker = this._workers.pop();
  6046. var data = this._layoutWorkerData[layoutId];
  6047. delete this._layoutWorkerData[layoutId];
  6048. this._layoutWorkers[layoutId] = worker;
  6049. worker.postMessage(data.buffer, [data.buffer]);
  6050. };
  6051. Packer.prototype._onWorkerMessage = function (msg) {
  6052. var data = new Float32Array(msg.data);
  6053. var layoutId = data[PACKET_INDEX_ID];
  6054. var layout = this._layouts[layoutId];
  6055. var callback = this._layoutCallbacks[layoutId];
  6056. var worker = this._layoutWorkers[layoutId];
  6057. if (layout) delete this._layoutCallbacks[layoutId];
  6058. if (callback) delete this._layoutCallbacks[layoutId];
  6059. if (worker) delete this._layoutWorkers[layoutId];
  6060. if (layout && callback) {
  6061. layout.width = data[PACKET_INDEX_WIDTH];
  6062. layout.height = data[PACKET_INDEX_HEIGHT];
  6063. layout.slots = data.subarray(PACKET_HEADER_SLOTS, data.length);
  6064. this._finalizeLayout(layout);
  6065. callback(layout);
  6066. }
  6067. if (worker) {
  6068. this._workers.push(worker);
  6069. this._sendToWorker();
  6070. }
  6071. };
  6072. Packer.prototype._finalizeLayout = function (layout) {
  6073. var grid = layout._grid;
  6074. var isHorizontal = layout._settings & HORIZONTAL;
  6075. var isBorderBox = grid._boxSizing === 'border-box';
  6076. delete layout._grid;
  6077. delete layout._settings;
  6078. layout.styles = {};
  6079. if (isHorizontal) {
  6080. layout.styles.width =
  6081. (isBorderBox ? layout.width + grid._borderLeft + grid._borderRight : layout.width) + 'px';
  6082. } else {
  6083. layout.styles.height =
  6084. (isBorderBox ? layout.height + grid._borderTop + grid._borderBottom : layout.height) + 'px';
  6085. }
  6086. return layout;
  6087. };
  6088. /**
  6089. * @public
  6090. * @param {Object} [options]
  6091. * @param {Boolean} [options.fillGaps]
  6092. * @param {Boolean} [options.horizontal]
  6093. * @param {Boolean} [options.alignRight]
  6094. * @param {Boolean} [options.alignBottom]
  6095. * @param {Boolean} [options.rounding]
  6096. */
  6097. Packer.prototype.setOptions = function (options) {
  6098. if (!options) return;
  6099. var fillGaps;
  6100. if (typeof options.fillGaps === 'boolean') {
  6101. fillGaps = options.fillGaps ? FILL_GAPS : 0;
  6102. } else {
  6103. fillGaps = this._options & FILL_GAPS;
  6104. }
  6105. var horizontal;
  6106. if (typeof options.horizontal === 'boolean') {
  6107. horizontal = options.horizontal ? HORIZONTAL : 0;
  6108. } else {
  6109. horizontal = this._options & HORIZONTAL;
  6110. }
  6111. var alignRight;
  6112. if (typeof options.alignRight === 'boolean') {
  6113. alignRight = options.alignRight ? ALIGN_RIGHT : 0;
  6114. } else {
  6115. alignRight = this._options & ALIGN_RIGHT;
  6116. }
  6117. var alignBottom;
  6118. if (typeof options.alignBottom === 'boolean') {
  6119. alignBottom = options.alignBottom ? ALIGN_BOTTOM : 0;
  6120. } else {
  6121. alignBottom = this._options & ALIGN_BOTTOM;
  6122. }
  6123. var rounding;
  6124. if (typeof options.rounding === 'boolean') {
  6125. rounding = options.rounding ? ROUNDING : 0;
  6126. } else {
  6127. rounding = this._options & ROUNDING;
  6128. }
  6129. this._options = fillGaps | horizontal | alignRight | alignBottom | rounding;
  6130. };
  6131. /**
  6132. * @public
  6133. * @param {Grid} grid
  6134. * @param {Number} layoutId
  6135. * @param {Item[]} items
  6136. * @param {Number} width
  6137. * @param {Number} height
  6138. * @param {Function} callback
  6139. * @returns {?Function}
  6140. */
  6141. Packer.prototype.createLayout = function (grid, layoutId, items, width, height, callback) {
  6142. if (this._layouts[layoutId]) {
  6143. throw new Error('A layout with the provided id is currently being processed.');
  6144. }
  6145. var horizontal = this._options & HORIZONTAL;
  6146. var layout = {
  6147. id: layoutId,
  6148. items: items,
  6149. slots: null,
  6150. width: horizontal ? 0 : width,
  6151. height: !horizontal ? 0 : height,
  6152. // Temporary data, which will be removed before sending the layout data
  6153. // outside of Packer's context.
  6154. _grid: grid,
  6155. _settings: this._options,
  6156. };
  6157. // If there are no items let's call the callback immediately.
  6158. if (!items.length) {
  6159. layout.slots = [];
  6160. this._finalizeLayout(layout);
  6161. callback(layout);
  6162. return;
  6163. }
  6164. // Create layout synchronously if needed.
  6165. if (this._processor) {
  6166. layout.slots = window.Float32Array
  6167. ? new Float32Array(items.length * 2)
  6168. : new Array(items.length * 2);
  6169. this._processor.computeLayout(layout, layout._settings);
  6170. this._finalizeLayout(layout);
  6171. callback(layout);
  6172. return;
  6173. }
  6174. // Worker data.
  6175. var data = new Float32Array(PACKET_HEADER_SLOTS + items.length * 2);
  6176. // Worker data header.
  6177. data[PACKET_INDEX_ID] = layoutId;
  6178. data[PACKET_INDEX_WIDTH] = layout.width;
  6179. data[PACKET_INDEX_HEIGHT] = layout.height;
  6180. data[PACKET_INDEX_OPTIONS] = layout._settings;
  6181. // Worker data items.
  6182. var i, j, item;
  6183. for (i = 0, j = PACKET_HEADER_SLOTS - 1, item; i < items.length; i++) {
  6184. item = items[i];
  6185. data[++j] = item._width + item._marginLeft + item._marginRight;
  6186. data[++j] = item._height + item._marginTop + item._marginBottom;
  6187. }
  6188. this._layoutQueue.push(layoutId);
  6189. this._layouts[layoutId] = layout;
  6190. this._layoutCallbacks[layoutId] = callback;
  6191. this._layoutWorkerData[layoutId] = data;
  6192. this._sendToWorker();
  6193. return this.cancelLayout.bind(this, layoutId);
  6194. };
  6195. /**
  6196. * @public
  6197. * @param {Number} layoutId
  6198. */
  6199. Packer.prototype.cancelLayout = function (layoutId) {
  6200. var layout = this._layouts[layoutId];
  6201. if (!layout) return;
  6202. delete this._layouts[layoutId];
  6203. delete this._layoutCallbacks[layoutId];
  6204. if (this._layoutWorkerData[layoutId]) {
  6205. delete this._layoutWorkerData[layoutId];
  6206. var queueIndex = this._layoutQueue.indexOf(layoutId);
  6207. if (queueIndex > -1) this._layoutQueue.splice(queueIndex, 1);
  6208. }
  6209. };
  6210. /**
  6211. * @public
  6212. */
  6213. Packer.prototype.destroy = function () {
  6214. // Move all currently used workers back in the workers array.
  6215. for (var key in this._layoutWorkers) {
  6216. this._workers.push(this._layoutWorkers[key]);
  6217. }
  6218. // Destroy all instance's workers.
  6219. destroyWorkerProcessors(this._workers);
  6220. // Reset data.
  6221. this._workers.length = 0;
  6222. this._layoutQueue.length = 0;
  6223. this._layouts = {};
  6224. this._layoutCallbacks = {};
  6225. this._layoutWorkers = {};
  6226. this._layoutWorkerData = {};
  6227. };
  6228. var debounceId = 0;
  6229. /**
  6230. * Returns a function, that, as long as it continues to be invoked, will not
  6231. * be triggered. The function will be called after it stops being called for
  6232. * N milliseconds. The returned function accepts one argument which, when
  6233. * being `true`, cancels the debounce function immediately. When the debounce
  6234. * function is canceled it cannot be invoked again.
  6235. *
  6236. * @param {Function} fn
  6237. * @param {Number} durationMs
  6238. * @returns {Function}
  6239. */
  6240. function debounce(fn, durationMs) {
  6241. var id = ++debounceId;
  6242. var timer = 0;
  6243. var lastTime = 0;
  6244. var isCanceled = false;
  6245. var tick = function (time) {
  6246. if (isCanceled) return;
  6247. if (lastTime) timer -= time - lastTime;
  6248. lastTime = time;
  6249. if (timer > 0) {
  6250. addDebounceTick(id, tick);
  6251. } else {
  6252. timer = lastTime = 0;
  6253. fn();
  6254. }
  6255. };
  6256. return function (cancel) {
  6257. if (isCanceled) return;
  6258. if (durationMs <= 0) {
  6259. if (cancel !== true) fn();
  6260. return;
  6261. }
  6262. if (cancel === true) {
  6263. isCanceled = true;
  6264. timer = lastTime = 0;
  6265. tick = undefined;
  6266. cancelDebounceTick(id);
  6267. return;
  6268. }
  6269. if (timer <= 0) {
  6270. timer = durationMs;
  6271. tick(0);
  6272. } else {
  6273. timer = durationMs;
  6274. }
  6275. };
  6276. }
  6277. var htmlCollectionType = '[object HTMLCollection]';
  6278. var nodeListType = '[object NodeList]';
  6279. /**
  6280. * Check if a value is a node list or a html collection.
  6281. *
  6282. * @param {*} val
  6283. * @returns {Boolean}
  6284. */
  6285. function isNodeList(val) {
  6286. var type = Object.prototype.toString.call(val);
  6287. return type === htmlCollectionType || type === nodeListType;
  6288. }
  6289. var objectType = 'object';
  6290. var objectToStringType = '[object Object]';
  6291. var toString = Object.prototype.toString;
  6292. /**
  6293. * Check if a value is a plain object.
  6294. *
  6295. * @param {*} val
  6296. * @returns {Boolean}
  6297. */
  6298. function isPlainObject(val) {
  6299. return typeof val === objectType && toString.call(val) === objectToStringType;
  6300. }
  6301. function noop() {}
  6302. /**
  6303. * Converts a value to an array or clones an array.
  6304. *
  6305. * @param {*} val
  6306. * @returns {Array}
  6307. */
  6308. function toArray(val) {
  6309. return isNodeList(val) ? Array.prototype.slice.call(val) : Array.prototype.concat(val);
  6310. }
  6311. var NUMBER_TYPE = 'number';
  6312. var STRING_TYPE = 'string';
  6313. var INSTANT_LAYOUT = 'instant';
  6314. var layoutId = 0;
  6315. /**
  6316. * Creates a new Grid instance.
  6317. *
  6318. * @class
  6319. * @param {(HTMLElement|String)} element
  6320. * @param {Object} [options]
  6321. * @param {(String|HTMLElement[]|NodeList|HTMLCollection)} [options.items="*"]
  6322. * @param {Number} [options.showDuration=300]
  6323. * @param {String} [options.showEasing="ease"]
  6324. * @param {Object} [options.visibleStyles={opacity: "1", transform: "scale(1)"}]
  6325. * @param {Number} [options.hideDuration=300]
  6326. * @param {String} [options.hideEasing="ease"]
  6327. * @param {Object} [options.hiddenStyles={opacity: "0", transform: "scale(0.5)"}]
  6328. * @param {(Function|Object)} [options.layout]
  6329. * @param {Boolean} [options.layout.fillGaps=false]
  6330. * @param {Boolean} [options.layout.horizontal=false]
  6331. * @param {Boolean} [options.layout.alignRight=false]
  6332. * @param {Boolean} [options.layout.alignBottom=false]
  6333. * @param {Boolean} [options.layout.rounding=false]
  6334. * @param {(Boolean|Number)} [options.layoutOnResize=150]
  6335. * @param {Boolean} [options.layoutOnInit=true]
  6336. * @param {Number} [options.layoutDuration=300]
  6337. * @param {String} [options.layoutEasing="ease"]
  6338. * @param {?Object} [options.sortData=null]
  6339. * @param {Boolean} [options.dragEnabled=false]
  6340. * @param {?String} [options.dragHandle=null]
  6341. * @param {?HtmlElement} [options.dragContainer=null]
  6342. * @param {?Function} [options.dragStartPredicate]
  6343. * @param {Number} [options.dragStartPredicate.distance=0]
  6344. * @param {Number} [options.dragStartPredicate.delay=0]
  6345. * @param {String} [options.dragAxis="xy"]
  6346. * @param {(Boolean|Function)} [options.dragSort=true]
  6347. * @param {Object} [options.dragSortHeuristics]
  6348. * @param {Number} [options.dragSortHeuristics.sortInterval=100]
  6349. * @param {Number} [options.dragSortHeuristics.minDragDistance=10]
  6350. * @param {Number} [options.dragSortHeuristics.minBounceBackAngle=1]
  6351. * @param {(Function|Object)} [options.dragSortPredicate]
  6352. * @param {Number} [options.dragSortPredicate.threshold=50]
  6353. * @param {String} [options.dragSortPredicate.action="move"]
  6354. * @param {String} [options.dragSortPredicate.migrateAction="move"]
  6355. * @param {Object} [options.dragRelease]
  6356. * @param {Number} [options.dragRelease.duration=300]
  6357. * @param {String} [options.dragRelease.easing="ease"]
  6358. * @param {Boolean} [options.dragRelease.useDragContainer=true]
  6359. * @param {Object} [options.dragCssProps]
  6360. * @param {Object} [options.dragPlaceholder]
  6361. * @param {Boolean} [options.dragPlaceholder.enabled=false]
  6362. * @param {?Function} [options.dragPlaceholder.createElement=null]
  6363. * @param {?Function} [options.dragPlaceholder.onCreate=null]
  6364. * @param {?Function} [options.dragPlaceholder.onRemove=null]
  6365. * @param {Object} [options.dragAutoScroll]
  6366. * @param {(Function|Array)} [options.dragAutoScroll.targets=[]]
  6367. * @param {?Function} [options.dragAutoScroll.handle=null]
  6368. * @param {Number} [options.dragAutoScroll.threshold=50]
  6369. * @param {Number} [options.dragAutoScroll.safeZone=0.2]
  6370. * @param {(Function|Number)} [options.dragAutoScroll.speed]
  6371. * @param {Boolean} [options.dragAutoScroll.sortDuringScroll=true]
  6372. * @param {Boolean} [options.dragAutoScroll.smoothStop=false]
  6373. * @param {?Function} [options.dragAutoScroll.onStart=null]
  6374. * @param {?Function} [options.dragAutoScroll.onStop=null]
  6375. * @param {String} [options.containerClass="muuri"]
  6376. * @param {String} [options.itemClass="muuri-item"]
  6377. * @param {String} [options.itemVisibleClass="muuri-item-visible"]
  6378. * @param {String} [options.itemHiddenClass="muuri-item-hidden"]
  6379. * @param {String} [options.itemPositioningClass="muuri-item-positioning"]
  6380. * @param {String} [options.itemDraggingClass="muuri-item-dragging"]
  6381. * @param {String} [options.itemReleasingClass="muuri-item-releasing"]
  6382. * @param {String} [options.itemPlaceholderClass="muuri-item-placeholder"]
  6383. */
  6384. function Grid(element, options) {
  6385. // Allow passing element as selector string
  6386. if (typeof element === STRING_TYPE) {
  6387. element = document.querySelector(element);
  6388. }
  6389. // Throw an error if the container element is not body element or does not
  6390. // exist within the body element.
  6391. var isElementInDom = element.getRootNode
  6392. ? element.getRootNode({ composed: true }) === document
  6393. : document.body.contains(element);
  6394. if (!isElementInDom || element === document.documentElement) {
  6395. throw new Error('Container element must be an existing DOM element.');
  6396. }
  6397. // Create instance settings by merging the options with default options.
  6398. var settings = mergeSettings(Grid.defaultOptions, options);
  6399. settings.visibleStyles = normalizeStyles(settings.visibleStyles);
  6400. settings.hiddenStyles = normalizeStyles(settings.hiddenStyles);
  6401. if (!isFunction(settings.dragSort)) {
  6402. settings.dragSort = !!settings.dragSort;
  6403. }
  6404. this._id = createUid();
  6405. this._element = element;
  6406. this._settings = settings;
  6407. this._isDestroyed = false;
  6408. this._items = [];
  6409. this._layout = {
  6410. id: 0,
  6411. items: [],
  6412. slots: [],
  6413. };
  6414. this._isLayoutFinished = true;
  6415. this._nextLayoutData = null;
  6416. this._emitter = new Emitter();
  6417. this._onLayoutDataReceived = this._onLayoutDataReceived.bind(this);
  6418. // Store grid instance to the grid instances collection.
  6419. GRID_INSTANCES[this._id] = this;
  6420. // Add container element's class name.
  6421. addClass(element, settings.containerClass);
  6422. // If layoutOnResize option is a valid number sanitize it and bind the resize
  6423. // handler.
  6424. bindLayoutOnResize(this, settings.layoutOnResize);
  6425. // Add initial items.
  6426. this.add(getInitialGridElements(element, settings.items), { layout: false });
  6427. // Layout on init if necessary.
  6428. if (settings.layoutOnInit) {
  6429. this.layout(true);
  6430. }
  6431. }
  6432. /**
  6433. * Public properties
  6434. * *****************
  6435. */
  6436. /**
  6437. * @public
  6438. * @static
  6439. * @see Item
  6440. */
  6441. Grid.Item = Item;
  6442. /**
  6443. * @public
  6444. * @static
  6445. * @see ItemLayout
  6446. */
  6447. Grid.ItemLayout = ItemLayout;
  6448. /**
  6449. * @public
  6450. * @static
  6451. * @see ItemVisibility
  6452. */
  6453. Grid.ItemVisibility = ItemVisibility;
  6454. /**
  6455. * @public
  6456. * @static
  6457. * @see ItemMigrate
  6458. */
  6459. Grid.ItemMigrate = ItemMigrate;
  6460. /**
  6461. * @public
  6462. * @static
  6463. * @see ItemDrag
  6464. */
  6465. Grid.ItemDrag = ItemDrag;
  6466. /**
  6467. * @public
  6468. * @static
  6469. * @see ItemDragRelease
  6470. */
  6471. Grid.ItemDragRelease = ItemDragRelease;
  6472. /**
  6473. * @public
  6474. * @static
  6475. * @see ItemDragPlaceholder
  6476. */
  6477. Grid.ItemDragPlaceholder = ItemDragPlaceholder;
  6478. /**
  6479. * @public
  6480. * @static
  6481. * @see Emitter
  6482. */
  6483. Grid.Emitter = Emitter;
  6484. /**
  6485. * @public
  6486. * @static
  6487. * @see Animator
  6488. */
  6489. Grid.Animator = Animator;
  6490. /**
  6491. * @public
  6492. * @static
  6493. * @see Dragger
  6494. */
  6495. Grid.Dragger = Dragger;
  6496. /**
  6497. * @public
  6498. * @static
  6499. * @see Packer
  6500. */
  6501. Grid.Packer = Packer;
  6502. /**
  6503. * @public
  6504. * @static
  6505. * @see AutoScroller
  6506. */
  6507. Grid.AutoScroller = AutoScroller;
  6508. /**
  6509. * The default Packer instance used by default for all layouts.
  6510. *
  6511. * @public
  6512. * @static
  6513. * @type {Packer}
  6514. */
  6515. Grid.defaultPacker = new Packer(2);
  6516. /**
  6517. * Default options for Grid instance.
  6518. *
  6519. * @public
  6520. * @static
  6521. * @type {Object}
  6522. */
  6523. Grid.defaultOptions = {
  6524. // Initial item elements
  6525. items: '*',
  6526. // Default show animation
  6527. showDuration: 300,
  6528. showEasing: 'ease',
  6529. // Default hide animation
  6530. hideDuration: 300,
  6531. hideEasing: 'ease',
  6532. // Item's visible/hidden state styles
  6533. visibleStyles: {
  6534. opacity: '1',
  6535. transform: 'scale(1)',
  6536. },
  6537. hiddenStyles: {
  6538. opacity: '0',
  6539. transform: 'scale(0.5)',
  6540. },
  6541. // Layout
  6542. layout: {
  6543. fillGaps: false,
  6544. horizontal: false,
  6545. alignRight: false,
  6546. alignBottom: false,
  6547. rounding: false,
  6548. },
  6549. layoutOnResize: 150,
  6550. layoutOnInit: true,
  6551. layoutDuration: 300,
  6552. layoutEasing: 'ease',
  6553. // Sorting
  6554. sortData: null,
  6555. // Drag & Drop
  6556. dragEnabled: false,
  6557. dragContainer: null,
  6558. dragHandle: null,
  6559. dragStartPredicate: {
  6560. distance: 0,
  6561. delay: 0,
  6562. },
  6563. dragAxis: 'xy',
  6564. dragSort: true,
  6565. dragSortHeuristics: {
  6566. sortInterval: 100,
  6567. minDragDistance: 10,
  6568. minBounceBackAngle: 1,
  6569. },
  6570. dragSortPredicate: {
  6571. threshold: 50,
  6572. action: ACTION_MOVE,
  6573. migrateAction: ACTION_MOVE,
  6574. },
  6575. dragRelease: {
  6576. duration: 300,
  6577. easing: 'ease',
  6578. useDragContainer: true,
  6579. },
  6580. dragCssProps: {
  6581. touchAction: 'none',
  6582. userSelect: 'none',
  6583. userDrag: 'none',
  6584. tapHighlightColor: 'rgba(0, 0, 0, 0)',
  6585. touchCallout: 'none',
  6586. contentZooming: 'none',
  6587. },
  6588. dragPlaceholder: {
  6589. enabled: false,
  6590. createElement: null,
  6591. onCreate: null,
  6592. onRemove: null,
  6593. },
  6594. dragAutoScroll: {
  6595. targets: [],
  6596. handle: null,
  6597. threshold: 50,
  6598. safeZone: 0.2,
  6599. speed: AutoScroller.smoothSpeed(1000, 2000, 2500),
  6600. sortDuringScroll: true,
  6601. smoothStop: false,
  6602. onStart: null,
  6603. onStop: null,
  6604. },
  6605. // Classnames
  6606. containerClass: 'muuri',
  6607. itemClass: 'muuri-item',
  6608. itemVisibleClass: 'muuri-item-shown',
  6609. itemHiddenClass: 'muuri-item-hidden',
  6610. itemPositioningClass: 'muuri-item-positioning',
  6611. itemDraggingClass: 'muuri-item-dragging',
  6612. itemReleasingClass: 'muuri-item-releasing',
  6613. itemPlaceholderClass: 'muuri-item-placeholder',
  6614. };
  6615. /**
  6616. * Public prototype methods
  6617. * ************************
  6618. */
  6619. /**
  6620. * Bind an event listener.
  6621. *
  6622. * @public
  6623. * @param {String} event
  6624. * @param {Function} listener
  6625. * @returns {Grid}
  6626. */
  6627. Grid.prototype.on = function (event, listener) {
  6628. this._emitter.on(event, listener);
  6629. return this;
  6630. };
  6631. /**
  6632. * Unbind an event listener.
  6633. *
  6634. * @public
  6635. * @param {String} event
  6636. * @param {Function} listener
  6637. * @returns {Grid}
  6638. */
  6639. Grid.prototype.off = function (event, listener) {
  6640. this._emitter.off(event, listener);
  6641. return this;
  6642. };
  6643. /**
  6644. * Get the container element.
  6645. *
  6646. * @public
  6647. * @returns {HTMLElement}
  6648. */
  6649. Grid.prototype.getElement = function () {
  6650. return this._element;
  6651. };
  6652. /**
  6653. * Get instance's item by element or by index. Target can also be an Item
  6654. * instance in which case the function returns the item if it exists within
  6655. * related Grid instance. If nothing is found with the provided target, null
  6656. * is returned.
  6657. *
  6658. * @private
  6659. * @param {(HtmlElement|Number|Item)} [target]
  6660. * @returns {?Item}
  6661. */
  6662. Grid.prototype.getItem = function (target) {
  6663. // If no target is specified or the instance is destroyed, return null.
  6664. if (this._isDestroyed || (!target && target !== 0)) {
  6665. return null;
  6666. }
  6667. // If target is number return the item in that index. If the number is lower
  6668. // than zero look for the item starting from the end of the items array. For
  6669. // example -1 for the last item, -2 for the second last item, etc.
  6670. if (typeof target === NUMBER_TYPE) {
  6671. return this._items[target > -1 ? target : this._items.length + target] || null;
  6672. }
  6673. // If the target is an instance of Item return it if it is attached to this
  6674. // Grid instance, otherwise return null.
  6675. if (target instanceof Item) {
  6676. return target._gridId === this._id ? target : null;
  6677. }
  6678. // In other cases let's assume that the target is an element, so let's try
  6679. // to find an item that matches the element and return it. If item is not
  6680. // found return null.
  6681. if (ITEM_ELEMENT_MAP) {
  6682. var item = ITEM_ELEMENT_MAP.get(target);
  6683. return item && item._gridId === this._id ? item : null;
  6684. } else {
  6685. for (var i = 0; i < this._items.length; i++) {
  6686. if (this._items[i]._element === target) {
  6687. return this._items[i];
  6688. }
  6689. }
  6690. }
  6691. return null;
  6692. };
  6693. /**
  6694. * Get all items. Optionally you can provide specific targets (elements,
  6695. * indices and item instances). All items that are not found are omitted from
  6696. * the returned array.
  6697. *
  6698. * @public
  6699. * @param {(HtmlElement|Number|Item|Array)} [targets]
  6700. * @returns {Item[]}
  6701. */
  6702. Grid.prototype.getItems = function (targets) {
  6703. // Return all items immediately if no targets were provided or if the
  6704. // instance is destroyed.
  6705. if (this._isDestroyed || targets === undefined) {
  6706. return this._items.slice(0);
  6707. }
  6708. var items = [];
  6709. var i, item;
  6710. if (Array.isArray(targets) || isNodeList(targets)) {
  6711. for (i = 0; i < targets.length; i++) {
  6712. item = this.getItem(targets[i]);
  6713. if (item) items.push(item);
  6714. }
  6715. } else {
  6716. item = this.getItem(targets);
  6717. if (item) items.push(item);
  6718. }
  6719. return items;
  6720. };
  6721. /**
  6722. * Update the cached dimensions of the instance's items. By default all the
  6723. * items are refreshed, but you can also provide an array of target items as the
  6724. * first argument if you want to refresh specific items. Note that all hidden
  6725. * items are not refreshed by default since their "display" property is "none"
  6726. * and their dimensions are therefore not readable from the DOM. However, if you
  6727. * do want to force update hidden item dimensions too you can provide `true`
  6728. * as the second argument, which makes the elements temporarily visible while
  6729. * their dimensions are being read.
  6730. *
  6731. * @public
  6732. * @param {Item[]} [items]
  6733. * @param {Boolean} [force=false]
  6734. * @returns {Grid}
  6735. */
  6736. Grid.prototype.refreshItems = function (items, force) {
  6737. if (this._isDestroyed) return this;
  6738. var targets = items || this._items;
  6739. var i, item, style, hiddenItemStyles;
  6740. if (force === true) {
  6741. hiddenItemStyles = [];
  6742. for (i = 0; i < targets.length; i++) {
  6743. item = targets[i];
  6744. if (!item.isVisible() && !item.isHiding()) {
  6745. style = item.getElement().style;
  6746. style.visibility = 'hidden';
  6747. style.display = '';
  6748. hiddenItemStyles.push(style);
  6749. }
  6750. }
  6751. }
  6752. for (i = 0; i < targets.length; i++) {
  6753. targets[i]._refreshDimensions(force);
  6754. }
  6755. if (force === true) {
  6756. for (i = 0; i < hiddenItemStyles.length; i++) {
  6757. style = hiddenItemStyles[i];
  6758. style.visibility = '';
  6759. style.display = 'none';
  6760. }
  6761. hiddenItemStyles.length = 0;
  6762. }
  6763. return this;
  6764. };
  6765. /**
  6766. * Update the sort data of the instance's items. By default all the items are
  6767. * refreshed, but you can also provide an array of target items if you want to
  6768. * refresh specific items.
  6769. *
  6770. * @public
  6771. * @param {Item[]} [items]
  6772. * @returns {Grid}
  6773. */
  6774. Grid.prototype.refreshSortData = function (items) {
  6775. if (this._isDestroyed) return this;
  6776. var targets = items || this._items;
  6777. for (var i = 0; i < targets.length; i++) {
  6778. targets[i]._refreshSortData();
  6779. }
  6780. return this;
  6781. };
  6782. /**
  6783. * Synchronize the item elements to match the order of the items in the DOM.
  6784. * This comes handy if you need to keep the DOM structure matched with the
  6785. * order of the items. Note that if an item's element is not currently a child
  6786. * of the container element (if it is dragged for example) it is ignored and
  6787. * left untouched.
  6788. *
  6789. * @public
  6790. * @returns {Grid}
  6791. */
  6792. Grid.prototype.synchronize = function () {
  6793. if (this._isDestroyed) return this;
  6794. var items = this._items;
  6795. if (!items.length) return this;
  6796. var fragment;
  6797. var element;
  6798. for (var i = 0; i < items.length; i++) {
  6799. element = items[i]._element;
  6800. if (element.parentNode === this._element) {
  6801. fragment = fragment || document.createDocumentFragment();
  6802. fragment.appendChild(element);
  6803. }
  6804. }
  6805. if (!fragment) return this;
  6806. this._element.appendChild(fragment);
  6807. this._emit(EVENT_SYNCHRONIZE);
  6808. return this;
  6809. };
  6810. /**
  6811. * Calculate and apply item positions.
  6812. *
  6813. * @public
  6814. * @param {Boolean} [instant=false]
  6815. * @param {Function} [onFinish]
  6816. * @returns {Grid}
  6817. */
  6818. Grid.prototype.layout = function (instant, onFinish) {
  6819. if (this._isDestroyed) return this;
  6820. // Cancel unfinished layout algorithm if possible.
  6821. var unfinishedLayout = this._nextLayoutData;
  6822. if (unfinishedLayout && isFunction(unfinishedLayout.cancel)) {
  6823. unfinishedLayout.cancel();
  6824. }
  6825. // Compute layout id (let's stay in Float32 range).
  6826. layoutId = (layoutId % MAX_SAFE_FLOAT32_INTEGER) + 1;
  6827. var nextLayoutId = layoutId;
  6828. // Store data for next layout.
  6829. this._nextLayoutData = {
  6830. id: nextLayoutId,
  6831. instant: instant,
  6832. onFinish: onFinish,
  6833. cancel: null,
  6834. };
  6835. // Collect layout items (all active grid items).
  6836. var items = this._items;
  6837. var layoutItems = [];
  6838. for (var i = 0; i < items.length; i++) {
  6839. if (items[i]._isActive) layoutItems.push(items[i]);
  6840. }
  6841. // Compute new layout.
  6842. this._refreshDimensions();
  6843. var gridWidth = this._width - this._borderLeft - this._borderRight;
  6844. var gridHeight = this._height - this._borderTop - this._borderBottom;
  6845. var layoutSettings = this._settings.layout;
  6846. var cancelLayout;
  6847. if (isFunction(layoutSettings)) {
  6848. cancelLayout = layoutSettings(
  6849. this,
  6850. nextLayoutId,
  6851. layoutItems,
  6852. gridWidth,
  6853. gridHeight,
  6854. this._onLayoutDataReceived
  6855. );
  6856. } else {
  6857. Grid.defaultPacker.setOptions(layoutSettings);
  6858. cancelLayout = Grid.defaultPacker.createLayout(
  6859. this,
  6860. nextLayoutId,
  6861. layoutItems,
  6862. gridWidth,
  6863. gridHeight,
  6864. this._onLayoutDataReceived
  6865. );
  6866. }
  6867. // Store layout cancel method if available.
  6868. if (
  6869. isFunction(cancelLayout) &&
  6870. this._nextLayoutData &&
  6871. this._nextLayoutData.id === nextLayoutId
  6872. ) {
  6873. this._nextLayoutData.cancel = cancelLayout;
  6874. }
  6875. return this;
  6876. };
  6877. /**
  6878. * Add new items by providing the elements you wish to add to the instance and
  6879. * optionally provide the index where you want the items to be inserted into.
  6880. * All elements that are not already children of the container element will be
  6881. * automatically appended to the container element. If an element has it's CSS
  6882. * display property set to "none" it will be marked as inactive during the
  6883. * initiation process. As long as the item is inactive it will not be part of
  6884. * the layout, but it will retain it's index. You can activate items at any
  6885. * point with grid.show() method. This method will automatically call
  6886. * grid.layout() if one or more of the added elements are visible. If only
  6887. * hidden items are added no layout will be called. All the new visible items
  6888. * are positioned without animation during their first layout.
  6889. *
  6890. * @public
  6891. * @param {(HTMLElement|HTMLElement[])} elements
  6892. * @param {Object} [options]
  6893. * @param {Number} [options.index=-1]
  6894. * @param {Boolean} [options.active]
  6895. * @param {(Boolean|Function|String)} [options.layout=true]
  6896. * @returns {Item[]}
  6897. */
  6898. Grid.prototype.add = function (elements, options) {
  6899. if (this._isDestroyed || !elements) return [];
  6900. var newItems = toArray(elements);
  6901. if (!newItems.length) return newItems;
  6902. var opts = options || {};
  6903. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  6904. var items = this._items;
  6905. var needsLayout = false;
  6906. var fragment;
  6907. var element;
  6908. var item;
  6909. var i;
  6910. // Collect all the elements that are not child of the grid element into a
  6911. // document fragment.
  6912. for (i = 0; i < newItems.length; i++) {
  6913. element = newItems[i];
  6914. if (element.parentNode !== this._element) {
  6915. fragment = fragment || document.createDocumentFragment();
  6916. fragment.appendChild(element);
  6917. }
  6918. }
  6919. // If we have a fragment, let's append it to the grid element. We could just
  6920. // not do this and the `new Item()` instantiation would handle this for us,
  6921. // but this way we can add the elements into the DOM a bit faster.
  6922. if (fragment) {
  6923. this._element.appendChild(fragment);
  6924. }
  6925. // Map provided elements into new grid items.
  6926. for (i = 0; i < newItems.length; i++) {
  6927. element = newItems[i];
  6928. item = newItems[i] = new Item(this, element, opts.active);
  6929. // If the item to be added is active, we need to do a layout. Also, we
  6930. // need to mark the item with the skipNextAnimation flag to make it
  6931. // position instantly (without animation) during the next layout. Without
  6932. // the hack the item would animate to it's new position from the northwest
  6933. // corner of the grid, which feels a bit buggy (imho).
  6934. if (item._isActive) {
  6935. needsLayout = true;
  6936. item._layout._skipNextAnimation = true;
  6937. }
  6938. }
  6939. // Set up the items' initial dimensions and sort data. This needs to be done
  6940. // in a separate loop to avoid layout thrashing.
  6941. for (i = 0; i < newItems.length; i++) {
  6942. item = newItems[i];
  6943. item._refreshDimensions();
  6944. item._refreshSortData();
  6945. }
  6946. // Add the new items to the items collection to correct index.
  6947. arrayInsert(items, newItems, opts.index);
  6948. // Emit add event.
  6949. if (this._hasListeners(EVENT_ADD)) {
  6950. this._emit(EVENT_ADD, newItems.slice(0));
  6951. }
  6952. // If layout is needed.
  6953. if (needsLayout && layout) {
  6954. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  6955. }
  6956. return newItems;
  6957. };
  6958. /**
  6959. * Remove items from the instance.
  6960. *
  6961. * @public
  6962. * @param {Item[]} items
  6963. * @param {Object} [options]
  6964. * @param {Boolean} [options.removeElements=false]
  6965. * @param {(Boolean|Function|String)} [options.layout=true]
  6966. * @returns {Item[]}
  6967. */
  6968. Grid.prototype.remove = function (items, options) {
  6969. if (this._isDestroyed || !items.length) return [];
  6970. var opts = options || {};
  6971. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  6972. var needsLayout = false;
  6973. var allItems = this.getItems();
  6974. var targetItems = [];
  6975. var indices = [];
  6976. var index;
  6977. var item;
  6978. var i;
  6979. // Remove the individual items.
  6980. for (i = 0; i < items.length; i++) {
  6981. item = items[i];
  6982. if (item._isDestroyed) continue;
  6983. index = this._items.indexOf(item);
  6984. if (index === -1) continue;
  6985. if (item._isActive) needsLayout = true;
  6986. targetItems.push(item);
  6987. indices.push(allItems.indexOf(item));
  6988. item._destroy(opts.removeElements);
  6989. this._items.splice(index, 1);
  6990. }
  6991. // Emit remove event.
  6992. if (this._hasListeners(EVENT_REMOVE)) {
  6993. this._emit(EVENT_REMOVE, targetItems.slice(0), indices);
  6994. }
  6995. // If layout is needed.
  6996. if (needsLayout && layout) {
  6997. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  6998. }
  6999. return targetItems;
  7000. };
  7001. /**
  7002. * Show specific instance items.
  7003. *
  7004. * @public
  7005. * @param {Item[]} items
  7006. * @param {Object} [options]
  7007. * @param {Boolean} [options.instant=false]
  7008. * @param {Boolean} [options.syncWithLayout=true]
  7009. * @param {Function} [options.onFinish]
  7010. * @param {(Boolean|Function|String)} [options.layout=true]
  7011. * @returns {Grid}
  7012. */
  7013. Grid.prototype.show = function (items, options) {
  7014. if (!this._isDestroyed && items.length) {
  7015. this._setItemsVisibility(items, true, options);
  7016. }
  7017. return this;
  7018. };
  7019. /**
  7020. * Hide specific instance items.
  7021. *
  7022. * @public
  7023. * @param {Item[]} items
  7024. * @param {Object} [options]
  7025. * @param {Boolean} [options.instant=false]
  7026. * @param {Boolean} [options.syncWithLayout=true]
  7027. * @param {Function} [options.onFinish]
  7028. * @param {(Boolean|Function|String)} [options.layout=true]
  7029. * @returns {Grid}
  7030. */
  7031. Grid.prototype.hide = function (items, options) {
  7032. if (!this._isDestroyed && items.length) {
  7033. this._setItemsVisibility(items, false, options);
  7034. }
  7035. return this;
  7036. };
  7037. /**
  7038. * Filter items. Expects at least one argument, a predicate, which should be
  7039. * either a function or a string. The predicate callback is executed for every
  7040. * item in the instance. If the return value of the predicate is truthy the
  7041. * item in question will be shown and otherwise hidden. The predicate callback
  7042. * receives the item instance as it's argument. If the predicate is a string
  7043. * it is considered to be a selector and it is checked against every item
  7044. * element in the instance with the native element.matches() method. All the
  7045. * matching items will be shown and others hidden.
  7046. *
  7047. * @public
  7048. * @param {(Function|String)} predicate
  7049. * @param {Object} [options]
  7050. * @param {Boolean} [options.instant=false]
  7051. * @param {Boolean} [options.syncWithLayout=true]
  7052. * @param {FilterCallback} [options.onFinish]
  7053. * @param {(Boolean|Function|String)} [options.layout=true]
  7054. * @returns {Grid}
  7055. */
  7056. Grid.prototype.filter = function (predicate, options) {
  7057. if (this._isDestroyed || !this._items.length) return this;
  7058. var itemsToShow = [];
  7059. var itemsToHide = [];
  7060. var isPredicateString = typeof predicate === STRING_TYPE;
  7061. var isPredicateFn = isFunction(predicate);
  7062. var opts = options || {};
  7063. var isInstant = opts.instant === true;
  7064. var syncWithLayout = opts.syncWithLayout;
  7065. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  7066. var onFinish = isFunction(opts.onFinish) ? opts.onFinish : null;
  7067. var tryFinishCounter = -1;
  7068. var tryFinish = noop;
  7069. var item;
  7070. var i;
  7071. // If we have onFinish callback, let's create proper tryFinish callback.
  7072. if (onFinish) {
  7073. tryFinish = function () {
  7074. ++tryFinishCounter && onFinish(itemsToShow.slice(0), itemsToHide.slice(0));
  7075. };
  7076. }
  7077. // Check which items need to be shown and which hidden.
  7078. if (isPredicateFn || isPredicateString) {
  7079. for (i = 0; i < this._items.length; i++) {
  7080. item = this._items[i];
  7081. if (isPredicateFn ? predicate(item) : elementMatches(item._element, predicate)) {
  7082. itemsToShow.push(item);
  7083. } else {
  7084. itemsToHide.push(item);
  7085. }
  7086. }
  7087. }
  7088. // Show items that need to be shown.
  7089. if (itemsToShow.length) {
  7090. this.show(itemsToShow, {
  7091. instant: isInstant,
  7092. syncWithLayout: syncWithLayout,
  7093. onFinish: tryFinish,
  7094. layout: false,
  7095. });
  7096. } else {
  7097. tryFinish();
  7098. }
  7099. // Hide items that need to be hidden.
  7100. if (itemsToHide.length) {
  7101. this.hide(itemsToHide, {
  7102. instant: isInstant,
  7103. syncWithLayout: syncWithLayout,
  7104. onFinish: tryFinish,
  7105. layout: false,
  7106. });
  7107. } else {
  7108. tryFinish();
  7109. }
  7110. // If there are any items to filter.
  7111. if (itemsToShow.length || itemsToHide.length) {
  7112. // Emit filter event.
  7113. if (this._hasListeners(EVENT_FILTER)) {
  7114. this._emit(EVENT_FILTER, itemsToShow.slice(0), itemsToHide.slice(0));
  7115. }
  7116. // If layout is needed.
  7117. if (layout) {
  7118. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  7119. }
  7120. }
  7121. return this;
  7122. };
  7123. /**
  7124. * Sort items. There are three ways to sort the items. The first is simply by
  7125. * providing a function as the comparer which works identically to native
  7126. * array sort. Alternatively you can sort by the sort data you have provided
  7127. * in the instance's options. Just provide the sort data key(s) as a string
  7128. * (separated by space) and the items will be sorted based on the provided
  7129. * sort data keys. Lastly you have the opportunity to provide a presorted
  7130. * array of items which will be used to sync the internal items array in the
  7131. * same order.
  7132. *
  7133. * @public
  7134. * @param {(Function|String|Item[])} comparer
  7135. * @param {Object} [options]
  7136. * @param {Boolean} [options.descending=false]
  7137. * @param {(Boolean|Function|String)} [options.layout=true]
  7138. * @returns {Grid}
  7139. */
  7140. Grid.prototype.sort = (function () {
  7141. var sortComparer;
  7142. var isDescending;
  7143. var origItems;
  7144. var indexMap;
  7145. function defaultComparer(a, b) {
  7146. var result = 0;
  7147. var criteriaName;
  7148. var criteriaOrder;
  7149. var valA;
  7150. var valB;
  7151. // Loop through the list of sort criteria.
  7152. for (var i = 0; i < sortComparer.length; i++) {
  7153. // Get the criteria name, which should match an item's sort data key.
  7154. criteriaName = sortComparer[i][0];
  7155. criteriaOrder = sortComparer[i][1];
  7156. // Get items' cached sort values for the criteria. If the item has no sort
  7157. // data let's update the items sort data (this is a lazy load mechanism).
  7158. valA = (a._sortData ? a : a._refreshSortData())._sortData[criteriaName];
  7159. valB = (b._sortData ? b : b._refreshSortData())._sortData[criteriaName];
  7160. // Sort the items in descending order if defined so explicitly. Otherwise
  7161. // sort items in ascending order.
  7162. if (criteriaOrder === 'desc' || (!criteriaOrder && isDescending)) {
  7163. result = valB < valA ? -1 : valB > valA ? 1 : 0;
  7164. } else {
  7165. result = valA < valB ? -1 : valA > valB ? 1 : 0;
  7166. }
  7167. // If we have -1 or 1 as the return value, let's return it immediately.
  7168. if (result) return result;
  7169. }
  7170. // If values are equal let's compare the item indices to make sure we
  7171. // have a stable sort. Note that this is not necessary in evergreen browsers
  7172. // because Array.sort() is nowadays stable. However, in order to guarantee
  7173. // same results in older browsers we need this.
  7174. if (!result) {
  7175. if (!indexMap) indexMap = createIndexMap(origItems);
  7176. result = isDescending ? compareIndexMap(indexMap, b, a) : compareIndexMap(indexMap, a, b);
  7177. }
  7178. return result;
  7179. }
  7180. function customComparer(a, b) {
  7181. var result = isDescending ? -sortComparer(a, b) : sortComparer(a, b);
  7182. if (!result) {
  7183. if (!indexMap) indexMap = createIndexMap(origItems);
  7184. result = isDescending ? compareIndexMap(indexMap, b, a) : compareIndexMap(indexMap, a, b);
  7185. }
  7186. return result;
  7187. }
  7188. return function (comparer, options) {
  7189. if (this._isDestroyed || this._items.length < 2) return this;
  7190. var items = this._items;
  7191. var opts = options || {};
  7192. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  7193. // Setup parent scope data.
  7194. isDescending = !!opts.descending;
  7195. origItems = items.slice(0);
  7196. indexMap = null;
  7197. // If function is provided do a native array sort.
  7198. if (isFunction(comparer)) {
  7199. sortComparer = comparer;
  7200. items.sort(customComparer);
  7201. }
  7202. // Otherwise if we got a string, let's sort by the sort data as provided in
  7203. // the instance's options.
  7204. else if (typeof comparer === STRING_TYPE) {
  7205. sortComparer = comparer
  7206. .trim()
  7207. .split(' ')
  7208. .filter(function (val) {
  7209. return val;
  7210. })
  7211. .map(function (val) {
  7212. return val.split(':');
  7213. });
  7214. items.sort(defaultComparer);
  7215. }
  7216. // Otherwise if we got an array, let's assume it's a presorted array of the
  7217. // items and order the items based on it. Here we blindly trust that the
  7218. // presorted array consists of the same item instances as the current
  7219. // `gird._items` array.
  7220. else if (Array.isArray(comparer)) {
  7221. items.length = 0;
  7222. items.push.apply(items, comparer);
  7223. }
  7224. // Otherwise let's throw an error.
  7225. else {
  7226. sortComparer = isDescending = origItems = indexMap = null;
  7227. throw new Error('Invalid comparer argument provided.');
  7228. }
  7229. // Emit sort event.
  7230. if (this._hasListeners(EVENT_SORT)) {
  7231. this._emit(EVENT_SORT, items.slice(0), origItems);
  7232. }
  7233. // If layout is needed.
  7234. if (layout) {
  7235. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  7236. }
  7237. // Reset data (to avoid mem leaks).
  7238. sortComparer = isDescending = origItems = indexMap = null;
  7239. return this;
  7240. };
  7241. })();
  7242. /**
  7243. * Move item to another index or in place of another item.
  7244. *
  7245. * @public
  7246. * @param {(HtmlElement|Number|Item)} item
  7247. * @param {(HtmlElement|Number|Item)} position
  7248. * @param {Object} [options]
  7249. * @param {String} [options.action="move"]
  7250. * - Accepts either "move" or "swap".
  7251. * - "move" moves the item in place of the other item.
  7252. * - "swap" swaps the position of the items.
  7253. * @param {(Boolean|Function|String)} [options.layout=true]
  7254. * @returns {Grid}
  7255. */
  7256. Grid.prototype.move = function (item, position, options) {
  7257. if (this._isDestroyed || this._items.length < 2) return this;
  7258. var items = this._items;
  7259. var opts = options || {};
  7260. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  7261. var isSwap = opts.action === ACTION_SWAP;
  7262. var action = isSwap ? ACTION_SWAP : ACTION_MOVE;
  7263. var fromItem = this.getItem(item);
  7264. var toItem = this.getItem(position);
  7265. var fromIndex;
  7266. var toIndex;
  7267. // Make sure the items exist and are not the same.
  7268. if (fromItem && toItem && fromItem !== toItem) {
  7269. // Get the indices of the items.
  7270. fromIndex = items.indexOf(fromItem);
  7271. toIndex = items.indexOf(toItem);
  7272. // Do the move/swap.
  7273. if (isSwap) {
  7274. arraySwap(items, fromIndex, toIndex);
  7275. } else {
  7276. arrayMove(items, fromIndex, toIndex);
  7277. }
  7278. // Emit move event.
  7279. if (this._hasListeners(EVENT_MOVE)) {
  7280. this._emit(EVENT_MOVE, {
  7281. item: fromItem,
  7282. fromIndex: fromIndex,
  7283. toIndex: toIndex,
  7284. action: action,
  7285. });
  7286. }
  7287. // If layout is needed.
  7288. if (layout) {
  7289. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  7290. }
  7291. }
  7292. return this;
  7293. };
  7294. /**
  7295. * Send item to another Grid instance.
  7296. *
  7297. * @public
  7298. * @param {(HtmlElement|Number|Item)} item
  7299. * @param {Grid} targetGrid
  7300. * @param {(HtmlElement|Number|Item)} position
  7301. * @param {Object} [options]
  7302. * @param {HTMLElement} [options.appendTo=document.body]
  7303. * @param {(Boolean|Function|String)} [options.layoutSender=true]
  7304. * @param {(Boolean|Function|String)} [options.layoutReceiver=true]
  7305. * @returns {Grid}
  7306. */
  7307. Grid.prototype.send = function (item, targetGrid, position, options) {
  7308. if (this._isDestroyed || targetGrid._isDestroyed || this === targetGrid) return this;
  7309. // Make sure we have a valid target item.
  7310. item = this.getItem(item);
  7311. if (!item) return this;
  7312. var opts = options || {};
  7313. var container = opts.appendTo || document.body;
  7314. var layoutSender = opts.layoutSender ? opts.layoutSender : opts.layoutSender === undefined;
  7315. var layoutReceiver = opts.layoutReceiver
  7316. ? opts.layoutReceiver
  7317. : opts.layoutReceiver === undefined;
  7318. // Start the migration process.
  7319. item._migrate.start(targetGrid, position, container);
  7320. // If migration was started successfully and the item is active, let's layout
  7321. // the grids.
  7322. if (item._migrate._isActive && item._isActive) {
  7323. if (layoutSender) {
  7324. this.layout(
  7325. layoutSender === INSTANT_LAYOUT,
  7326. isFunction(layoutSender) ? layoutSender : undefined
  7327. );
  7328. }
  7329. if (layoutReceiver) {
  7330. targetGrid.layout(
  7331. layoutReceiver === INSTANT_LAYOUT,
  7332. isFunction(layoutReceiver) ? layoutReceiver : undefined
  7333. );
  7334. }
  7335. }
  7336. return this;
  7337. };
  7338. /**
  7339. * Destroy the instance.
  7340. *
  7341. * @public
  7342. * @param {Boolean} [removeElements=false]
  7343. * @returns {Grid}
  7344. */
  7345. Grid.prototype.destroy = function (removeElements) {
  7346. if (this._isDestroyed) return this;
  7347. var container = this._element;
  7348. var items = this._items.slice(0);
  7349. var layoutStyles = (this._layout && this._layout.styles) || {};
  7350. var i, prop;
  7351. // Unbind window resize event listener.
  7352. unbindLayoutOnResize(this);
  7353. // Destroy items.
  7354. for (i = 0; i < items.length; i++) items[i]._destroy(removeElements);
  7355. this._items.length = 0;
  7356. // Restore container.
  7357. removeClass(container, this._settings.containerClass);
  7358. for (prop in layoutStyles) container.style[prop] = '';
  7359. // Emit destroy event and unbind all events.
  7360. this._emit(EVENT_DESTROY);
  7361. this._emitter.destroy();
  7362. // Remove reference from the grid instances collection.
  7363. delete GRID_INSTANCES[this._id];
  7364. // Flag instance as destroyed.
  7365. this._isDestroyed = true;
  7366. return this;
  7367. };
  7368. /**
  7369. * Private prototype methods
  7370. * *************************
  7371. */
  7372. /**
  7373. * Emit a grid event.
  7374. *
  7375. * @private
  7376. * @param {String} event
  7377. * @param {...*} [arg]
  7378. */
  7379. Grid.prototype._emit = function () {
  7380. if (this._isDestroyed) return;
  7381. this._emitter.emit.apply(this._emitter, arguments);
  7382. };
  7383. /**
  7384. * Check if there are any events listeners for an event.
  7385. *
  7386. * @private
  7387. * @param {String} event
  7388. * @returns {Boolean}
  7389. */
  7390. Grid.prototype._hasListeners = function (event) {
  7391. if (this._isDestroyed) return false;
  7392. return this._emitter.countListeners(event) > 0;
  7393. };
  7394. /**
  7395. * Update container's width, height and offsets.
  7396. *
  7397. * @private
  7398. */
  7399. Grid.prototype._updateBoundingRect = function () {
  7400. var element = this._element;
  7401. var rect = element.getBoundingClientRect();
  7402. this._width = rect.width;
  7403. this._height = rect.height;
  7404. this._left = rect.left;
  7405. this._top = rect.top;
  7406. this._right = rect.right;
  7407. this._bottom = rect.bottom;
  7408. };
  7409. /**
  7410. * Update container's border sizes.
  7411. *
  7412. * @private
  7413. * @param {Boolean} left
  7414. * @param {Boolean} right
  7415. * @param {Boolean} top
  7416. * @param {Boolean} bottom
  7417. */
  7418. Grid.prototype._updateBorders = function (left, right, top, bottom) {
  7419. var element = this._element;
  7420. if (left) this._borderLeft = getStyleAsFloat(element, 'border-left-width');
  7421. if (right) this._borderRight = getStyleAsFloat(element, 'border-right-width');
  7422. if (top) this._borderTop = getStyleAsFloat(element, 'border-top-width');
  7423. if (bottom) this._borderBottom = getStyleAsFloat(element, 'border-bottom-width');
  7424. };
  7425. /**
  7426. * Refresh all of container's internal dimensions and offsets.
  7427. *
  7428. * @private
  7429. */
  7430. Grid.prototype._refreshDimensions = function () {
  7431. this._updateBoundingRect();
  7432. this._updateBorders(1, 1, 1, 1);
  7433. this._boxSizing = getStyle(this._element, 'box-sizing');
  7434. };
  7435. /**
  7436. * Calculate and apply item positions.
  7437. *
  7438. * @private
  7439. * @param {Object} layout
  7440. */
  7441. Grid.prototype._onLayoutDataReceived = (function () {
  7442. var itemsToLayout = [];
  7443. return function (layout) {
  7444. if (this._isDestroyed || !this._nextLayoutData || this._nextLayoutData.id !== layout.id) return;
  7445. var grid = this;
  7446. var instant = this._nextLayoutData.instant;
  7447. var onFinish = this._nextLayoutData.onFinish;
  7448. var numItems = layout.items.length;
  7449. var counter = numItems;
  7450. var item;
  7451. var left;
  7452. var top;
  7453. var i;
  7454. // Reset next layout data.
  7455. this._nextLayoutData = null;
  7456. if (!this._isLayoutFinished && this._hasListeners(EVENT_LAYOUT_ABORT)) {
  7457. this._emit(EVENT_LAYOUT_ABORT, this._layout.items.slice(0));
  7458. }
  7459. // Update the layout reference.
  7460. this._layout = layout;
  7461. // Update the item positions and collect all items that need to be laid
  7462. // out. It is critical that we update the item position _before_ the
  7463. // layoutStart event as the new data might be needed in the callback.
  7464. itemsToLayout.length = 0;
  7465. for (i = 0; i < numItems; i++) {
  7466. item = layout.items[i];
  7467. // Make sure we have a matching item.
  7468. if (!item) {
  7469. --counter;
  7470. continue;
  7471. }
  7472. // Get the item's new left and top values.
  7473. left = layout.slots[i * 2];
  7474. top = layout.slots[i * 2 + 1];
  7475. // Let's skip the layout process if we can. Possibly avoids a lot of DOM
  7476. // operations which saves us some CPU cycles.
  7477. if (item._canSkipLayout(left, top)) {
  7478. --counter;
  7479. continue;
  7480. }
  7481. // Update the item's position.
  7482. item._left = left;
  7483. item._top = top;
  7484. // Only active non-dragged items need to be moved.
  7485. if (item.isActive() && !item.isDragging()) {
  7486. itemsToLayout.push(item);
  7487. } else {
  7488. --counter;
  7489. }
  7490. }
  7491. // Set layout styles to the grid element.
  7492. if (layout.styles) {
  7493. setStyles(this._element, layout.styles);
  7494. }
  7495. // layoutStart event is intentionally emitted after the container element's
  7496. // dimensions are set, because otherwise there would be no hook for reacting
  7497. // to container dimension changes.
  7498. if (this._hasListeners(EVENT_LAYOUT_START)) {
  7499. this._emit(EVENT_LAYOUT_START, layout.items.slice(0), instant === true);
  7500. // Let's make sure that the current layout process has not been overridden
  7501. // in the layoutStart event, and if so, let's stop processing the aborted
  7502. // layout.
  7503. if (this._layout.id !== layout.id) return;
  7504. }
  7505. var tryFinish = function () {
  7506. if (--counter > 0) return;
  7507. var hasLayoutChanged = grid._layout.id !== layout.id;
  7508. var callback = isFunction(instant) ? instant : onFinish;
  7509. if (!hasLayoutChanged) {
  7510. grid._isLayoutFinished = true;
  7511. }
  7512. if (isFunction(callback)) {
  7513. callback(layout.items.slice(0), hasLayoutChanged);
  7514. }
  7515. if (!hasLayoutChanged && grid._hasListeners(EVENT_LAYOUT_END)) {
  7516. grid._emit(EVENT_LAYOUT_END, layout.items.slice(0));
  7517. }
  7518. };
  7519. if (!itemsToLayout.length) {
  7520. tryFinish();
  7521. return this;
  7522. }
  7523. this._isLayoutFinished = false;
  7524. for (i = 0; i < itemsToLayout.length; i++) {
  7525. if (this._layout.id !== layout.id) break;
  7526. itemsToLayout[i]._layout.start(instant === true, tryFinish);
  7527. }
  7528. if (this._layout.id === layout.id) {
  7529. itemsToLayout.length = 0;
  7530. }
  7531. return this;
  7532. };
  7533. })();
  7534. /**
  7535. * Show or hide Grid instance's items.
  7536. *
  7537. * @private
  7538. * @param {Item[]} items
  7539. * @param {Boolean} toVisible
  7540. * @param {Object} [options]
  7541. * @param {Boolean} [options.instant=false]
  7542. * @param {Boolean} [options.syncWithLayout=true]
  7543. * @param {Function} [options.onFinish]
  7544. * @param {(Boolean|Function|String)} [options.layout=true]
  7545. */
  7546. Grid.prototype._setItemsVisibility = function (items, toVisible, options) {
  7547. var grid = this;
  7548. var targetItems = items.slice(0);
  7549. var opts = options || {};
  7550. var isInstant = opts.instant === true;
  7551. var callback = opts.onFinish;
  7552. var layout = opts.layout ? opts.layout : opts.layout === undefined;
  7553. var counter = targetItems.length;
  7554. var startEvent = toVisible ? EVENT_SHOW_START : EVENT_HIDE_START;
  7555. var endEvent = toVisible ? EVENT_SHOW_END : EVENT_HIDE_END;
  7556. var method = toVisible ? 'show' : 'hide';
  7557. var needsLayout = false;
  7558. var completedItems = [];
  7559. var hiddenItems = [];
  7560. var item;
  7561. var i;
  7562. // If there are no items call the callback, but don't emit any events.
  7563. if (!counter) {
  7564. if (isFunction(callback)) callback(targetItems);
  7565. return;
  7566. }
  7567. // Prepare the items.
  7568. for (i = 0; i < targetItems.length; i++) {
  7569. item = targetItems[i];
  7570. // If inactive item is shown or active item is hidden we need to do
  7571. // layout.
  7572. if ((toVisible && !item._isActive) || (!toVisible && item._isActive)) {
  7573. needsLayout = true;
  7574. }
  7575. // If inactive item is shown we also need to do a little hack to make the
  7576. // item not animate it's next positioning (layout).
  7577. item._layout._skipNextAnimation = !!(toVisible && !item._isActive);
  7578. // If a hidden item is being shown we need to refresh the item's
  7579. // dimensions.
  7580. if (toVisible && item._visibility._isHidden) {
  7581. hiddenItems.push(item);
  7582. }
  7583. // Add item to layout or remove it from layout.
  7584. if (toVisible) {
  7585. item._addToLayout();
  7586. } else {
  7587. item._removeFromLayout();
  7588. }
  7589. }
  7590. // Force refresh the dimensions of all hidden items.
  7591. if (hiddenItems.length) {
  7592. this.refreshItems(hiddenItems, true);
  7593. hiddenItems.length = 0;
  7594. }
  7595. // Show the items in sync with the next layout.
  7596. function triggerVisibilityChange() {
  7597. if (needsLayout && opts.syncWithLayout !== false) {
  7598. grid.off(EVENT_LAYOUT_START, triggerVisibilityChange);
  7599. }
  7600. if (grid._hasListeners(startEvent)) {
  7601. grid._emit(startEvent, targetItems.slice(0));
  7602. }
  7603. for (i = 0; i < targetItems.length; i++) {
  7604. // Make sure the item is still in the original grid. There is a chance
  7605. // that the item starts migrating before tiggerVisibilityChange is called.
  7606. if (targetItems[i]._gridId !== grid._id) {
  7607. if (--counter < 1) {
  7608. if (isFunction(callback)) callback(completedItems.slice(0));
  7609. if (grid._hasListeners(endEvent)) grid._emit(endEvent, completedItems.slice(0));
  7610. }
  7611. continue;
  7612. }
  7613. targetItems[i]._visibility[method](isInstant, function (interrupted, item) {
  7614. // If the current item's animation was not interrupted add it to the
  7615. // completedItems array.
  7616. if (!interrupted) completedItems.push(item);
  7617. // If all items have finished their animations call the callback
  7618. // and emit showEnd/hideEnd event.
  7619. if (--counter < 1) {
  7620. if (isFunction(callback)) callback(completedItems.slice(0));
  7621. if (grid._hasListeners(endEvent)) grid._emit(endEvent, completedItems.slice(0));
  7622. }
  7623. });
  7624. }
  7625. }
  7626. // Trigger the visibility change, either async with layout or instantly.
  7627. if (needsLayout && opts.syncWithLayout !== false) {
  7628. this.on(EVENT_LAYOUT_START, triggerVisibilityChange);
  7629. } else {
  7630. triggerVisibilityChange();
  7631. }
  7632. // Trigger layout if needed.
  7633. if (needsLayout && layout) {
  7634. this.layout(layout === INSTANT_LAYOUT, isFunction(layout) ? layout : undefined);
  7635. }
  7636. };
  7637. /**
  7638. * Private helpers
  7639. * ***************
  7640. */
  7641. /**
  7642. * Merge default settings with user settings. The returned object is a new
  7643. * object with merged values. The merging is a deep merge meaning that all
  7644. * objects and arrays within the provided settings objects will be also merged
  7645. * so that modifying the values of the settings object will have no effect on
  7646. * the returned object.
  7647. *
  7648. * @param {Object} defaultSettings
  7649. * @param {Object} [userSettings]
  7650. * @returns {Object} Returns a new object.
  7651. */
  7652. function mergeSettings(defaultSettings, userSettings) {
  7653. // Create a fresh copy of default settings.
  7654. var settings = mergeObjects({}, defaultSettings);
  7655. // Merge user settings to default settings.
  7656. if (userSettings) {
  7657. settings = mergeObjects(settings, userSettings);
  7658. }
  7659. // Handle visible/hidden styles manually so that the whole object is
  7660. // overridden instead of the props.
  7661. if (userSettings && userSettings.visibleStyles) {
  7662. settings.visibleStyles = userSettings.visibleStyles;
  7663. } else if (defaultSettings && defaultSettings.visibleStyles) {
  7664. settings.visibleStyles = defaultSettings.visibleStyles;
  7665. }
  7666. if (userSettings && userSettings.hiddenStyles) {
  7667. settings.hiddenStyles = userSettings.hiddenStyles;
  7668. } else if (defaultSettings && defaultSettings.hiddenStyles) {
  7669. settings.hiddenStyles = defaultSettings.hiddenStyles;
  7670. }
  7671. return settings;
  7672. }
  7673. /**
  7674. * Merge two objects recursively (deep merge). The source object's properties
  7675. * are merged to the target object.
  7676. *
  7677. * @param {Object} target
  7678. * - The target object.
  7679. * @param {Object} source
  7680. * - The source object.
  7681. * @returns {Object} Returns the target object.
  7682. */
  7683. function mergeObjects(target, source) {
  7684. var sourceKeys = Object.keys(source);
  7685. var length = sourceKeys.length;
  7686. var isSourceObject;
  7687. var propName;
  7688. var i;
  7689. for (i = 0; i < length; i++) {
  7690. propName = sourceKeys[i];
  7691. isSourceObject = isPlainObject(source[propName]);
  7692. // If target and source values are both objects, merge the objects and
  7693. // assign the merged value to the target property.
  7694. if (isPlainObject(target[propName]) && isSourceObject) {
  7695. target[propName] = mergeObjects(mergeObjects({}, target[propName]), source[propName]);
  7696. continue;
  7697. }
  7698. // If source's value is object and target's is not let's clone the object as
  7699. // the target's value.
  7700. if (isSourceObject) {
  7701. target[propName] = mergeObjects({}, source[propName]);
  7702. continue;
  7703. }
  7704. // If source's value is an array let's clone the array as the target's
  7705. // value.
  7706. if (Array.isArray(source[propName])) {
  7707. target[propName] = source[propName].slice(0);
  7708. continue;
  7709. }
  7710. // In all other cases let's just directly assign the source's value as the
  7711. // target's value.
  7712. target[propName] = source[propName];
  7713. }
  7714. return target;
  7715. }
  7716. /**
  7717. * Collect and return initial items for grid.
  7718. *
  7719. * @param {HTMLElement} gridElement
  7720. * @param {?(HTMLElement[]|NodeList|HtmlCollection|String)} elements
  7721. * @returns {(HTMLElement[]|NodeList|HtmlCollection)}
  7722. */
  7723. function getInitialGridElements(gridElement, elements) {
  7724. // If we have a wildcard selector let's return all the children.
  7725. if (elements === '*') {
  7726. return gridElement.children;
  7727. }
  7728. // If we have some more specific selector, let's filter the elements.
  7729. if (typeof elements === STRING_TYPE) {
  7730. var result = [];
  7731. var children = gridElement.children;
  7732. for (var i = 0; i < children.length; i++) {
  7733. if (elementMatches(children[i], elements)) {
  7734. result.push(children[i]);
  7735. }
  7736. }
  7737. return result;
  7738. }
  7739. // If we have an array of elements or a node list.
  7740. if (Array.isArray(elements) || isNodeList(elements)) {
  7741. return elements;
  7742. }
  7743. // Otherwise just return an empty array.
  7744. return [];
  7745. }
  7746. /**
  7747. * Bind grid's resize handler to window.
  7748. *
  7749. * @param {Grid} grid
  7750. * @param {(Number|Boolean)} delay
  7751. */
  7752. function bindLayoutOnResize(grid, delay) {
  7753. if (typeof delay !== NUMBER_TYPE) {
  7754. delay = delay === true ? 0 : -1;
  7755. }
  7756. if (delay >= 0) {
  7757. grid._resizeHandler = debounce(function () {
  7758. grid.refreshItems().layout();
  7759. }, delay);
  7760. window.addEventListener('resize', grid._resizeHandler);
  7761. }
  7762. }
  7763. /**
  7764. * Unbind grid's resize handler from window.
  7765. *
  7766. * @param {Grid} grid
  7767. */
  7768. function unbindLayoutOnResize(grid) {
  7769. if (grid._resizeHandler) {
  7770. grid._resizeHandler(true);
  7771. window.removeEventListener('resize', grid._resizeHandler);
  7772. grid._resizeHandler = null;
  7773. }
  7774. }
  7775. /**
  7776. * Normalize style declaration object, returns a normalized (new) styles object
  7777. * (prefixed properties and invalid properties removed).
  7778. *
  7779. * @param {Object} styles
  7780. * @returns {Object}
  7781. */
  7782. function normalizeStyles(styles) {
  7783. var normalized = {};
  7784. var docElemStyle = document.documentElement.style;
  7785. var prop, prefixedProp;
  7786. // Normalize visible styles (prefix and remove invalid).
  7787. for (prop in styles) {
  7788. if (!styles[prop]) continue;
  7789. prefixedProp = getPrefixedPropName(docElemStyle, prop);
  7790. if (!prefixedProp) continue;
  7791. normalized[prefixedProp] = styles[prop];
  7792. }
  7793. return normalized;
  7794. }
  7795. /**
  7796. * Create index map from items.
  7797. *
  7798. * @param {Item[]} items
  7799. * @returns {Object}
  7800. */
  7801. function createIndexMap(items) {
  7802. var result = {};
  7803. for (var i = 0; i < items.length; i++) {
  7804. result[items[i]._id] = i;
  7805. }
  7806. return result;
  7807. }
  7808. /**
  7809. * Sort comparer function for items' index map.
  7810. *
  7811. * @param {Object} indexMap
  7812. * @param {Item} itemA
  7813. * @param {Item} itemB
  7814. * @returns {Number}
  7815. */
  7816. function compareIndexMap(indexMap, itemA, itemB) {
  7817. var indexA = indexMap[itemA._id];
  7818. var indexB = indexMap[itemB._id];
  7819. return indexA - indexB;
  7820. }
  7821. return Grid;
  7822. })));